aerospike 2.22.0 → 2.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -1
  3. data/lib/aerospike/atomic/atomic.rb +1 -1
  4. data/lib/aerospike/cdt/context.rb +7 -7
  5. data/lib/aerospike/cdt/list_return_type.rb +4 -0
  6. data/lib/aerospike/cdt/map_operation.rb +6 -6
  7. data/lib/aerospike/cdt/map_return_type.rb +4 -0
  8. data/lib/aerospike/client.rb +34 -59
  9. data/lib/aerospike/command/admin_command.rb +1 -1
  10. data/lib/aerospike/command/batch_index_node.rb +1 -1
  11. data/lib/aerospike/command/batch_item.rb +1 -1
  12. data/lib/aerospike/command/command.rb +62 -24
  13. data/lib/aerospike/command/field_type.rb +3 -0
  14. data/lib/aerospike/command/login_command.rb +4 -4
  15. data/lib/aerospike/command/multi_command.rb +8 -2
  16. data/lib/aerospike/command/read_command.rb +2 -2
  17. data/lib/aerospike/connection/authenticate.rb +3 -3
  18. data/lib/aerospike/features.rb +9 -9
  19. data/lib/aerospike/host/parse.rb +2 -2
  20. data/lib/aerospike/key.rb +10 -1
  21. data/lib/aerospike/node/refresh/info.rb +1 -1
  22. data/lib/aerospike/node/verify/name.rb +1 -1
  23. data/lib/aerospike/node/verify/partition_generation.rb +1 -1
  24. data/lib/aerospike/node/verify/peers_generation.rb +1 -1
  25. data/lib/aerospike/node/verify/rebalance_generation.rb +1 -1
  26. data/lib/aerospike/policy/policy.rb +4 -1
  27. data/lib/aerospike/policy/scan_policy.rb +20 -1
  28. data/lib/aerospike/privilege.rb +1 -1
  29. data/lib/aerospike/query/node_partitions.rb +39 -0
  30. data/lib/aerospike/query/partition_filter.rb +66 -0
  31. data/lib/aerospike/query/partition_status.rb +36 -0
  32. data/lib/aerospike/query/partition_tracker.rb +347 -0
  33. data/lib/aerospike/query/scan_command.rb +3 -3
  34. data/lib/aerospike/query/scan_executor.rb +69 -0
  35. data/lib/aerospike/query/scan_partition_command.rb +49 -0
  36. data/lib/aerospike/query/statement.rb +1 -1
  37. data/lib/aerospike/query/stream_command.rb +14 -1
  38. data/lib/aerospike/result_code.rb +79 -4
  39. data/lib/aerospike/role.rb +2 -2
  40. data/lib/aerospike/task/execute_task.rb +2 -2
  41. data/lib/aerospike/task/index_task.rb +1 -1
  42. data/lib/aerospike/user_role.rb +1 -1
  43. data/lib/aerospike/utils/buffer.rb +6 -0
  44. data/lib/aerospike/utils/pool.rb +1 -1
  45. data/lib/aerospike/value/value.rb +6 -6
  46. data/lib/aerospike/version.rb +1 -1
  47. data/lib/aerospike.rb +6 -0
  48. metadata +11 -5
@@ -23,10 +23,10 @@ module Aerospike
23
23
  module Features
24
24
 
25
25
  # Server supports List Complex Data Type (CDT)
26
- CDT_LIST = :"cdt-list"
26
+ CDT_LIST = :'cdt-list'
27
27
 
28
28
  # Server supports Map Complex Data Type (CDT)
29
- CDT_MAP = :"cdt-map"
29
+ CDT_MAP = :'cdt-map'
30
30
 
31
31
  # Server supports Float data type
32
32
  FLOAT = :float
@@ -34,16 +34,16 @@ module Aerospike
34
34
  # Server supports geo-spatial data type and indexing
35
35
  GEO = :geo
36
36
 
