aerospike 2.19.0 → 2.26.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +354 -244
  3. data/lib/aerospike/atomic/atomic.rb +1 -1
  4. data/lib/aerospike/cdt/context.rb +137 -70
  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_policy.rb +16 -2
  8. data/lib/aerospike/cdt/map_return_type.rb +13 -1
  9. data/lib/aerospike/client.rb +137 -115
  10. data/lib/aerospike/cluster/create_connection.rb +1 -1
  11. data/lib/aerospike/cluster.rb +41 -4
  12. data/lib/aerospike/command/admin_command.rb +368 -52
  13. data/lib/aerospike/command/batch_index_command.rb +4 -8
  14. data/lib/aerospike/command/batch_index_exists_command.rb +1 -1
  15. data/lib/aerospike/command/batch_index_node.rb +1 -1
  16. data/lib/aerospike/command/batch_item.rb +1 -1
  17. data/lib/aerospike/command/command.rb +180 -123
  18. data/lib/aerospike/command/field_type.rb +25 -24
  19. data/lib/aerospike/command/login_command.rb +164 -0
  20. data/lib/aerospike/command/multi_command.rb +25 -2
  21. data/lib/aerospike/command/operate_args.rb +99 -0
  22. data/lib/aerospike/command/operate_command.rb +6 -11
  23. data/lib/aerospike/command/read_command.rb +2 -2
  24. data/lib/aerospike/connection/authenticate.rb +36 -3
  25. data/lib/aerospike/exp/exp.rb +1329 -0
  26. data/lib/aerospike/exp/exp_bit.rb +388 -0
  27. data/lib/aerospike/exp/exp_hll.rb +169 -0
  28. data/lib/aerospike/exp/exp_list.rb +403 -0
  29. data/lib/aerospike/exp/exp_map.rb +493 -0
  30. data/lib/aerospike/exp/operation.rb +56 -0
  31. data/lib/aerospike/features.rb +22 -9
  32. data/lib/aerospike/host/parse.rb +2 -2
  33. data/lib/aerospike/key.rb +10 -1
  34. data/lib/aerospike/node/refresh/info.rb +1 -1
  35. data/lib/aerospike/node/verify/name.rb +1 -1
  36. data/lib/aerospike/node/verify/partition_generation.rb +1 -1
  37. data/lib/aerospike/node/verify/peers_generation.rb +1 -1
  38. data/lib/aerospike/node/verify/rebalance_generation.rb +1 -1
  39. data/lib/aerospike/node_validator.rb +6 -1
  40. data/lib/aerospike/operation.rb +20 -22
  41. data/lib/aerospike/policy/auth_mode.rb +36 -0
  42. data/lib/aerospike/policy/client_policy.rb +4 -1
  43. data/lib/aerospike/policy/policy.rb +29 -13
  44. data/lib/aerospike/policy/query_policy.rb +35 -2
  45. data/lib/aerospike/policy/scan_policy.rb +19 -2
  46. data/lib/aerospike/privilege.rb +133 -0
  47. data/lib/aerospike/query/filter.rb +44 -32
  48. data/lib/aerospike/query/node_partitions.rb +39 -0
  49. data/lib/aerospike/query/partition_filter.rb +66 -0
  50. data/lib/aerospike/{command/roles.rb → query/partition_status.rb} +16 -19
  51. data/lib/aerospike/query/partition_tracker.rb +347 -0
  52. data/lib/aerospike/query/query_command.rb +20 -10
  53. data/lib/aerospike/query/query_executor.rb +71 -0
  54. data/lib/aerospike/query/query_partition_command.rb +267 -0
  55. data/lib/aerospike/query/recordset.rb +9 -9
  56. data/lib/aerospike/query/scan_command.rb +3 -2
  57. data/lib/aerospike/query/scan_executor.rb +71 -0
  58. data/lib/aerospike/query/scan_partition_command.rb +49 -0
  59. data/lib/aerospike/query/statement.rb +8 -1
  60. data/lib/aerospike/query/stream_command.rb +17 -0
  61. data/lib/aerospike/result_code.rb +83 -8
  62. data/lib/aerospike/role.rb +55 -0
  63. data/lib/aerospike/task/execute_task.rb +19 -16
  64. data/lib/aerospike/task/index_task.rb +1 -1
  65. data/lib/aerospike/user_role.rb +26 -1
  66. data/lib/aerospike/utils/buffer.rb +93 -29
  67. data/lib/aerospike/utils/packer.rb +7 -6
  68. data/lib/aerospike/utils/pool.rb +1 -1
  69. data/lib/aerospike/value/particle_type.rb +1 -12
  70. data/lib/aerospike/value/value.rb +35 -60
  71. data/lib/aerospike/version.rb +1 -1
  72. data/lib/aerospike.rb +156 -136
  73. metadata +24 -6
@@ -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
@@ -1,4 +1,5 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
+
2
3
  # Copyright 2014-2020 Aerospike, Inc.
3
4
  #
4
5
  # Portions may be licensed to Aerospike, Inc. under one or more contributor
@@ -16,24 +17,20 @@
16
17
 
17
18
  module Aerospike
18
19
 
19
- # Pre-defined user roles.
20
- module Role
21
-
22
- # Manage users and their roles.
23
- USER_ADMIN = 'user-admin'
24
-
25
- # Manage indicies, user-defined functions and server configuration.
26
- SYS_ADMIN = 'sys-admin'
27
-
28
- # Allow read, write and UDF transactions with the database.
29
- READ_WRITE_UDF = "read-write-udf"
30
-
31
- # Allow read and write transactions with the database.
32
- READ_WRITE = 'read-write'
20
+ class PartitionStatus #:nodoc:
21
+ attr_accessor :bval, :id, :digest, :retry
33
22
 
