aerospike 2.25.0 → 2.27.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 12a4c3d56df63146053710f4438b8030672d76d9fa01cf4ddf5453429d275e11
4
- data.tar.gz: '025678955679feb936123140c4bce14117a85ac954eb0e3d294960269cfa9b92'
3
+ metadata.gz: 3fe67950bfa737ce26a8a0e823cd84424219c6ce41720e0202ca93d4dcab2c1d
4
+ data.tar.gz: 83976d88565599f00136967bd86fe832a88783518c51be2789c1463fa06e795e
5
5
  SHA512:
6
- metadata.gz: 2e5d828c912aaa9a189de8c7d1441bb255b4ae73a6075d262b75b3cb10f8315b4afc2d5853cf015cfd829f9fd4cbc14014bd007a76f2e2de2426aad6e489ae62
7
- data.tar.gz: 53d8e293b39e14b99ce747c06f125b2cdc695c7654ea7d21cbce927e2f8f1064eab0ab2f0b585308353a36a8f345eeafb4c21854b0184ebe652a32a83afddf19
6
+ metadata.gz: e018d80b673081cdbf229aa5c48dd7ebbeb9a127492cc04c651a4e56bab92d7764aba82ffb3e3783f6a6f34755ded66be58c3bf5a9e110be61c0c21e20128932
7
+ data.tar.gz: 8e02a4d37b620f838dde53b63d4af3fe20146905dd4f3a30c14f97a83453c69f0dccfbaf1f44a777bf4ab53e89583cc7a6e4343204e6749b1d4fb888f0a7200f
data/CHANGELOG.md CHANGED
@@ -2,10 +2,24 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [2.27.0] 2023-05-18
6
+ - **New Features**
7
+ - [CLIENT-1176] Support write operations in background query
8
+
9
+
10
+ ## [2.26.0] 2022-12-02
11
+
12
+ - **New Features**
13
+ - [CLIENT-1808] Support creating a secondary index on elements within a CDT using `Context`.
14
+ - [CLIENT-1991] Add base64 encoding methods to `Context`.
15
+ - [CLIENT-2007] Support using `Context` in query filters.
16
+
5
17
  ## [2.25.0] 2022-11-28
6
18
 
7
19
  - **New Features**
8
20
 
21
+ - [CLIENT-1984] Support scan-show and query-show info commands.
22
+
9
23
  - [CLIENT-1362] Adds support Aerospike Expression filters. Expression filters are now supported on all commands, including `Client#get`, `Client#put`, `Client#delete`, `Client#operate`, `Client#scan`, `Client#query`, `Client#execute_udf`, etc.
10
24
 
11
25
  - Adds `Policy#filter_exp` and `Policy#fail_on_filtered_out`
@@ -17,16 +17,17 @@
17
17
  # License for the specific language governing permissions and limitations under
18
18
  # the License.
19
19
 
20
+ require "base64"
21
+
20
22
  module Aerospike
21
23
  module CDT
22
24
 
23
- ##
24
- # Nested CDT context. Identifies the location of nested list/map to apply the operation.
25
- # for the current level.
26
- # An array of CTX identifies location of the list/map on multiple
27
- # levels on nesting.
25
+ ##
26
+ # Nested CDT context. Identifies the location of nested list/map to apply the operation.
27
+ # for the current level.
28
+ # An array of CTX identifies location of the list/map on multiple
29
+ # levels on nesting.
28
30
  class Context
29
-
30
31
  attr_accessor :id, :value
31
32
 
32
33
  def initialize(id, value)
@@ -37,64 +38,64 @@ module Aerospike
37
38
  ##
38
39
  # Create list with given type at index offset, given an order and pad.
39
40
  def self.list_index_create(index, order, pad)
40
- Context.new(0x10 | ListOrder.flag(order, pad), index)
41
- end
42
-
43
- ##
44
- # Lookup list by index offset.
45
- # If the index is negative, the resolved index starts backwards from end of list.
46
- # If an index is out of bounds, a parameter error will be returned.
47
- # Examples:
48
- # 0: First item.
49
- # 4: Fifth item.
50
- # -1: Last item.
51
- # -3: Third to last item.
52
- def self.list_index(index)
53
- Context.new(0x10, index)
54
- end
55
-
56
- ##
57
- # Lookup list by rank.
58
- # 0 = smallest value
59
- # N = Nth smallest value
60
- # -1 = largest value
61
- def self.list_rank(rank)
62
- Context.new(0x11, rank)
63
- end
64
-
65
- ##
66
- # Lookup list by value.
67
- def self.list_value(key)
68
- Context.new(0x13, key)
69
- end
70
-
71
- ##
72
- # Lookup map by index offset.
73
- # If the index is negative, the resolved index starts backwards from end of list.
74
- # If an index is out of bounds, a parameter error will be returned.
75
- # Examples:
76
- # 0: First item.
77
- # 4: Fifth item.
78
- # -1: Last item.
79
- # -3: Third to last item.
80
- def self.map_index(index)
81
- Context.new(0x20, index)
82
- end
83
-
84
- ##
85
- # Lookup map by rank.
86
- # 0 = smallest value
87
- # N = Nth smallest value
88
- # -1 = largest value
89
- def self.map_rank(rank)
90
- Context.new(0x21, rank)
91
- end
92
-
93
- ##
94
- # Lookup map by key.
95
- def self.map_key(key)
96
- Context.new(0x22, key)
97
- end
41
+ Context.new(0x10 | ListOrder.flag(order, pad), index)
42
+ end
43
+
44
+ ##
45
+ # Lookup list by index offset.
46
+ # If the index is negative, the resolved index starts backwards from end of list.
47
+ # If an index is out of bounds, a parameter error will be returned.
48
+ # Examples:
49
+ # 0: First item.
50
+ # 4: Fifth item.
51
+ # -1: Last item.
52
+ # -3: Third to last item.
53
+ def self.list_index(index)
54
+ Context.new(0x10, index)
55
+ end
56
+
57
+ ##
58
+ # Lookup list by rank.
59
+ # 0 = smallest value
60
+ # N = Nth smallest value
61
+ # -1 = largest value
62
+ def self.list_rank(rank)
63
+ Context.new(0x11, rank)
64
+ end
65
+
66
+ ##
67
+ # Lookup list by value.
68
+ def self.list_value(key)
69
+ Context.new(0x13, key)
70
+ end
71
+
72
+ ##
73
+ # Lookup map by index offset.
74
+ # If the index is negative, the resolved index starts backwards from end of list.
75
+ # If an index is out of bounds, a parameter error will be returned.
76
+ # Examples:
77
+ # 0: First item.
78
+ # 4: Fifth item.
79
+ # -1: Last item.
80
+ # -3: Third to last item.
81
+ def self.map_index(index)
82
+ Context.new(0x20, index)
83
+ end
84
+
85
+ ##
86
+ # Lookup map by rank.
87
+ # 0 = smallest value
88
+ # N = Nth smallest value
89
+ # -1 = largest value
90
+ def self.map_rank(rank)
91
+ Context.new(0x21, rank)
92
+ end
93
+
94
+ ##
95
+ # Lookup map by key.
96
+ def self.map_key(key)
97
+ Context.new(0x22, key)
98
+ end
98
99
 
