github.com/datastax/go-cassandra-native-protocol@v0.0.0-20220706104457-5e8aad05cf90/message/query_options.go (about)

     1  // Copyright 2020 DataStax
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package message
    16  
    17  import (
    18  	"errors"
    19  	"fmt"
    20  	"io"
    21  
    22  	"github.com/datastax/go-cassandra-native-protocol/primitive"
    23  )
    24  
    25  // QueryOptions is the set of options common to Query and Execute messages.
    26  // +k8s:deepcopy-gen=true
    27  type QueryOptions struct {
    28  	// The consistency level to use when executing the query. This field is mandatory; its default is ANY, as the
    29  	// zero value of primitive.ConsistencyLevel is primitive.ConsistencyLevelAny. Note that ANY is NOT suitable for
    30  	// read queries.
    31  	Consistency primitive.ConsistencyLevel
    32  
    33  	// The positional values of the associated query. Positional values are designated in query strings with a
    34  	// question mark bind marker ('?'). Positional values are valid for all protocol versions.
    35  	// It is illegal to use both positional and named values at the same time. If this happens, positional values will
    36  	// be used and named values will be silently ignored.
    37  	PositionalValues []*primitive.Value
    38  
    39  	// The named values of the associated query. Named values are designated in query strings with a
    40  	// a bind marker starting with a colon (e.g. ':var1'). Named values can only be used with protocol version 3 or
    41  	// higher.
    42  	// It is illegal to use both positional and named values at the same time. If this happens, positional values will
    43  	// be used and named values will be silently ignored.
    44  	NamedValues map[string]*primitive.Value
    45  
    46  	// If true, asks the server to skip result set metadata when sending results. In such case, the RowsResult message
    47  	// returned as a response to the query (if any) will contain no result set metadata, and will have the NO_METADATA
    48  	// flag set; in other words, RowsResult.Metadata will be present but RowsResult.Metadata.Columns will be nil.
    49  	SkipMetadata bool
    50  
    51  	// The desired page size. A value of zero or negative is usually interpreted as "no pagination" and can result
    52  	// in the entire result set being returned in one single page.
    53  	PageSize int32
    54  
    55  	// Whether the page size is expressed in number of rows or number of bytes. Valid for DSE protocol versions only.
    56  	PageSizeInBytes bool
    57  
    58  	// PagingState is a [bytes] value coming from a previously-received RowsResult.Metadata object. If provided, the
    59  	// query will be executed but starting from a given paging state.
    60  	PagingState []byte
    61  
    62  	// The (optional) serial consistency level to use when executing the query. Valid for protocol versions 2 and
    63  	// higher.
    64  	SerialConsistency *primitive.ConsistencyLevel
    65  
    66  	// The default timestamp for the query in microseconds (negative values are discouraged but supported for
    67  	// backward compatibility reasons except for the smallest negative value (-2^63) that is forbidden). If provided,
    68  	// this will replace the server-side assigned timestamp as default timestamp. Note that a timestamp in the query
    69  	// itself (that is, if the query has a USING TIMESTAMP clause) will still override this timestamp.
    70  	// Default timestamps are valid for protocol versions 3 and higher.
    71  	DefaultTimestamp *int64
    72  
    73  	// Valid for protocol version 5 and DSE protocol version 2 only.
    74  	Keyspace string
    75  
    76  	// Introduced in Protocol Version 5, not present in DSE protocol versions.
    77  	NowInSeconds *int32
    78  
    79  	// Valid only for DSE protocol versions.
    80  	ContinuousPagingOptions *ContinuousPagingOptions
    81  }
    82  
    83  func (o *QueryOptions) String() string {
    84  	return fmt.Sprintf(
    85  		"[cl=%v, positionalVals=%v, namedVals=%v, skip=%v, psize=%v, state=%v, serialCl=%v]",
    86  		o.Consistency,
    87  		o.PositionalValues,
    88  		o.NamedValues,
    89  		o.SkipMetadata,
    90  		o.PageSize,
    91  		o.PagingState,
    92  		o.SerialConsistency)
    93  }
    94  
    95  func (o *QueryOptions) Flags() primitive.QueryFlag {
    96  	var flags primitive.QueryFlag
    97  	// prefer positional values, if provided, and ignore named ones.
    98  	if o.PositionalValues != nil {
    99  		flags = flags.Add(primitive.QueryFlagValues)
   100  	} else if o.NamedValues != nil {
   101  		flags = flags.Add(primitive.QueryFlagValues)
   102  		flags = flags.Add(primitive.QueryFlagValueNames)
   103  	}
   104  	if o.SkipMetadata {
   105  		flags = flags.Add(primitive.QueryFlagSkipMetadata)
   106  	}
   107  	if o.PageSize > 0 {
   108  		flags = flags.Add(primitive.QueryFlagPageSize)
   109  		if o.PageSizeInBytes {
   110  			flags = flags.Add(primitive.QueryFlagDsePageSizeBytes)
   111  		}
   112  	}
   113  	if o.PagingState != nil {
   114  		flags = flags.Add(primitive.QueryFlagPagingState)
   115  	}
   116  	if o.SerialConsistency != nil {
   117  		flags = flags.Add(primitive.QueryFlagSerialConsistency)
   118  	}
   119  	if o.DefaultTimestamp != nil {
   120  		flags = flags.Add(primitive.QueryFlagDefaultTimestamp)
   121  	}
   122  	if o.Keyspace != "" {
   123  		flags = flags.Add(primitive.QueryFlagWithKeyspace)
   124  	}
   125  	if o.NowInSeconds != nil {
   126  		flags = flags.Add(primitive.QueryFlagNowInSeconds)
   127  	}
   128  	if o.ContinuousPagingOptions != nil {
   129  		flags = flags.Add(primitive.QueryFlagDseWithContinuousPagingOptions)
   130  	}
   131  	return flags
   132  }
   133  
   134  func EncodeQueryOptions(options *QueryOptions, dest io.Writer, version primitive.ProtocolVersion) (err error) {
   135  	if options == nil {
   136  		options = &QueryOptions{} // use defaults if nil provided
   137  	}
   138  	if err := primitive.CheckValidConsistencyLevel(options.Consistency); err != nil {
   139  		return err
   140  	} else if err = primitive.WriteShort(uint16(options.Consistency), dest); err != nil {
   141  		return fmt.Errorf("cannot write consistency: %w", err)
   142  	}
   143  	flags := options.Flags()
   144  	if version.Uses4BytesQueryFlags() {
   145  		if err = primitive.WriteInt(int32(flags), dest); err != nil {
   146  			return fmt.Errorf("cannot write flags: %w", err)
   147  		}
   148  	} else {
   149  		if err = primitive.WriteByte(uint8(flags), dest); err != nil {
   150  			return fmt.Errorf("cannot write flags: %w", err)
   151  		}
   152  	}
   153  	if flags.Contains(primitive.QueryFlagValues) {
   154  		if flags.Contains(primitive.QueryFlagValueNames) {
   155  			if err = primitive.WriteNamedValues(options.NamedValues, dest, version); err != nil {
   156  				return fmt.Errorf("cannot write named [value]s: %w", err)
   157  			}
   158  		} else {
   159  			if err = primitive.WritePositionalValues(options.PositionalValues, dest, version); err != nil {
   160  				return fmt.Errorf("cannot write positional [value]s: %w", err)
   161  			}
   162  		}
   163  	}
   164  	if flags.Contains(primitive.QueryFlagPageSize) {
   165  		if err = primitive.WriteInt(options.PageSize, dest); err != nil {
   166  			return fmt.Errorf("cannot write page size: %w", err)
   167  		}
   168  	}
   169  	if flags.Contains(primitive.QueryFlagPagingState) {
   170  		if err = primitive.WriteBytes(options.PagingState, dest); err != nil {
   171  			return fmt.Errorf("cannot write paging state: %w", err)
   172  		}
   173  	}
   174  	if flags.Contains(primitive.QueryFlagSerialConsistency) {
   175  		if err := primitive.CheckSerialConsistencyLevel(*options.SerialConsistency); err != nil {
   176  			return err
   177  		} else if err = primitive.WriteShort(uint16(*options.SerialConsistency), dest); err != nil {
   178  			return fmt.Errorf("cannot write serial consistency: %w", err)
   179  		}
   180  	}
   181  	if flags.Contains(primitive.QueryFlagDefaultTimestamp) {
   182  		if err = primitive.WriteLong(*options.DefaultTimestamp, dest); err != nil {
   183  			return fmt.Errorf("cannot write default timestamp: %w", err)
   184  		}
   185  	}
   186  	if flags.Contains(primitive.QueryFlagWithKeyspace) {
   187  		if options.Keyspace == "" {
   188  			return errors.New("cannot write empty keyspace")
   189  		} else if err = primitive.WriteString(options.Keyspace, dest); err != nil {
   190  			return fmt.Errorf("cannot write keyspace: %w", err)
   191  		}
   192  	}
   193  	if flags.Contains(primitive.QueryFlagNowInSeconds) {
   194  		if err = primitive.WriteInt(*options.NowInSeconds, dest); err != nil {
   195  			return fmt.Errorf("cannot write now-in-seconds: %w", err)
   196  		}
   197  	}
   198  	if flags.Contains(primitive.QueryFlagDseWithContinuousPagingOptions) {
   199  		if err = EncodeContinuousPagingOptions(options.ContinuousPagingOptions, dest, version); err != nil {
   200  			return fmt.Errorf("cannot encode continuous paging options: %w", err)
   201  		}
   202  	}
   203  	return nil
   204  }
   205  
   206  func LengthOfQueryOptions(options *QueryOptions, version primitive.ProtocolVersion) (length int, err error) {
   207  	if options == nil {
   208  		options = &QueryOptions{} // use defaults if nil provided
   209  	}
   210  	length += primitive.LengthOfShort // consistency level
   211  	if version.Uses4BytesQueryFlags() {
   212  		length += primitive.LengthOfInt
   213  	} else {
   214  		length += primitive.LengthOfByte
   215  	}
   216  	var s int
   217  	flags := options.Flags()
   218  	if flags.Contains(primitive.QueryFlagValues) {
   219  		if flags.Contains(primitive.QueryFlagValueNames) {
   220  			s, err = primitive.LengthOfNamedValues(options.NamedValues)
   221  		} else {
   222  			s, err = primitive.LengthOfPositionalValues(options.PositionalValues)
   223  		}
   224  	}
   225  	if err != nil {
   226  		return -1, fmt.Errorf("cannot compute length of query options values: %w", err)
   227  	}
   228  	length += s
   229  	if flags.Contains(primitive.QueryFlagPageSize) {
   230  		length += primitive.LengthOfInt
   231  	}
   232  	if flags.Contains(primitive.QueryFlagPagingState) {
   233  		length += primitive.LengthOfBytes(options.PagingState)
   234  	}
   235  	if flags.Contains(primitive.QueryFlagSerialConsistency) {
   236  		length += primitive.LengthOfShort
   237  	}
   238  	if flags.Contains(primitive.QueryFlagDefaultTimestamp) {
   239  		length += primitive.LengthOfLong
   240  	}
   241  	if flags.Contains(primitive.QueryFlagWithKeyspace) {
   242  		length += primitive.LengthOfString(options.Keyspace)
   243  	}
   244  	if flags.Contains(primitive.QueryFlagNowInSeconds) {
   245  		length += primitive.LengthOfInt
   246  	}
   247  	if flags.Contains(primitive.QueryFlagDseWithContinuousPagingOptions) {
   248  		if lengthOfContinuousPagingOptions, err := LengthOfContinuousPagingOptions(options.ContinuousPagingOptions, version); err != nil {
   249  			return -1, fmt.Errorf("cannot compute length of continuous paging options: %w", err)
   250  		} else {
   251  			length += lengthOfContinuousPagingOptions
   252  		}
   253  	}
   254  	return
   255  }
   256  
   257  func DecodeQueryOptions(source io.Reader, version primitive.ProtocolVersion) (options *QueryOptions, err error) {
   258  	options = &QueryOptions{}
   259  	var consistency uint16
   260  	if consistency, err = primitive.ReadShort(source); err != nil {
   261  		return nil, fmt.Errorf("cannot read consistency: %w", err)
   262  	}
   263  	options.Consistency = primitive.ConsistencyLevel(consistency)
   264  	if err = primitive.CheckValidConsistencyLevel(options.Consistency); err != nil {
   265  		return nil, err
   266  	}
   267  	var flags primitive.QueryFlag
   268  	if version.Uses4BytesQueryFlags() {
   269  		var f int32
   270  		f, err = primitive.ReadInt(source)
   271  		flags = primitive.QueryFlag(f)
   272  	} else {
   273  		var f uint8
   274  		f, err = primitive.ReadByte(source)
   275  		flags = primitive.QueryFlag(f)
   276  	}
   277  	if err != nil {
   278  		return nil, fmt.Errorf("cannot read flags: %w", err)
   279  	}
   280  	if flags.Contains(primitive.QueryFlagValues) {
   281  		if flags.Contains(primitive.QueryFlagValueNames) {
   282  			options.NamedValues, err = primitive.ReadNamedValues(source, version)
   283  		} else {
   284  			options.PositionalValues, err = primitive.ReadPositionalValues(source, version)
   285  		}
   286  	}
   287  	if err != nil {
   288  		return nil, fmt.Errorf("cannot read [value]s: %w", err)
   289  	}
   290  	options.SkipMetadata = flags.Contains(primitive.QueryFlagSkipMetadata)
   291  	if flags.Contains(primitive.QueryFlagPageSize) {
   292  		if options.PageSize, err = primitive.ReadInt(source); err != nil {
   293  			return nil, fmt.Errorf("cannot read page size: %w", err)
   294  		}
   295  		if flags.Contains(primitive.QueryFlagDsePageSizeBytes) {
   296  			options.PageSizeInBytes = true
   297  		}
   298  	}
   299  	if flags.Contains(primitive.QueryFlagPagingState) {
   300  		if options.PagingState, err = primitive.ReadBytes(source); err != nil {
   301  			return nil, fmt.Errorf("cannot read paging state: %w", err)
   302  		}
   303  	}
   304  	if flags.Contains(primitive.QueryFlagSerialConsistency) {
   305  		var optionsSerialConsistencyUint uint16
   306  		if optionsSerialConsistencyUint, err = primitive.ReadShort(source); err != nil {
   307  			return nil, fmt.Errorf("cannot read serial consistency: %w", err)
   308  		}
   309  		optionsSerialConsistency := primitive.ConsistencyLevel(optionsSerialConsistencyUint)
   310  		if err = primitive.CheckValidConsistencyLevel(optionsSerialConsistency); err != nil {
   311  			return nil, err
   312  		}
   313  		options.SerialConsistency = &optionsSerialConsistency
   314  	}
   315  	if flags.Contains(primitive.QueryFlagDefaultTimestamp) {
   316  		var optionsDefaultTimestamp int64
   317  		if optionsDefaultTimestamp, err = primitive.ReadLong(source); err != nil {
   318  			return nil, fmt.Errorf("cannot read default timestamp: %w", err)
   319  		}
   320  		options.DefaultTimestamp = &optionsDefaultTimestamp
   321  	}
   322  	if flags.Contains(primitive.QueryFlagWithKeyspace) {
   323  		if options.Keyspace, err = primitive.ReadString(source); err != nil {
   324  			return nil, fmt.Errorf("cannot read keyspace: %w", err)
   325  		}
   326  	}
   327  	if flags.Contains(primitive.QueryFlagNowInSeconds) {
   328  		var optionsNowInSeconds int32
   329  		if optionsNowInSeconds, err = primitive.ReadInt(source); err != nil {
   330  			return nil, fmt.Errorf("cannot read now-in-seconds: %w", err)
   331  		}
   332  		options.NowInSeconds = &optionsNowInSeconds
   333  	}
   334  	if flags.Contains(primitive.QueryFlagDseWithContinuousPagingOptions) {
   335  		if options.ContinuousPagingOptions, err = DecodeContinuousPagingOptions(source, version); err != nil {
   336  			return nil, fmt.Errorf("cannot read continuous paging options: %w", err)
   337  		}
   338  	}
   339  	return options, nil
   340  }