37
- # Server requires "lut=now" in truncate command (AER-5955)
38
- LUT_NOW = :"lut-now"
37
+ # Server requires 'lut=now' in truncate command (AER-5955)
38
+ LUT_NOW = :'lut-now'
39
39
 
40
- # Server supports the new "peers" protocol for automatic node discovery
40
+ # Server supports the new 'peers' protocol for automatic node discovery
41
41
  PEERS = :peers
42
42
 
43
- # Server supports the "truncate-namespace" command
44
- TRUNCATE_NAMESPACE = :"truncate-namespace"
43
+ # Server supports the 'truncate-namespace' command
44
+ TRUNCATE_NAMESPACE = :'truncate-namespace'
45
45
 
46
- # Server supports the "blob-bits" command
47
- BLOB_BITS = :"blob-bits"
46
+ # Server supports the 'blob-bits' command
47
+ BLOB_BITS = :'blob-bits'
48
48
  end
49
49
  end
@@ -20,7 +20,7 @@
20
20
  module Aerospike
21
21
  class Host
22
22
  module Parse
23
- INTEGER_REGEX = /\A\d+\z/
23
+ INTEGER_REGEX = /\A\d+\z/.freeze
24
24
 
25
25
  class << self
26
26
  ##
@@ -73,4 +73,4 @@ module Aerospike
73
73
  end
74
74
  end
75
75
  end
76
- end
76
+ end
data/lib/aerospike/key.rb CHANGED
@@ -53,15 +53,20 @@ module Aerospike
53
53
 
54
54
  attr_reader :namespace, :set_name, :digest
55
55
 
56
- def initialize(ns, set, val, digest=nil, v1_compatible: self.class.v1_compatible?)
56
+ def initialize(ns, set, val, digest=nil, bval: nil, v1_compatible: self.class.v1_compatible?)
57
57
  @namespace = ns
58
58
  @set_name = set
59
59
  @user_key = Value.of(val)
60
60
  check_key!(@namespace, @set_name, @user_key, !digest.nil?)
61
61
  @digest = digest || compute_digest(v1_compatible)
62
+ @bval = bval
62
63
  end
63
64
 
64
65
 
66
+ def bval
67
+ @bval
68
+ end
69
+
65
70
  def to_s
66
71
  "#{@namespace}:#{@set_name}:#{@user_key}:#{@digest.nil? ? '' : @digest.bytes}"
67
72
  end
@@ -85,6 +90,10 @@ module Aerospike
85
90
  @digest.hash
86
91
  end
87
92
 
93
+ def partition_id
94
+ (@digest[0..3].unpack(Partition::UNPACK_FORMAT)[0] & 0xFFFF) % Node::PARTITIONS
95
+ end
96
+
88
97
  private
89
98
 
90
99
  def valid_key?(value, has_digest)
@@ -30,7 +30,7 @@ module Aerospike
30
30
  def call(node, peers)
31
31
  conn = node.tend_connection
32
32
  if peers.use_peers?
33
- cmds = node.cluster.rack_aware ? CMDS_REBALANCE : CMDS_PEERS
33
+ cmds = node.cluster.rack_aware ? CMDS_REBALANCE : CMDS_PEERS
34
34
  info_map = ::Aerospike::Info.request(conn, *cmds)
35
35
  Verify::PeersGeneration.(node, info_map, peers)
36
36
  Verify::PartitionGeneration.(node, info_map)
@@ -40,4 +40,4 @@ module Aerospike
40
40
  end
41
41
  end
42
42
  end
43
- end
43
+ end
@@ -40,4 +40,4 @@ module Aerospike
40
40
  end
41
41
  end
42
42
  end
43
- end
43
+ end
@@ -38,4 +38,4 @@ module Aerospike
38
38
  end
39
39
  end
40
40
  end
41
- end
41
+ end
@@ -40,4 +40,4 @@ module Aerospike
40
40
  end
41
41
  end
42
42
  end