99
100
  ##
100
101
  # Create map with given type at map key.
@@ -102,12 +103,78 @@ module Aerospike
102
103
  Context.new(0x22 | order[:flag], key)
103
104
  end
104
105
 
105
- ##
106
- # Lookup map by value.
107
- def self.map_value(key)
108
- Context.new(0x23, key)
109
- end
106
+ ##
107
+ # Lookup map by value.
108
+ def self.map_value(key)
109
+ Context.new(0x23, key)
110
+ end
111
+
112
+ ##
113
+ # Encodes the context via message pack.
114
+ def self.pack(packer, ctx)
115
+ unless ctx.to_a.empty?
116
+ packer.write_array_header(2)
117
+ ctx.each do |c|
118
+ packer.write(c.id)
119
+ Value.of(c.value)
120
+ end
121
+ end
122
+ end
123
+
124
+ ##
125
+ # Encodes the context via message pack and return the results.
126
+ def self.bytes(ctx)
127
+ unless ctx.to_a.empty?
128
+ Packer.use do |packer|
129
+ packer.write_array_header(ctx.length * 2)
130
+ ctx.each do |c|
131
+ packer.write(c.id)
132
+ Value.of(c.value).pack(packer)
133
+ end
134
+ return packer.bytes
135
+ end
136
+ end
137
+ nil
138
+ end
139
+
140
+ def ==(other)
141
+ self.id == other.id && self.value == other.value
142
+ end
110
143
 
144
+ ##
145
+ # decodes the base64 encoded messagepack byte array
146
+ # and converts it to an array of Context.
147
+ def self.from_bytes(buf)
148
+ list = nil
149
+ Unpacker.use do |unpacker|
150
+ list = unpacker.unpack(buf)
151
+ end
152
+
153
+ unless list.length % 2 == 0
154
+ raise Exceptions::Aerospike.new(Aerospike::ResultCode::PARAMETER_ERROR, "Invalid buffer")
155
+ end
156
+
157
+ list.each_slice(2).map { |id, value| Context.new(id, value) }
158
+ end
159
+
160
+ ##
161
+ # Encodes the context array to messagepack and then encodes
162
+ # the resulting byte array to base64.
163
+ def self.base64(ctx)
164
+ unless ctx.to_a.empty?
165
+ data = self.bytes(ctx)
166
+ return Base64.strict_encode64(data).force_encoding("binary")
167
+ end
168
+ ""
169
+ end
170
+
171
+ ##
172
+ # Decodes the byte array to messagepack and then decodes
173
+ # the resulting byte array to an array of Context.
174
+ def self.from_base64(buf)
175
+ bytes = Base64.strict_decode64(buf)
176
+ self.from_bytes(bytes)
177
+ end
111
178
  end
112
179
  end
113
180
  end
@@ -1,4 +1,4 @@
1
- # Copyright 2014-2020 Aerospike, Inc.
1
+ # Copyright 2014-2023 Aerospike, Inc.
2
2
  #
3
3
  # Portions may be licensed to Aerospike, Inc. under one or more contributor
4
4
  # license agreements.
@@ -225,7 +225,6 @@ module Aerospike
225
225
  policy = create_policy(options, Policy, default_info_policy)
226
226
 
227
227
  node = @cluster.random_node
228
- conn = node.get_connection(policy.timeout)
229
228
 
230
229
  if set_name && !set_name.to_s.strip.empty?
231
230
  str_cmd = "truncate:namespace=#{namespace}"
@@ -566,7 +565,8 @@ module Aerospike
566
565
  # This method is only supported by Aerospike 3 servers.
567
566
  # index_type should be :string, :numeric or :geo2dsphere (requires server version 3.7 or later)
568
567
  # collection_type should be :list, :mapkeys or :mapvalues