34
- # Allow read transactions with the database.
35
- READ = 'read'
23
+ def initialize(id)
24
+ @id = id
25
+ @retry = true
26
+ end
36
27
 
37
- end # module
28
+ #
29
+ # Show the PartitionStatus as String.
30
+ #
31
+ def to_s
32
+ "PartitionStatus<bval: #{@bval}, id: #{@id}, retry: #{@retry}, digest: #{@digest}>"
33
+ end
38
34
 
39
- end # module
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
@@ -23,12 +23,13 @@ module Aerospike
23
23
 
24
24
  class QueryCommand < StreamCommand #:nodoc:
25
25
 
26
- def initialize(node, policy, statement, recordset)
26
+ def initialize(node, policy, statement, recordset, partitions)
27
27
  super(node)
28
28
 
29
29
  @policy = policy
30
30
  @statement = statement
31
31
  @recordset = recordset
32
+ @partitions = partitions
32
33
  end
33
34
 
34
35
  def write_buffer
@@ -82,6 +83,9 @@ module Aerospike
82
83
  fieldCount+=1
83
84
  end
84
85
  else
86
+ @data_offset += @partitions.length * 2 + FIELD_HEADER_SIZE
87
+ fieldCount += 1
88
+
85
89
  if @policy.records_per_second > 0
86
90
  @data_offset += 4 + FIELD_HEADER_SIZE
87
91
  fieldCount += 1
@@ -89,8 +93,8 @@ module Aerospike
89
93
 
90
94
  # Calling query with no filters is more efficiently handled by a primary index scan.
91
95
  # Estimate scan options size.
92
- @data_offset += (2 + FIELD_HEADER_SIZE)
93
- fieldCount+=1
96
+ # @data_offset += (2 + FIELD_HEADER_SIZE)
97
+ # fieldCount+=1
94
98
  end
95
99
 
96
100
  @statement.set_task_id
@@ -177,18 +181,24 @@ module Aerospike
177
181
  end
178
182
  end
179
183
  else
184
+ write_field_header(@partitions.length * 2, Aerospike::FieldType::PID_ARRAY)
185
+ for pid in @partitions
186
+ @data_buffer.write_uint16_little_endian(pid, @data_offset)
187
+ @data_offset += 2
188
+ end
189
+
180
190
  if @policy.records_per_second > 0
181
191
  write_field_int(@policy.records_per_second, Aerospike::FieldType::RECORDS_PER_SECOND)
182
192
  end
183
193
 
184
194
  # Calling query with no filters is more efficiently handled by a primary index scan.
185
- write_field_header(2, Aerospike::FieldType::SCAN_OPTIONS)
186
- priority = @policy.priority.ord
187
- priority = priority << 4
188
- @data_buffer.write_byte(priority, @data_offset)
189
- @data_offset+=1
190
- @data_buffer.write_byte(100.ord, @data_offset)
191
- @data_offset+=1
195
+ # write_field_header(2, Aerospike::FieldType::SCAN_OPTIONS)
196
+ # priority = @policy.priority.ord
197
+ # priority = priority << 4
198
+ # @data_buffer.write_byte(priority, @data_offset)
199
+ # @data_offset+=1
200
+ # @data_buffer.write_byte(100.ord, @data_offset)
201
+ # @data_offset+=1
192
202
  end
193
203
 
194
204
  write_field_header(8, Aerospike::FieldType::TRAN_ID)
@@ -0,0 +1,71 @@
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 QueryExecutor # :nodoc:
20
+ def self.query_partitions(cluster, policy, tracker, statement, recordset)
21
+ interval = policy.sleep_between_retries
22
+
23
+ should_retry = false
24
+
25
+ loop do
26
+ # reset last_expn
27
+ @last_expn = nil
28
+
29
+ list = tracker.assign_partitions_to_nodes(cluster, statement.namespace)
30
+
31
+ if policy.concurrent_nodes
32
+ threads = []
33
+ # Use a thread per node
34
+ list.each do |node_partition|
35
+ threads << Thread.new do
36
+ Thread.current.abort_on_exception = true
37
+ command = QueryPartitionCommand.new(node_partition.node, tracker, policy, statement, recordset, node_partition)
38
+ begin
39
+ command.execute
40
+ rescue => e
41
+ @last_expn = e unless e == QUERY_TERMINATED_EXCEPTION
42
+ should_retry ||= command.should_retry(e)
43
+ Aerospike.logger.error(e.backtrace.join("\n")) unless e == QUERY_TERMINATED_EXCEPTION
44
+ end
45
+ end
46
+ end
47
+ threads.each(&:join)
48
+ else
49
+ # Use a single thread for all nodes for all node
50
+ list.each do |node_partition|
51
+ command = QueryPartitionCommand.new(node_partition.node, tracker, policy, statement, recordset, node_partition)
52
+ begin
53
+ command.execute
54
+ rescue => e
55
+ @last_expn = e unless e == QUERY_TERMINATED_EXCEPTION
56
+ should_retry ||= command.should_retry(e)
57
+ Aerospike.logger.error(e.backtrace.join("\n")) unless e == QUERY_TERMINATED_EXCEPTION
58
+ end
59
+ end
60
+ end
61
+
62
+ if tracker.complete?(@cluster, policy) || !should_retry
63
+ recordset.thread_finished(@last_expn)
64
+ return
65
+ end
66
+ sleep(interval) if policy.sleep_between_retries > 0
67
+ statement.reset_task_id
68
+ end
69
+ end
70
+ end
71
+ end