43
- end
43
+ end
@@ -26,6 +26,9 @@ module Aerospike
26
26
  attr_accessor :priority, :timeout, :max_retries, :sleep_between_retries, :consistency_level,
27
27
  :predexp, :fail_on_filtered_out, :replica, :use_compression
28
28
 
29
+ alias total_timeout timeout
30
+ alias total_timeout= timeout=
31
+
29
32
  def initialize(opt={})
30
33
  # Container object for transaction policy attributes used in all database
31
34
  # operation calls.
@@ -85,7 +88,7 @@ module Aerospike
85
88
 
86
89
 
87
90
  # Send read commands to the node containing the key's partition replica type.
88
- # Write commands are not affected by this setting, because all writes are directed
91
+ # Write commands are not affected by this setting, because all writes are directed
89
92
  # to the node containing the key's master partition.
90
93
  #
91
94
  # Default to sending read commands to the node containing the key's master partition.
@@ -22,6 +22,7 @@ module Aerospike
22
22
  # Container object for scan policy command.
23
23
  class ScanPolicy < Policy
24
24
 
25
+ attr_accessor :max_records
25
26
  attr_accessor :scan_percent
26
27
  attr_accessor :concurrent_nodes
27
28
  attr_accessor :include_bin_data
@@ -35,6 +36,16 @@ module Aerospike
35
36
 
36
37
  @max_retries = 0
37
38
 
39
+ # Approximates the number of records to return to the client. This number is divided by the
40
+ # number of nodes involved in the query. The actual number of records returned
41
+ # may be less than MaxRecords if node record counts are small and unbalanced across
42
+ # nodes.
43
+ #
44
+ # This field is supported on server versions >= 4.9.
45
+ #
46
+ # Default: 0 (do not limit record count)
47
+ @max_records = opt.fetch(:max_records) { 0 }
48
+
38
49
  # Percent of data to scan. Valid integer range is 1 to 100.
39
50
  # Default is 100.
40
51
  @scan_percent = opt[:scan_percent] || 100
@@ -51,7 +62,15 @@ module Aerospike
51
62
  # Default is true.
52
63
  @fail_on_cluster_change = opt.fetch(:fail_on_cluster_change) { true }
53
64
 
54
- @socket_timeout = opt[:socket_timeout] || 10000
65
+ # Determines network timeout for each attempt.
66
+ #
67
+ # If socket_timeout is not zero and socket_timeout is reached before an attempt completes,
68
+ # the Timeout above is checked. If Timeout is not exceeded, the transaction
69
+ # is retried. If both socket_timeout and Timeout are non-zero, socket_timeout must be less
70
+ # than or equal to Timeout, otherwise Timeout will also be used for socket_timeout.
71
+ #
72
+ # Default: 30s
73
+ @socket_timeout = opt[:socket_timeout] || 30000
55
74
 
56
75
  # Number of records to place in queue before blocking. Records received
57
76
  # from multiple server nodes will be placed in a queue. A separate thread
@@ -130,4 +130,4 @@ module Aerospike
130
130
 
131
131
  end # class
132
132
 