569
- def create_index(namespace, set_name, index_name, bin_name, index_type, collection_type = nil, options = nil)
568
+ # ctx is an optional list of context. Supported on server v6.1+.
569
+ def create_index(namespace, set_name, index_name, bin_name, index_type, collection_type = nil, options = nil, ctx: nil)
570
570
  if options.nil? && collection_type.is_a?(Hash)
571
571
  options, collection_type = collection_type, nil
572
572
  end
@@ -575,6 +575,7 @@ module Aerospike
575
575
  str_cmd = "sindex-create:ns=#{namespace}"
576
576
  str_cmd << ";set=#{set_name}" unless set_name.to_s.strip.empty?
577
577
  str_cmd << ";indexname=#{index_name};numbins=1"
578
+ str_cmd << ";context=#{CDT::Context.base64(ctx)}" unless ctx.to_a.empty?
578
579
  str_cmd << ";indextype=#{collection_type.to_s.upcase}" if collection_type
579
580
  str_cmd << ";indexdata=#{bin_name},#{index_type.to_s.upcase}"
580
581
  str_cmd << ";priority=normal"
@@ -729,6 +730,59 @@ module Aerospike
729
730
  query_partitions(Aerospike::PartitionFilter.all, statement, options)
730
731
  end
731
732
 
733
+ #----------------------------------------------------------
734
+ # Query/Execute (Supported by Aerospike 3+ servers only)
735
+ #----------------------------------------------------------
736
+
737
+ # QueryExecute applies operations on records that match the statement filter.
738
+ # Records are not returned to the client.
739
+ # This asynchronous server call will return before the command is complete.
740
+ # The user can optionally wait for command completion by using the returned
741
+ # ExecuteTask instance.
742
+ #
743
+ # This method is only supported by Aerospike 3+ servers.
744
+ # If the policy is nil, the default relevant policy will be used.
745
+ #
746
+ # @param statement [Aerospike::Statement] The query or batch read statement.
747
+ # @param operations [Array<Aerospike::Operation>] An optional list of operations.
748
+ # @param options [Hash] An optional hash of policy options.
749
+ # @return [Aerospike::ExecuteTask] An ExecuteTask instance that can be used to wait for command completion.
750
+ #
751
+ # @raise [Aerospike::Exceptions::Aerospike] if an error occurs during the operation.
752
+ def query_execute(statement, operations = [], options = nil)
753
+ policy = create_policy(options, WritePolicy, default_write_policy)
754
+
755
+ if statement.nil?
756
+ raise Aerospike::Exceptions::Aerospike.new(Aerospike::ResultCode::INVALID_COMMAND, "Query failed of invalid statement.")
757
+ end
758
+
759
+ statement = statement.clone
760
+ unless operations.empty?
761
+ statement.operations = operations
762
+ end
763
+
764
+ task_id = statement.task_id
765
+ nodes = @cluster.nodes
766
+ if nodes.empty?
767
+ raise Aerospike::Exceptions::Aerospike.new(Aerospike::ResultCode::SERVER_NOT_AVAILABLE, "Query failed because cluster is empty.")
768
+ end
769
+
770
+ # Use a thread per node
771
+ nodes.each do |node|
772
+ Thread.new do
773
+ Thread.current.abort_on_exception = true
774
+ begin
775
+ command = ServerCommand.new(@cluster, node, policy, statement, true, task_id)
776
+ execute_command(command)
777
+ rescue => e
778
+ Aerospike.logger.error(e)
779
+ raise e
780
+ end
781
+ end
782
+ end
783
+ ExecuteTask.new(@cluster, statement)
784
+ end
785
+
732
786
  #-------------------------------------------------------
733
787
  # User administration
734
788
  #-------------------------------------------------------
@@ -1,4 +1,4 @@
1
- # Copyright 2014-2020 Aerospike, Inc.
1
+ # Copyright 2014-2024 Aerospike, Inc.
2
2
  #
3
3
  # Portions may be licensed to Aerospike, Inc. under one or more contributor
4
4
  # license agreements.
@@ -465,6 +465,254 @@ module Aerospike
465
465
  end_cmd
466
466
  end
467
467
 
468
+ def set_query(policy, statement, background, node_partitions)
469
+ function_arg_buffer = nil
470
+ field_count = 0
471
+ filter_size = 0
472
+
473
+ begin_cmd
474
+
475
+ if statement.namespace
476
+ @data_offset += statement.namespace.bytesize + FIELD_HEADER_SIZE
477
+ field_count += 1
478
+ end
479
+
480
+ if statement.set_name
481
+ @data_offset += statement.set_name.bytesize + FIELD_HEADER_SIZE
482
+ field_count += 1
483
+ end
484
+
485
+ # Estimate recordsPerSecond field size. This field is used in new servers and not used
486
+ # (but harmless to add) in old servers.
487
+ if statement.records_per_second > 0
488
+ @data_offset += 4 + FIELD_HEADER_SIZE
489
+ field_count += 1
490
+ end
491
+
492
+ # Estimate socket timeout field size. This field is used in new servers and not used
493
+ # (but harmless to add) in old servers.
494
+ @data_offset += 4 + FIELD_HEADER_SIZE
495
+ field_count += 1
496
+
497
+ # Estimate task_id field.
498
+ @data_offset += 8 + FIELD_HEADER_SIZE
499
+ field_count += 1
500
+
501
+ filter = statement.filters[0]
502
+ bin_names = statement.bin_names
503
+ packed_ctx = nil
504
+
505
+ if filter
506
+ col_type = filter.collection_type
507
+
508
+ # Estimate INDEX_TYPE field.
509
+ if col_type > 0
510
+ @data_offset += FIELD_HEADER_SIZE + 1
511
+ field_count += 1
512
+ end
513
+
514
+ # Estimate INDEX_RANGE field.
515
+ @data_offset += FIELD_HEADER_SIZE
516
+ filter_size += 1 # num filters
517
+ filter_size += filter.estimate_size
518
+
519
+ @data_offset += filter_size
520
+ field_count += 1
521
+
522
+ packed_ctx = filter.packed_ctx
523
+ if packed_ctx
524
+ @data_offset += FIELD_HEADER_SIZE + packed_ctx.length
525
+ field_count += 1
526
+ end
527
+ end
528
+
529
+ statement.set_task_id
530
+ predexp = policy.predexp || statement.predexp
531
+
532
+ if predexp
533
+ @data_offset += FIELD_HEADER_SIZE
534
+ pred_size = Aerospike::PredExp.estimate_size(predexp)
535
+ @data_offset += pred_size
536
+ field_count += 1
537
+ end
538
+
539
+ unless policy.filter_exp.nil?
540
+ exp_size = estimate_expression_size(policy.filter_exp)
541
+ field_count += 1 if exp_size > 0
542
+ end
543
+
544
+ # Estimate aggregation/background function size.
545
+ if statement.function_name
546
+ @data_offset += FIELD_HEADER_SIZE + 1 # udf type
547
+ @data_offset += statement.package_name.bytesize + FIELD_HEADER_SIZE
548
+ @data_offset += statement.function_name.bytesize + FIELD_HEADER_SIZE
549
+
550
+ function_arg_buffer = ""
551
+ if statement.function_args && statement.function_args.length > 0
552
+ function_arg_buffer = Value.of(statement.function_args).to_bytes
553
+ end
554
+ @data_offset += FIELD_HEADER_SIZE + function_arg_buffer.bytesize
555
+ field_count += 4
556
+ end
557
+
558
+ max_records = 0
559
+ parts_full_size = 0
560
+ parts_partial_digest_size = 0
561
+ parts_partial_bval_size = 0
562
+
563
+ unless node_partitions.nil?
564
+ parts_full_size = node_partitions.parts_full.length * 2
565
+ parts_partial_digest_size = node_partitions.parts_partial.length * 20
566
+
567
+ unless filter.nil?
568
+ parts_partial_bval_size = node_partitions.parts_partial.length * 8
569
+ end
570
+ max_records = node_partitions.record_max
571
+ end
572
+
573
+ if parts_full_size > 0
574
+ @data_offset += parts_full_size + FIELD_HEADER_SIZE
575
+ field_count += 1
576
+ end
577
+
578
+ if parts_partial_digest_size > 0
579
+ @data_offset += parts_partial_digest_size + FIELD_HEADER_SIZE
580
+ field_count += 1
581
+ end
582
+
583
+ if parts_partial_bval_size > 0
584
+ @data_offset += parts_partial_bval_size + FIELD_HEADER_SIZE
585
+ field_count += 1
586
+ end
587
+
588
+ # Estimate max records field size. This field is used in new servers and not used
589
+ # (but harmless to add) in old servers.
590
+ if max_records > 0
591
+ @data_offset += 8 + FIELD_HEADER_SIZE
592
+ field_count += 1
593
+ end
594
+
595
+ operations = statement.operations
596
+ operation_count = 0
597
+
598
+ if !operations.empty?
599
+
600
+ unless background
601
+ raise Aerospike::Exceptions::Aerospike.new(Aerospike::ResultCode::PARAMETER_ERROR)
602
+ end
603
+
604
+ operations.each do |operation|
605
+ estimate_operation_size_for_operation(operation)
606
+ end
607
+ operation_count = operations.size
608
+ elsif !bin_names.empty?
609
+ bin_names.each do |bin_name|
610
+ estimate_operation_size_for_bin_name(bin_name)
611
+ end
612
+ operation_count = bin_names.length
613
+ # Estimate size for selected bin names (query bin names already handled for old servers).
614
+ end
615
+
616
+ size_buffer
617
+
618
+ if background
619
+ write_header_with_policy(policy, 0, INFO2_WRITE, field_count, operation_count)
620
+ else
621
+ read_attr = INFO1_READ
622
+ read_attr |= INFO1_NOBINDATA unless policy.include_bin_data
623
+ read_attr |= INFO1_SHORT_QUERY if policy.short_query
624
+ write_header(policy, read_attr, 0, field_count, operation_count)
625
+ end
626
+
627
+ write_field_string(statement.namespace, FieldType::NAMESPACE) if statement.namespace
628
+ write_field_string(statement.set_name, FieldType::TABLE) if statement.set_name
629
+
630
+ # Write records per second.
631
+ write_field_int(statement.records_per_second, FieldType::RECORDS_PER_SECOND) if statement.records_per_second > 0
632
+
633
+ write_filter_exp(policy.filter_exp, exp_size)
634
+
635
+ # Write socket idle timeout.
636
+ write_field_int(policy.socket_timeout, FieldType::SOCKET_TIMEOUT)
637
+
638
+ # Write task_id field
639
+ write_field_int64(statement.task_id, FieldType::TRAN_ID)
640
+
641
+ unless predexp.nil?
642
+ write_field_header(pred_size, Aerospike::FieldType::PREDEXP)
643
+ @data_offset = Aerospike::PredExp.write(
644
+ predexp, @data_buffer, @data_offset
645
+ )
646
+ end
647
+
648
+ if filter
649
+ type = filter.collection_type
650
+
651
+ if type > 0
652
+ write_field_header(1, FieldType::INDEX_TYPE)
653
+ @data_offset += @data_buffer.write_byte(type, @data_offset)
654
+ end
655
+
656
+ write_field_header(filter_size, FieldType::INDEX_RANGE)
657
+ @data_offset += @data_buffer.write_byte(1, @data_offset)
658
+ @data_offset = filter.write(@data_buffer, @data_offset)
659
+
660
+ if packed_ctx
661
+ write_field_header(packed_ctx.length, FieldType::INDEX_CONTEXT)
662
+ @data_offset += @data_buffer.write_binary(packed_ctx, @data_offset)
663
+ end
664
+ end
665
+
666
+ if statement.function_name
667
+ write_field_header(1, FieldType::UDF_OP)
668
+ @data_offset += @data_buffer.write_byte(1, @data_offset)
669
+ write_field_string(statement.package_name, FieldType::UDF_PACKAGE_NAME)
670
+ write_field_string(statement.function_name, FieldType::UDF_FUNCTION)
671
+ write_field_string(function_arg_buffer, FieldType::UDF_ARGLIST)
672
+ end
673
+
674
+ if parts_full_size > 0
675
+ write_field_header(parts_full_size, FieldType::PID_ARRAY)
676
+ node_partitions.parts_full.each do |part|
677
+ @data_offset += @data_buffer.write_uint16_little_endian(part.id, @data_offset)
678
+ end
679
+ end
680
+
681
+ if parts_partial_digest_size > 0
682
+ write_field_header(parts_partial_digest_size, FieldType::DIGEST_ARRAY)
683
+ node_partitions.parts_partial.each do |part|
684
+ @data_offset += @data_buffer.write_binary(part.digest, @data_offset)
685
+ end
686
+ end
687
+
688
+ if parts_partial_bval_size > 0
689
+ write_field_header(parts_partial_bval_size, FieldType::BVAL_ARRAY)
690
+ @node_partitions.parts_partial.each do |part|
691
+ @data_offset += @data_buffer.write_uint64_little_endian(part.bval, @data_offset)
692
+ end
693
+ end
694
+
695
+ if max_records > 0
696
+ write_field(max_records, FieldType::MAX_RECORDS)
697
+ end
698
+
699
+ if operations.empty?
700
+ if bin_names.empty?
701
+ bin_names.each do |bin_name|
702
+ write_operation_for_bin_name(bin_name, Operation::READ)
703
+ end
704
+ end
705
+ else
706
+ operations.each do |operation|
707
+ write_operation_for_operation(operation)
708
+ end
709
+ end
710
+
711
+ end_cmd
712
+
713
+ nil
714
+ end
715
+
468
716
  def execute