133
- end
133
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2014-2020 Aerospike, Inc.
4
+ #
5
+ # Portions may be licensed to Aerospike, Inc. under one or more contributor
6
+ # license agreements.
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may not
9
+ # use this file except in compliance with the License. You may obtain a copy of
10
+ # the License at http:#www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15
+ # License for the specific language governing permissions and limitations under
16
+ # the License.
17
+
18
+ module Aerospike
19
+ class NodePartitions
20
+ attr_accessor :node, :parts_full, :parts_partial, :record_count, :record_max, :parts_unavailable
21
+
22
+ def initialize(node)
23
+ @node= node
24
+ @parts_full= []
25
+ @parts_partial= []
26
+ @record_count= 0
27
+ @parts_unavailable= 0
28
+ @record_max= 0
29
+ end
30
+
31
+ def to_s
32
+ "Node #{@node.inspect}: full: #{@parts_full.length}, partial: #{@parts_partial.length}"
33
+ end
34
+
35
+ def add_partition(partition_status)
36
+ partition_status.digest.nil? ? @parts_full << partition_status : @parts_partial << partition_status
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2014-2020 Aerospike, Inc.
4
+ #
5
+ # Portions may be licensed to Aerospike, Inc. under one or more contributor
6
+ # license agreements.
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may not
9
+ # use this file except in compliance with the License. You may obtain a copy of
10
+ # the License at http:#www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15
+ # License for the specific language governing permissions and limitations under
16
+ # the License.
17
+
18
+ module Aerospike
19
+ class PartitionFilter
20
+ attr_reader :partition_begin, :count, :digest
21
+ attr_accessor :partitions, :done
22
+
23
+ alias done? done
24
+
25
+ # Creates a partition filter that
26
+ # reads all the partitions.
27
+ def self.all
28
+ PartitionFilter.new(0, Aerospike::Node::PARTITIONS)
29
+ end
30
+
31
+ # Creates a partition filter by partition id.
32
+ # Partition id is between 0 - 4095
33
+ def self.by_id(partition_id)
34
+ PartitionFilter.new(partition_id, 1)
35
+ end
36
+
37
+ # Creates a partition filter by partition range.
38
+ # begin partition id is between 0 - 4095
39
+ # count is the number of partitions, in the range of 1 - 4096 inclusive.
40
+ def self.by_range(partition_begin, count)
41
+ PartitionFilter.new(partition_begin, count)
42
+ end
43
+
44
+ # Creates a partition filter that will return
45
+ # records after key's digest in the partition containing the digest.
46
+ # Note that digest order is not the same as userKey order. This method
47
+ # only works for scan or query with nil filter.
48
+ def self.by_key(key)
49
+ PartitionFilter.new(key.partition_id, 1, key.digest)
50
+ end
51
+
52
+ def to_s
53
+ "PartitionFilter<begin: #{@partition_begin}, count: #{@count}, digest: #{@digest}, done: #{@done}>"
54
+ end
55
+
56
+ private
57
+
58
+ def initialize(partition_begin, count, digest = nil, partitions = nil, done = false)
59
+ @partition_begin = partition_begin
60
+ @count = count
61
+ @digest = digest
62
+ @partitions = partitions
63
+ @done = done
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2014-2020 Aerospike, Inc.
4
+ #
5
+ # Portions may be licensed to Aerospike, Inc. under one or more contributor
6
+ # license agreements.
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may not
9
+ # use this file except in compliance with the License. You may obtain a copy of
10
+ # the License at http:#www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15
+ # License for the specific language governing permissions and limitations under
16
+ # the License.
17
+
18
+ module Aerospike
19
+
20
+ class PartitionStatus #:nodoc:
21
+ attr_accessor :bval, :id, :digest, :retry
22
+
23
+ def initialize(id)
24
+ @id = id
25
+ @retry = true
26
+ end
27
+
28
+ #
29
+ # Show the PartitionStatus as String.
30
+ #
31
+ def to_s
32
+ "PartitionStatus<bval: #{@bval}, id: #{@id}, retry: #{@retry}, digest: #{@digest}>"
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,347 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2014-2020 Aerospike, Inc.
4
+ #
5
+ # Portions may be licensed to Aerospike, Inc. under one or more contributor
6
+ # license agreements.
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may not
9
+ # use this file except in compliance with the License. You may obtain a copy of
10
+ # the License at http:#www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15
+ # License for the specific language governing permissions and limitations under
16
+ # the License.
17
+
18
+ module Aerospike
19
+ class PartitionTracker
20
+ attr_reader :partitions, :partitions_capacity, :partition_begin, :node_capacity,
21
+ :node_filter, :partition_filter, :node_partitions_list, :max_records,
22
+ :sleep_between_retries, :socket_timeout, :total_timeout, :iteration, :deadline
23
+
24
+ def initialize(policy, nodes, partition_filter = nil)
25
+ if partition_filter.nil?
26
+ return init_for_node(policy, nodes[0]) if nodes.length == 1
27
+ return init_for_nodes(policy, nodes)
28
+ end
29
+
30
+ # Validate here instead of initial PartitionFilter constructor because total number of
31
+ # cluster partitions may change on the server and PartitionFilter will never have access
32
+ # to Cluster instance. Use fixed number of partitions for now.
33
+ unless partition_filter.partition_begin.between?(0, Node::PARTITIONS - 1)
34
+ raise Aerospike::Exceptions::Aerospike.new(
35
+ Aerospike::ResultCode::PARAMETER_ERROR,
36
+ "Invalid partition begin #{partition_filter.partition_begin}. Valid range: 0-#{Aerospike::Node::PARTITIONS - 1}"
37
+ )
38
+ end
39
+
40
+ if partition_filter.count <= 0
41
+ raise Aerospike::Exceptions::Aerospike.new(
42
+ Aerospike::ResultCode::PARAMETER_ERROR,
43
+ "Invalid partition count #{partition_filter.count}"
44
+ )
45
+ end
46
+
47
+ if partition_filter.partition_begin + partition_filter.count > Node::PARTITIONS
48
+ raise Aerospike::Exceptions::Aerospike.new(
49
+ Aerospike::ResultCode::PARAMETER_ERROR,
50
+ "Invalid partition range (#{partition_filter.partition_begin}, #{partition_filter.partition_begin + partition_filter.count}"
51
+ )
52
+ end
53
+
54
+ @partition_begin = partition_filter.partition_begin
55
+ @node_capacity = nodes.length
56
+ @node_filter = nil
57
+ @partitions_capacity = partition_filter.count
58
+ @max_records = policy.max_records
59
+ @iteration = 1
60
+
61
+ if partition_filter.partitions.nil? then
62
+ partition_filter.partitions = init_partitions(policy, partition_filter.count, partition_filter.digest)
63
+ elsif policy.max_records <= 0
64
+ # Retry all partitions when max_records not specified.
65
+ partition_filter.partitions.each do |ps|
66
+ ps.retry = true
67
+ end
68
+ end
69
+
70
+ @partitions = partition_filter.partitions
71
+ @partition_filter = partition_filter
72
+ init_timeout(policy)
73
+ end
74
+
75
+ def assign_partitions_to_nodes(cluster, namespace)
76
+ list = []
77
+
78
+ pmap = cluster.partitions
79
+ replica_array = pmap[namespace]
80
+ raise Aerospike::Exceptions::InvalidNamespace("namespace not found in the partition map") if !replica_array
81
+
82
+ master = (replica_array.get)[0]
83
+ master = master.get
84
+
85
+ @partitions.each do |part|
86
+ if part&.retry
87
+ node = master[part.id]
88
+
89
+ unless node
90
+ raise Exceptions::Aerospike.new(Aerospike::ResultCode::INVALID_NAMESPACE, "Invalid Partition Id #{part.id} for namespace `#{namespace}` in Partition Scan")
91
+ end
92
+
93
+ part.retry = false
94
+
95
+
96
+ # Use node name to check for single node equality because
97
+ # partition map may be in transitional state between
98
+ # the old and new node with the same name.
99
+ next if @node_filter && @node_filter.name != node.name
100
+
101
+ np = find_node(list, node)
102
+
103
+ unless np
104
+ # If the partition map is in a transitional state, multiple
105
+ # node_partitions instances (each with different partitions)
106
+ # may be created for a single node.
107
+ np = NodePartitions.new(node)
108
+ list << np
109
+ end
110
+ np.add_partition(part)
111
+ end
112
+ end
113
+
114
+ if @max_records.positive?
115
+ # Distribute max_records across nodes.
116
+ node_size = list.length
117
+
118
+ if @max_records < node_size
119
+ # Only include nodes that have at least 1 record requested.
120
+ node_size = @max_records
121
+ list = list[0...node_size]
122
+ end
123
+
124
+ max = 0
125
+ max = @max_records / node_size if node_size.positive?
126
+ rem = @max_records - (max * node_size)
127
+
128
+ list[0...node_size].each_with_index do |np, i|
129
+ np.record_max = (i < rem ? max + 1 : max)
130
+ end
131
+ end
132
+
133
+ @node_partitions_list = list
134
+ list
135
+ end
136
+
137
+ def init_timeout(policy)
138
+ @sleep_between_retries = policy.sleep_between_retries
139
+ @socket_timeout = policy.socket_timeout
140
+ @total_timeout = policy.timeout
141
+ if @total_timeout.positive?
142
+ @deadline = Time.now + @total_timeout
143
+ if !@socket_timeout || @socket_timeout > @total_timeout
144
+ @socket_timeout = @total_timeout
145
+ end
146
+ end
147
+ end
148
+
149
+ def init_partitions(policy, partition_count, digest)
150
+ parts_all = Array.new(partition_count)
151
+
152
+ (0...partition_count).each do |i|
153
+ parts_all[i] = Aerospike::PartitionStatus.new(@partition_begin + i)
154
+ end
155
+
156
+ parts_all[0].digest = digest if digest
157
+
158
+ @sleep_between_retries = policy.sleep_between_retries
159
+ @socket_timeout = policy.socket_timeout
160
+ @total_timeout = policy.timeout
161
+
162
+ if @total_timeout.positive?
163
+ @deadline = Time.now + @total_timeout
164
+
165
+ if @socket_timeout == 0 || @socket_timeout > @total_timeout
166
+ @socket_timeout = @total_timeout
167
+ end
168
+ end
169
+
170
+ parts_all
171
+ end
172
+
173
+ attr_writer :sleep_between_retries
174
+
175
+
176
+ def find_node(list, node)
177
+ list.each do |node_partition|
178
+ # Use pointer equality for performance.
179
+ return node_partition if node_partition.node == node
180
+ end
181
+ nil
182
+ end
183
+
184
+ def partition_unavailable(node_partitions, partition_id)
185
+ @partitions[partition_id-@partition_begin].retry = true
186
+ node_partitions.parts_unavailable+=1
187
+ end
188
+
189
+ def set_digest(node_partitions, key)
190
+ partition_id = key.partition_id
191
+ @partitions[partition_id-@partition_begin].digest = key.digest
192
+ node_partitions.record_count+=1
193
+ end
194
+
195
+ def set_last(node_partitions, key, bval)
196
+ partition_id = key.partition_id()
197
+ if partition_id-@partition_begin < 0
198
+ raise "key.partition_id: #{@partition_id}, partition_begin: #{@partition_begin}"
199
+ end
200
+ ps = @partitions[partition_id-@partition_begin]
201
+ ps.digest = key.digest
202
+ ps.bval = bval
203
+ node_partitions.record_count+=1
204
+ end
205
+
206
+ def complete?(cluster, policy)
207
+ record_count = 0
208
+ parts_unavailable = 0
209
+
210
+ @node_partitions_list.each do |np|
211
+ record_count += np.record_count
212
+ parts_unavailable += np.parts_unavailable
213
+ end
214
+
215
+ if parts_unavailable == 0
216
+ if @max_records <= 0
217
+ @partition_filter&.done = true
218
+ else
219
+ if cluster.supports_partition_query.get()
220
+ done = true
221
+
222
+ @node_partitions_list.each do |np|
223
+ if np.record_count >= np.record_max
224
+ mark_retry(np)
225
+ done = false
226
+ end
227
+ end
228
+
229
+ @partition_filter&.done = done
230
+ else
231
+ # Server version >= 6.0 will return all records for each node up to
232
+ # that node's max. If node's record count reached max, there stilthen
233
+ # may be records available for that node.
234
+ @node_partitions_list.each do |np|
235
+ mark_retry(np) if np.record_count > 0
236
+ end
237
+ # Servers version < 6.0 can return less records than max and still
238
+ # have more records for each node, so the node is only done if nthen
239
+ # records were retrieved for that node.
240
+
241
+ @partition_filter&.done = (record_count == 0)
242
+ end
243
+ end
244
+ return true
245
+ end
246
+
247
+ return true if @max_records&.positive? && record_count >= @max_records
248
+
249
+ # Check if limits have been reached
250
+ if policy.max_retries.positive? && @iteration > policy.max_retries
251
+ raise Aerospike::Exceptions::Aerospike.new(Aerospike::ResultCode::MAX_RETRIES_EXCEEDED, "Max retries exceeded: #{policy.max_retries}")
252
+ end
253
+
254
+ if policy.total_timeout > 0
255
+ # Check for total timeout.
256
+ remaining = @deadline - Time.now - @sleep_between_retries
257
+
258
+ raise Aerospike::Exceptions::Timeout.new(policy.totle_timeout, @iteration) if remaining <= 0
259
+
260
+ if remaining < @total_timeout
261
+ @total_timeout = remaining
262
+
263
+ if @socket_timeout > @total_timeout
264
+ @socket_timeout = @total_timeout
265
+ end
266
+ end
267
+ end
268
+
269
+ # Prepare for next iteration.
270
+ if @max_records > 0
271
+ @max_records -= record_count
272
+ end
273
+ @iteration+=1
274
+ false
275
+ end
276
+
277
+ def should_retry(node_partitions, err)
278
+ case err
279
+ when Aerospike::Exceptions::Aerospike
280
+ case err.result_code
281
+ when Aerospike::ResultCode::TIMEOUT,
282
+ Aerospike::ResultCode::NETWORK_ERROR,
283
+ Aerospike::ResultCode::SERVER_NOT_AVAILABLE,
284
+ Aerospike::ResultCode::INDEX_NOTFOUND
285
+ mark_retry(node_partitions)
286
+ node_partitions.parts_unavailable = node_partitions.parts_full.length + node_partitions.parts_partial.length
287
+ true
288
+ end
289
+ else
290
+ false
291
+ end
292
+ end
293
+
294
+ def mark_retry(node_partitions)
295
+ node_partitions.parts_full.each do |ps|
296
+ ps.retry = true
297
+ end
298
+
299
+ node_partitions.parts_partial.each do |ps|
300
+ ps.retry = true
301
+ end
302
+ end
303
+
304
+ def to_s
305
+ sb = StringIO.new
306
+ @partitions.each_with_index do |ps, i|
307
+ sb << ps.to_s
308
+ sb << if (i+1)%16 == 0
309
+ "\n"
310
+ else
311
+ "\t"
312
+ end
313
+ end
314
+ sb.string
315
+ end
316
+
317
+ private
318
+
319
+ def init_for_nodes(policy, nodes)
320
+ ppn = Aerospike::Node::PARTITIONS / nodes.length
321
+ ppn += ppn / 4
322
+
323
+ @partition_begin = 0
324
+ @node_capacity = nodes.length
325
+ @node_filter = nil
326
+ @partitions_capacity = ppn
327
+ @max_records = policy.max_records
328
+ @iteration = 1
329
+
330
+ @partitions = init_partitions(policy, Aerospike::Node::PARTITIONS, nil)
331
+ init_timeout(policy)
332
+ end
333
+
334
+ def init_for_node(policy, node)
335
+ @partition_begin = 0
336
+ @node_capacity = 1
337
+ @node_filter = node
338
+ @partitions_capacity = Aerospike::Node::PARTITIONS
339
+ @max_records = policy.max_records
340
+ @iteration = 1
341
+
342
+ @partitions = init_partitions(policy, Aerospike::Node::PARTITIONS, nil)
343
+ init_timeout(policy)
344
+ end
345
+
346
+ end
347
+ end