469
717
  iterations = 0
470
718
 
@@ -537,7 +785,7 @@ module Aerospike
537
785
  parse_result
538
786
  rescue => e
539
787
  case e
540
- # do not log the following exceptions
788
+ # do not log the following exceptions
541
789
  when Aerospike::Exceptions::ScanTerminated
542
790
  when Aerospike::Exceptions::QueryTerminated
543
791
  else
@@ -703,9 +951,8 @@ module Aerospike
703
951
  read_attr |= INFO1_CONSISTENCY_ALL if policy.consistency_level == Aerospike::ConsistencyLevel::CONSISTENCY_ALL
704
952
  write_attr |= INFO2_DURABLE_DELETE if policy.durable_delete
705
953
  read_attr |= INFO1_COMPRESS_RESPONSE if policy.use_compression
706
-
707
954
  # Write all header data except total size which must be written last.
708
- @data_buffer.write_byte(MSG_REMAINING_HEADER_SIZE, 8) # Message heade.length.
955
+ @data_buffer.write_byte(MSG_REMAINING_HEADER_SIZE, 8) # Message header.length.
709
956
  @data_buffer.write_byte(read_attr, 9)
710
957
  @data_buffer.write_byte(write_attr, 10)
711
958
  @data_buffer.write_byte(info_attr, 11)
@@ -22,7 +22,7 @@ module Aerospike
22
22
  # Container object for client policy command.
23
23
  class Policy
24
24
  attr_accessor :filter_exp, :priority, :timeout, :max_retries, :sleep_between_retries, :consistency_level,
25
- :predexp, :fail_on_filtered_out, :replica, :use_compression
25
+ :predexp, :fail_on_filtered_out, :replica, :use_compression, :socket_timeout
26
26
 
27
27
  alias total_timeout timeout
28
28
  alias total_timeout= timeout=
@@ -133,6 +133,16 @@ module Aerospike
133
133
  # Duration to sleep between retries if a transaction fails and the
134
134
  # timeout was not exceeded. Enter zero to skip sleep.
135
135
  @sleep_between_retries = opt[:sleep_between_retries] || 0.5
136
+
137
+ # Determines network timeout for each attempt.
138
+ #
139
+ # If socket_timeout is not zero and socket_timeout is reached before an attempt completes,
140
+ # the Timeout above is checked. If Timeout is not exceeded, the transaction
141
+ # is retried. If both socket_timeout and Timeout are non-zero, socket_timeout must be less
142
+ # than or equal to Timeout, otherwise Timeout will also be used for socket_timeout.
143
+ #
144
+ # Default: 30s
145
+ @socket_timeout = opt[:socket_timeout] || 30000
136
146
  end
137
147
  end # class
138
148
  end # module
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
- # Copyright 2014-2020 Aerospike, Inc.
2
+ # Copyright 2014-2023 Aerospike, Inc.
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
5
5
  # you may not use this file except in compliance with the License.
@@ -24,8 +24,8 @@ module Aerospike
24
24
  class WritePolicy < Policy
25
25
 
26
26
  attr_accessor :record_exists_action, :generation_policy,
27
- :generation, :ttl, :send_key, :commit_level,
28
- :durable_delete
27
+ :generation, :ttl, :send_key, :commit_level,
28
+ :durable_delete
29
29
 
30
30
  alias expiration ttl
31
31
  alias expiration= ttl=
@@ -74,6 +74,11 @@ module Aerospike
74
74
  # Valid for Aerospike Server Enterprise Edition 3.10+ only.
75
75
  @durable_delete = opt.fetch(:durable_delete, false)
76
76
 
77
+ # Transaction timeout.
78
+ # This timeout is used to set the socket timeout and is also sent to the
79
+ # server along with the transaction in the wire protocol.
80
+ # Default for write policy is 1.
81
+ @timeout = opt[:timeout] || 1
77
82
  self
78
83
  end
79
84
 
@@ -15,39 +15,51 @@
15
15
  # the License.
16
16
 
17
17
  module Aerospike
18
-
19
18
  class Filter
19
+ attr_reader :packed_ctx
20
20
 
21
- def self.Equal(bin_name, value)
22
- Filter.new(bin_name, value, value)
23
- end
21
+ # open up the class to alias the class methods for naming consistency
22
+ class << self
23
+ def equal(bin_name, value, ctx: nil)
24
+ Filter.new(bin_name, value, value, nil, nil, ctx)
25
+ end
24
26
 
25
- def self.Contains(bin_name, value, col_type)
26
- Filter.new(bin_name, value, value, nil, col_type)
27
- end
27
+ def contains(bin_name, value, col_type, ctx: nil)
28
+ Filter.new(bin_name, value, value, nil, col_type, ctx)
29
+ end
28
30
 
29
- def self.Range(bin_name, from, to, col_type = nil)
30
- Filter.new(bin_name, from, to, nil, col_type)
31
- end
31
+ def range(bin_name, from, to, col_type = nil, ctx: nil)
32
+ Filter.new(bin_name, from, to, nil, col_type, ctx)
33
+ end
32
34
 
33
- def self.geoWithinGeoJSONRegion(bin_name, region, col_type = nil)
34
- region = region.to_json
35
- Filter.new(bin_name, region, region, ParticleType::GEOJSON, col_type)
36
- end
35
+ def geo_within_geo_region(bin_name, region, col_type = nil, ctx: nil)
36
+ region = region.to_json
37
+ Filter.new(bin_name, region, region, ParticleType::GEOJSON, col_type, ctx)
38
+ end
37
39
 
38
- def self.geoWithinRadius(bin_name, lon, lat, radius_meter, col_type = nil)
39
- region = GeoJSON.new({type: "AeroCircle", coordinates: [[lon, lat], radius_meter]})
40
- geoWithinGeoJSONRegion(bin_name, region, col_type)
41
- end
40
+ def geo_within_radius(bin_name, lon, lat, radius_meter, col_type = nil, ctx: nil)
41
+ region = GeoJSON.new({ type: "AeroCircle", coordinates: [[lon, lat], radius_meter] })
42
+ geo_within_geo_region(bin_name, region, col_type, ctx: ctx)
43
+ end
42
44
 
43
- def self.geoContainsGeoJSONPoint(bin_name, point, col_type = nil)
44
- point = point.to_json
45
- Filter.new(bin_name, point, point, ParticleType::GEOJSON, col_type)
46
- end
45
+ def geo_contains_geo_point(bin_name, point, col_type = nil, ctx: nil)
46
+ point = point.to_json
47
+ Filter.new(bin_name, point, point, ParticleType::GEOJSON, col_type, ctx)
48
+ end
47
49
 
48
- def self.geoContainsPoint(bin_name, lon, lat, col_type = nil)
49
- point = GeoJSON.new({type: "Point", coordinates: [lon, lat]})
50
- geoContainsGeoJSONPoint(bin_name, point, col_type)
50
+ def geo_contains_point(bin_name, lon, lat, col_type = nil, ctx: nil)
51
+ point = GeoJSON.new({ type: "Point", coordinates: [lon, lat] })
52
+ geo_contains_geo_point(bin_name, point, col_type, ctx: ctx)
53
+ end
54
+
55
+ # alias the old names for compatibility
56
+ alias :Equal :equal
57
+ alias :Contains :contains
58
+ alias :Range :range
59
+ alias :geoWithinGeoJSONRegion :geo_within_geo_region
60
+ alias :geoWithinRadius :geo_within_radius
61
+ alias :geoContainsGeoJSONPoint :geo_contains_geo_point
62
+ alias :geoContainsPoint :geo_contains_point
51
63
  end
52
64
 
53
65
  def estimate_size
@@ -56,21 +68,21 @@ module Aerospike
56
68
 
57
69
  def write(buf, offset)
58
70
  # Write name.
59
- len = buf.write_binary(@name, offset+1)
71
+ len = buf.write_binary(@name, offset + 1)
60
72
  buf.write_byte(len, offset)
61
73
  offset += len + 1
62
74
 
63
75
  # Write particle type.
64
76
  buf.write_byte(@val_type, offset)
65
- offset+=1
77
+ offset += 1
66
78
 
67
79
  # Write filter begin.
68
- len = @begin.write(buf, offset+4)
80
+ len = @begin.write(buf, offset + 4)
69
81
  buf.write_int32(len, offset)
70
82
  offset += len + 4
71
83
 
72
84
  # Write filter end.
73
- len = @end.write(buf, offset+4)
85
+ len = @end.write(buf, offset + 4)
74
86
  buf.write_int32(len, offset)
75
87
  offset += len + 4
76
88
 
@@ -98,7 +110,7 @@ module Aerospike
98
110
 
99
111
  private
100
112
 
101
- def initialize(bin_name, begin_value, end_value, val_type = nil, col_type = nil)
113
+ def initialize(bin_name, begin_value, end_value, val_type = nil, col_type = nil, ctx = nil)
102
114
  @name = bin_name
103
115
  @begin = Aerospike::Value.of(begin_value)
104
116
  @end = Aerospike::Value.of(end_value)
@@ -107,8 +119,8 @@ module Aerospike
107
119
  # but in certain cases caller can override the type.
108
120
  @val_type = val_type || @begin.type
109
121
  @col_type = col_type
110
- end
111
122
 
123
+ @packed_ctx = CDT::Context.bytes(ctx)
124
+ end
112
125
  end # class
113
-
114
126
  end
@@ -82,12 +82,11 @@ module Aerospike
82
82
  @data_offset += filter_size
83
83
  field_count += 1
84
84
 
85
- # TODO: Implement
86
- # packed_ctx = filter.packed_ctx
87
- # if packed_ctx
88
- # @data_offset += FIELD_HEADER_SIZE + packed_ctx.length
89
- # field_count+=1
90
- # end
85
+ packed_ctx = filter.packed_ctx
86
+ if packed_ctx
87
+ @data_offset += FIELD_HEADER_SIZE + packed_ctx.length
88
+ field_count += 1
89
+ end
91
90
  end
92
91
 
93
92
  @statement.set_task_id
@@ -210,11 +209,10 @@ module Aerospike
210
209
  @data_offset += @data_buffer.write_byte(1, @data_offset)
211
210
  @data_offset = filter.write(@data_buffer, @data_offset)
212
211
 
213
- # TODO: Implement
214
- # if packed_ctx
215
- # write_field_header(packed_ctx.length, FieldType::INDEX_CONTEXT)
216
- # @data_buffer.write_binary(packed_ctx, @data_offset)
217
- # end
212
+ if packed_ctx
213
+ write_field_header(packed_ctx.length, FieldType::INDEX_CONTEXT)
214
+ @data_offset += @data_buffer.write_binary(packed_ctx, @data_offset)
215
+ end
218
216
  end
219
217
 
220
218
  if @statement.function_name
@@ -0,0 +1,60 @@
1
+ # encoding: utf-8
2
+ # Copyright 2014-2023 Aerospike, Inc.
3
+ #
4
+ # Portions may be licensed to Aerospike, Inc. under one or more contributor
5
+ # license agreements.
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may not
8
+ # use this file except in compliance with the License. You may obtain a copy of
9
+ # the License at http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+ # License for the specific language governing permissions and limitations under
15
+ # the License.
16
+
17
+
18
+ module Aerospike
19
+ class ServerCommand < MultiCommand
20
+ attr_accessor :statement, :task_id, :cluster, :write_policy, :background
21
+
22
+ def initialize(cluster, node, policy, statement, background, task_id)
23
+ super(node)
24
+ @statement = statement
25
+ @task_id = task_id
26
+ @cluster = cluster
27
+ @policy = policy
28
+ @background = background
29
+ end
30
+
31
+ def write?
32
+ true
33
+ end
34
+
35
+ def write_buffer
36
+ set_query(@policy, @statement, background, nil)
37
+ end
38
+
39
+ def parse_row
40
+ field_count = @data_buffer.read_int16(18)
41
+ result_code = @data_buffer.read(5).ord & 0xFF
42
+ skip_key(field_count)
43
+
44
+ if result_code != 0
45
+ if result_code == Aerospike::ResultCode::KEY_NOT_FOUND_ERROR
46
+ return false
47
+ end
48
+ raise Aerospike::Exceptions::Aerospike.new(result_code)
49
+ end
50
+ op_count = @data_buffer.read_int16(20)
51
+ if op_count <= 0
52
+ return Record.new(@node, key, bins, generation, expiration)
53
+ end
54
+
55
+ unless valid?
56
+ raise Aerospike::Exceptions::QueryTerminated
57
+ end
58
+ end
59
+ end
60
+ end
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
- # Copyright 2014-2020 Aerospike, Inc.
2
+ # Copyright 2014-2023 Aerospike, Inc.
3
3
  #
4
4
  # Portions may be licensed to Aerospike, Inc. under one or more contributor
5
5
  # license agreements.
@@ -16,11 +16,14 @@
16
16
 
17
17
  module Aerospike
18
18
 
19
+ # The Aerospike::Statement class represents a query or scan statement to be executed on the database.
20
+ # It provides a set of properties that define the query or scan, including namespace, set name, bin names,
21
+ # index name, filters, and operations.
19
22
  class Statement
20
23
 
21
24
  attr_accessor :namespace, :set_name, :index_name, :bin_names, :task_id
22
- attr_accessor :filters, :package_name, :function_name, :function_args
23
- attr_accessor :predexp, :return_data
25
+ attr_accessor :filters, :package_name, :function_name, :function_args, :operations
26
+ attr_accessor :predexp, :return_data, :records_per_second
24
27
 
25
28
  def initialize(namespace, set_name, bin_names=[])
26
29
  # Namespace determines query Namespace
@@ -56,6 +59,14 @@ module Aerospike
56
59
  @package_name = nil
57
60
  @function_name = nil
58
61
  @function_args = nil
62
+ @operations = nil
63
+
64
+
65
+ # Limit returned records per second (rps) rate for each server.
66
+ # Will not apply rps limit if records_per_second is zero.
67
+ # Currently only applicable to a query without a defined filter (scan).
68
+ # Default is 0
69
+ @records_per_second = 0
59
70
 
60
71
  # TaskId determines query task id. (Optional)
61
72
  @task_id = rand(RAND_MAX)
@@ -64,33 +75,35 @@ module Aerospike
64
75
  @return_data = true
65
76
  end
66
77
 
67
- def set_aggregate_function(package_name, function_name, function_args=[], return_data=true)
78
+ def set_aggregate_function(package_name, function_name, function_args=[], return_data=true)
68
79
  @package_name = package_name
69
80
  @function_name = function_name
70
81
  @function_args = function_args
71
82
  @return_data = return_data
72
- end
83
+ end
73
84
 
74
- def is_scan?
75
- return (filters.nil? || (filters.empty?))
76
- end
85
+ def is_scan?
86
+ return (filters.nil? || (filters.empty?))
87
+ end
77
88
 
78
- def set_task_id
79
- while @task_id == 0
80
- @task_id = rand(RAND_MAX)
89
+ def set_task_id
90
+ while @task_id == 0
91
+ @task_id = rand(RAND_MAX)
92
+ end
81
93
  end
82
- end
83
94
 
84
- def reset_task_id
85
- @task_id = rand(RAND_MAX)
86
- while @task_id == 0
95
+ def reset_task_id
87
96
  @task_id = rand(RAND_MAX)
97
+ while @task_id == 0
98
+ @task_id = rand(RAND_MAX)
99
+ end
88
100
  end
89
- end
90
101
 
91
- private
92
102
 
93
- RAND_MAX = 2**63
103
+
104
+ private
105
+
106
+ RAND_MAX = 2**63 - 1
94
107
 
95
108
  end # class
96
109
  end
@@ -1,4 +1,4 @@
1
- # Copyright 2014-2020 Aerospike, Inc.
1
+ # Copyright 2014-2023 Aerospike, Inc.
2
2
  #
3
3
  # Portions may be licensed to Aerospike, Inc. under one or more contributor
4
4
  # license agreements.
@@ -33,6 +33,8 @@ module Aerospike
33
33
  @node = node
34
34
  end
35
35
 
36
+
37
+
36
38
  def to_s
37
39
  "key: `#{key}` bins: `#{bins}` generation: `#{generation}`, ttl: `#{ttl}`"
38
40
  end
@@ -41,6 +43,18 @@ module Aerospike
41
43
 
42
44
  CITRUSLEAF_EPOCH = 1262304000
43
45
 
46
+ # Arguments:
47
+ # value: the key to retrieve the value for
48
+ #
49
+ # Returns:
50
+ # the value of the specified key, or `nil` if `@bins` is `nil`
51
+ def get_value(value)
52
+ unless @bins.nil?
53
+ return @bins[value]
54
+ end
55
+ nil
56
+ end
57
+
44
58
  # Converts an absolute expiration time (in seconds from citrusleaf epoch)
45
59
  # to relative time-to-live (TTL) in seconds
46
60
  def expiration_to_ttl(secs_from_epoc)
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module Aerospike
3
- VERSION = "2.25.0"
3
+ VERSION = "2.27.0"
4
4
  end
data/lib/aerospike.rb CHANGED
@@ -169,6 +169,7 @@ require "aerospike/query/scan_executor"
169
169
  require "aerospike/query/scan_partition_command"
170
170
  require "aerospike/query/query_executor"
171
171
  require "aerospike/query/query_partition_command"
172
+ require "aerospike/query/server_command"
172
173
 
173
174
  require "aerospike/exp/exp"
174
175
  require "aerospike/exp/exp_map"
metadata CHANGED
@@ -1,15 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aerospike
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.25.0
4
+ version: 2.27.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Khosrow Afroozeh
8
8
  - Jan Hecking
9
- autorequire:
9
+ - Sachin Venkatesha Murthy
10
+ autorequire:
10
11
  bindir: bin
11
12
  cert_chain: []
12
- date: 2022-11-28 00:00:00.000000000 Z
13
+ date: 2023-05-18 00:00:00.000000000 Z
13
14
  dependencies:
14
15
  - !ruby/object:Gem::Dependency
15
16
  name: msgpack
@@ -44,6 +45,7 @@ description: Official Aerospike Client for ruby. Access your Aerospike cluster w
44
45
  email:
45
46
  - khosrow@aerospike.com
46
47
  - jhecking@aerospike.com
48
+ - smurthy@aerospike.com
47
49
  executables: []
48
50
  extensions: []
49
51
  extra_rdoc_files: []
@@ -181,6 +183,7 @@ files:
181
183
  - lib/aerospike/query/scan_command.rb
182
184
  - lib/aerospike/query/scan_executor.rb
183
185
  - lib/aerospike/query/scan_partition_command.rb
186
+ - lib/aerospike/query/server_command.rb
184
187
  - lib/aerospike/query/statement.rb
185
188
  - lib/aerospike/query/stream_command.rb
186
189
  - lib/aerospike/record.rb
@@ -227,8 +230,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
227
230
  - !ruby/object:Gem::Version
228
231
  version: '0'
229
232
  requirements: []
230
- rubygems_version: 3.3.5
231
- signing_key:
233
+ rubygems_version: 3.3.3
234
+ signing_key:
232
235
  specification_version: 4
233
236
  summary: An Aerospike driver for Ruby.
234
237
  test_files: []