aerospike 2.11.0 → 2.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -1
  3. data/README.md +1 -1
  4. data/lib/aerospike.rb +17 -4
  5. data/lib/aerospike/aerospike_exception.rb +7 -1
  6. data/lib/aerospike/atomic/atomic.rb +1 -1
  7. data/lib/aerospike/bin.rb +1 -1
  8. data/lib/aerospike/cdt/bit_operation.rb +376 -0
  9. data/lib/aerospike/cdt/bit_overflow_action.rb +46 -0
  10. data/lib/aerospike/cdt/bit_policy.rb +36 -0
  11. data/lib/aerospike/cdt/bit_resize_flags.rb +44 -0
  12. data/lib/aerospike/cdt/bit_write_flags.rb +51 -0
  13. data/lib/aerospike/cdt/context.rb +101 -0
  14. data/lib/aerospike/cdt/hll_operation.rb +200 -0
  15. data/lib/aerospike/cdt/hll_policy.rb +34 -0
  16. data/lib/aerospike/cdt/hll_write_flags.rb +53 -0
  17. data/lib/aerospike/cdt/list_operation.rb +127 -97
  18. data/lib/aerospike/cdt/list_sort_flags.rb +10 -2
  19. data/lib/aerospike/cdt/map_operation.rb +154 -93
  20. data/lib/aerospike/cdt/map_order.rb +1 -1
  21. data/lib/aerospike/cdt/map_policy.rb +1 -1
  22. data/lib/aerospike/cdt/map_return_type.rb +1 -1
  23. data/lib/aerospike/cdt/map_write_mode.rb +1 -1
  24. data/lib/aerospike/client.rb +33 -17
  25. data/lib/aerospike/cluster.rb +139 -17
  26. data/lib/aerospike/cluster/partition.rb +1 -1
  27. data/lib/aerospike/cluster/partition_parser.rb +169 -0
  28. data/lib/aerospike/cluster/rack_parser.rb +117 -0
  29. data/lib/aerospike/command/admin_command.rb +1 -1
  30. data/lib/aerospike/command/batch_direct_command.rb +2 -1
  31. data/lib/aerospike/command/batch_direct_exists_command.rb +1 -1
  32. data/lib/aerospike/command/batch_direct_node.rb +3 -3
  33. data/lib/aerospike/command/batch_index_command.rb +11 -2
  34. data/lib/aerospike/command/batch_index_node.rb +2 -2
  35. data/lib/aerospike/command/batch_item.rb +1 -1
  36. data/lib/aerospike/command/command.rb +168 -12
  37. data/lib/aerospike/command/delete_command.rb +21 -5
  38. data/lib/aerospike/command/execute_command.rb +1 -1
  39. data/lib/aerospike/command/exists_command.rb +21 -5
  40. data/lib/aerospike/command/field_type.rb +2 -1
  41. data/lib/aerospike/command/multi_command.rb +55 -5
  42. data/lib/aerospike/command/operate_command.rb +6 -1
  43. data/lib/aerospike/command/read_command.rb +63 -20
  44. data/lib/aerospike/command/read_header_command.rb +18 -6
  45. data/lib/aerospike/command/roles.rb +1 -1
  46. data/lib/aerospike/command/single_command.rb +9 -3
  47. data/lib/aerospike/command/touch_command.rb +48 -4
  48. data/lib/aerospike/command/unsupported_particle_type_validator.rb +1 -1
  49. data/lib/aerospike/command/write_command.rb +13 -4
  50. data/lib/aerospike/connection/create.rb +1 -1
  51. data/lib/aerospike/features.rb +6 -1
  52. data/lib/aerospike/geo_json.rb +1 -1
  53. data/lib/aerospike/host.rb +1 -1
  54. data/lib/aerospike/info.rb +1 -1
  55. data/lib/aerospike/key.rb +1 -1
  56. data/lib/aerospike/language.rb +1 -1
  57. data/lib/aerospike/node.rb +21 -7
  58. data/lib/aerospike/node/rebalance.rb +50 -0
  59. data/lib/aerospike/node/refresh/info.rb +4 -1
  60. data/lib/aerospike/node/refresh/partitions.rb +6 -15
  61. data/lib/aerospike/node/refresh/racks.rb +47 -0
  62. data/lib/aerospike/node/refresh/reset.rb +1 -0
  63. data/lib/aerospike/node/verify/rebalance_generation.rb +43 -0
  64. data/lib/aerospike/node_validator.rb +4 -19
  65. data/lib/aerospike/operation.rb +13 -3
  66. data/lib/aerospike/policy/admin_policy.rb +1 -1
  67. data/lib/aerospike/policy/batch_policy.rb +1 -1
  68. data/lib/aerospike/policy/client_policy.rb +16 -1
  69. data/lib/aerospike/policy/commit_level.rb +1 -1
  70. data/lib/aerospike/policy/consistency_level.rb +1 -1
  71. data/lib/aerospike/policy/generation_policy.rb +1 -1
  72. data/lib/aerospike/policy/operate_policy.rb +1 -1
  73. data/lib/aerospike/policy/policy.rb +64 -2
  74. data/lib/aerospike/policy/priority.rb +1 -1
  75. data/lib/aerospike/policy/query_policy.rb +8 -1
  76. data/lib/aerospike/policy/record_bin_multiplicity.rb +1 -1
  77. data/lib/aerospike/policy/record_exists_action.rb +1 -1
  78. data/lib/aerospike/policy/replica.rb +45 -0
  79. data/lib/aerospike/policy/scan_policy.rb +8 -1
  80. data/lib/aerospike/policy/write_policy.rb +1 -1
  81. data/lib/aerospike/query/filter.rb +1 -1
  82. data/lib/aerospike/query/query_command.rb +16 -5
  83. data/lib/aerospike/query/recordset.rb +1 -1
  84. data/lib/aerospike/query/scan_command.rb +1 -1
  85. data/lib/aerospike/query/statement.rb +9 -2
  86. data/lib/aerospike/query/stream_command.rb +1 -1
  87. data/lib/aerospike/record.rb +1 -1
  88. data/lib/aerospike/result_code.rb +26 -7
  89. data/lib/aerospike/socket/base.rb +4 -3
  90. data/lib/aerospike/task/execute_task.rb +1 -1
  91. data/lib/aerospike/task/index_task.rb +1 -1
  92. data/lib/aerospike/task/task.rb +1 -1
  93. data/lib/aerospike/task/udf_register_task.rb +1 -1
  94. data/lib/aerospike/task/udf_remove_task.rb +1 -1
  95. data/lib/aerospike/ttl.rb +1 -1
  96. data/lib/aerospike/udf.rb +1 -1
  97. data/lib/aerospike/user_role.rb +1 -1
  98. data/lib/aerospike/utils/buffer.rb +14 -4
  99. data/lib/aerospike/utils/packer.rb +1 -1
  100. data/lib/aerospike/utils/pool.rb +1 -1
  101. data/lib/aerospike/utils/unpacker.rb +1 -1
  102. data/lib/aerospike/value/particle_type.rb +2 -2
  103. data/lib/aerospike/value/value.rb +165 -33
  104. data/lib/aerospike/version.rb +1 -1
  105. metadata +20 -8
  106. data/lib/aerospike/cluster/partition_tokenizer_new.rb +0 -130
  107. data/lib/aerospike/cluster/partition_tokenizer_old.rb +0 -135
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
- # Copyright 2016-2017 Aerospike, Inc.
2
+ # Copyright 2016-2020 Aerospike, Inc.
3
3
  #
4
4
  # Portions may be licensed to Aerospike, Inc. under one or more contributor
5
5
  # license agreements.
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
- # Copyright 2016-2018 Aerospike, Inc.
2
+ # Copyright 2016-2020 Aerospike, Inc.
3
3
  #
4
4
  # Portions may be licensed to Aerospike, Inc. under one or more contributor
5
5
  # license agreements.
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
- # Copyright 2016-2017 Aerospike, Inc.
2
+ # Copyright 2016-2020 Aerospike, Inc.
3
3
  #
4
4
  # Portions may be licensed to Aerospike, Inc. under one or more contributor
5
5
  # license agreements.
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
- # Copyright 2016-2018 Aerospike, Inc.
2
+ # Copyright 2016-2020 Aerospike, Inc.
3
3
  #
4
4
  # Portions may be licensed to Aerospike, Inc. under one or more contributor
5
5
  # license agreements.
@@ -1,4 +1,4 @@
1
- # Copyright 2014-2018 Aerospike, Inc.
1
+ # Copyright 2014-2020 Aerospike, Inc.
2
2
  #
3
3
  # Portions may be licensed to Aerospike, Inc. under one or more contributor
4
4
  # license agreements.
@@ -44,6 +44,8 @@ module Aerospike
44
44
  attr_accessor :default_read_policy
45
45
  attr_accessor :default_scan_policy
46
46
  attr_accessor :default_write_policy
47
+ attr_accessor :default_operate_policy
48
+ attr_accessor :cluster
47
49
 
48
50
  def initialize(hosts = nil, policy: ClientPolicy.new, connect: true)
49
51
 
@@ -224,8 +226,19 @@ module Aerospike
224
226
  def truncate(namespace, set_name = nil, before_last_update = nil, options = {})
225
227
  policy = create_policy(options, Policy, default_info_policy)
226
228
 
227
- str_cmd = "truncate:namespace=#{namespace}"
228
- str_cmd << ";set=#{set_name}" unless set_name.to_s.strip.empty?
229
+ node = @cluster.random_node
230
+ conn = node.get_connection(policy.timeout)
231
+
232
+ if set_name && !set_name.to_s.strip.empty?
233
+ str_cmd = "truncate:namespace=#{namespace}"
234
+ str_cmd << ";set=#{set_name}" unless set_name.to_s.strip.empty?
235
+ else
236
+ if node.supports_feature(Aerospike::Features::TRUNCATE_NAMESPACE)
237
+ str_cmd = "truncate-namespace:namespace=#{namespace}"
238
+ else
239
+ str_cmd = "truncate:namespace=#{namespace}"
240
+ end
241
+ end
229
242
 
230
243
  if before_last_update
231
244
  lut_nanos = (before_last_update.to_f * 1_000_000_000.0).round
@@ -235,8 +248,7 @@ module Aerospike
235
248
  str_cmd << ";lut=now"
236
249
  end
237
250
 
238
- # Send index command to one node. That node will distribute the command to other nodes.
239
- response = send_info_command(policy, str_cmd).upcase
251
+ response = send_info_command(policy, str_cmd, node).upcase
240
252
  return if response == 'OK'
241
253
  raise Aerospike::Exceptions::Aerospike.new(Aerospike::ResultCode::SERVER_ERROR, "Truncate failed: #{response}")
242
254
  end
@@ -322,11 +334,11 @@ module Aerospike
322
334
 
323
335
  if policy.use_batch_direct
324
336
  key_map = BatchItem.generate_map(keys)
325
- execute_batch_direct_commands(keys) do |node, batch|
337
+ execute_batch_direct_commands(policy, keys) do |node, batch|
326
338
  BatchDirectCommand.new(node, batch, policy, key_map, bin_names, results, info_flags)
327
339
  end
328
340
  else
329
- execute_batch_index_commands(keys) do |node, batch|
341
+ execute_batch_index_commands(policy, keys) do |node, batch|
330
342
  BatchIndexCommand.new(node, batch, policy, bin_names, results, info_flags)
331
343
  end
332
344
  end
@@ -351,11 +363,11 @@ module Aerospike
351
363
 
352
364
  if policy.use_batch_direct
353
365
  key_map = BatchItem.generate_map(keys)
354
- execute_batch_direct_commands(keys) do |node, batch|
366
+ execute_batch_direct_commands(policy, keys) do |node, batch|
355
367
  BatchDirectExistsCommand.new(node, batch, policy, key_map, results)
356
368
  end
357
369
  else
358
- execute_batch_index_commands(keys) do |node, batch|
370
+ execute_batch_index_commands(policy, keys) do |node, batch|
359
371
  BatchIndexExistsCommand.new(node, batch, policy, results)
360
372
  end
361
373
  end
@@ -372,7 +384,7 @@ module Aerospike
372
384
  # read the result, all in one database call. Operations are executed in
373
385
  # the order they are specified.
374
386
  def operate(key, operations, options = nil)
375
- policy = create_policy(options, WritePolicy, default_write_policy)
387
+ policy = create_policy(options, OperatePolicy, default_operate_policy)
376
388
 
377
389
  command = OperateCommand.new(@cluster, policy, key, operations)
378
390
  execute_command(command)
@@ -816,11 +828,16 @@ module Aerospike
816
828
  self.default_query_policy = create_policy(policies[:query], QueryPolicy)
817
829
  self.default_scan_policy = create_policy(policies[:scan], ScanPolicy)
818
830
  self.default_write_policy = create_policy(policies[:write], WritePolicy)
831
+ self.default_operate_policy = create_policy(policies[:operate], OperatePolicy)
819
832
  end
820
833
 
821
- def send_info_command(policy, command)
834
+ def send_info_command(policy, command, node = nil)
822
835
  Aerospike.logger.debug { "Sending info command: #{command}" }
823
- _, response = @cluster.request_info(policy, command).first
836
+ if node
837
+ _, response = @cluster.request_node_info(node, policy, command).first
838
+ else
839
+ _, response = @cluster.request_info(policy, command).first
840
+ end
824
841
  response.to_s
825
842
  end
826
843
 
@@ -884,17 +901,16 @@ module Aerospike
884
901
  command.execute
885
902
  end
886
903
 
887
- def execute_batch_index_commands(keys)
904
+ def execute_batch_index_commands(policy, keys)
888
905
  if @cluster.nodes.empty?
889
906
  raise Aerospike::Exceptions::Aerospike.new(Aerospike::ResultCode::SERVER_NOT_AVAILABLE, "Executing Batch Index command failed because cluster is empty.")
890
907
  end
891
908
 
892
- batch_nodes = BatchIndexNode.generate_list(@cluster, keys)
909
+ batch_nodes = BatchIndexNode.generate_list(@cluster, policy.replica, keys)
893
910
  threads = []
894
911
 
895
912
  batch_nodes.each do |batch|
896
913
  threads << Thread.new do
897
- Thread.current.abort_on_exception = true
898
914
  command = yield batch.node, batch
899
915
  execute_command(command)
900
916
  end
@@ -903,12 +919,12 @@ module Aerospike
903
919
  threads.each(&:join)
904
920
  end
905
921
 
906
- def execute_batch_direct_commands(keys)
922
+ def execute_batch_direct_commands(policy, keys)
907
923
  if @cluster.nodes.empty?
908
924
  raise Aerospike::Exceptions::Aerospike.new(Aerospike::ResultCode::SERVER_NOT_AVAILABLE, "Executing Batch Direct command failed because cluster is empty.")
909
925
  end
910
926
 
911
- batch_nodes = BatchDirectNode.generate_list(@cluster, keys)
927
+ batch_nodes = BatchDirectNode.generate_list(@cluster, policy.replica, keys)
912
928
  threads = []
913
929
 
914
930
  # Use a thread per namespace per node
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2014-2018 Aerospike, Inc.
3
+ # Copyright 2014-2020 Aerospike, Inc.
4
4
  #
5
5
  # Portions may be licensed to Aerospike, Inc. under one or more contributor
6
6
  # license agreements.
@@ -28,6 +28,7 @@ module Aerospike
28
28
  attr_reader :features, :tls_options
29
29
  attr_reader :cluster_id, :aliases
30
30
  attr_reader :cluster_name
31
+ attr_accessor :rack_aware, :rack_id
31
32
 
32
33
  def initialize(policy, hosts)
33
34
  @cluster_seeds = hosts
@@ -37,6 +38,10 @@ module Aerospike
37
38
  @tend_interval = policy.tend_interval
38
39
  @cluster_name = policy.cluster_name
39
40
  @tls_options = policy.tls
41
+ @rack_aware = policy.rack_aware
42
+ @rack_id = policy.rack_id
43
+
44
+ @replica_index = Atomic.new(0)
40
45
 
41
46
  @aliases = {}
42
47
  @cluster_nodes = []
@@ -102,18 +107,128 @@ module Aerospike
102
107
  (node_array.length > 0) && !@closed.value
103
108
  end
104
109
 
105
- def get_node_for_key(key)
106
- partition = Partition.new_by_key(key)
110
+ # Returns a node on the cluster for read operations
111
+ def batch_read_node(partition, replica_policy)
112
+ case replica_policy
113
+ when Aerospike::Replica::MASTER, Aerospike::Replica::SEQUENCE
114
+ return master_node(partition)
115
+ when Aerospike::Replica::MASTER_PROLES
116
+ return master_proles_node(partition)
117
+ when Aerospike::Replica::PREFER_RACK
118
+ return rack_node(partition, seq)
119
+ when Aerospike::Replica::RANDOM
120
+ return random_node
121
+ else
122
+ raise Aerospike::Exceptions::InvalidNode("invalid policy.replica value")
123
+ end
124
+ end
125
+
126
+ # Returns a node on the cluster for read operations
127
+ def read_node(partition, replica_policy, seq)
128
+ case replica_policy
129
+ when Aerospike::Replica::MASTER
130
+ return master_node(partition)
131
+ when Aerospike::Replica::MASTER_PROLES
132
+ return master_proles_node(partition)
133
+ when Aerospike::Replica::PREFER_RACK
134
+ return rack_node(partition, seq)
135
+ when Aerospike::Replica::SEQUENCE
136
+ return sequence_node(partition, seq)
137
+ when Aerospike::Replica::RANDOM
138
+ return random_node
139
+ else
140
+ raise Aerospike::Exceptions::InvalidNode("invalid policy.replica value")
141
+ end
142
+ end
143
+
144
+ # Returns a node on the cluster for read operations
145
+ def master_node(partition)
146
+ partition_map = partitions
147
+ replica_array = partition_map[partition.namespace]
148
+ raise Aerospike::Exceptions::InvalidNamespace("namespace not found in the partition map") if !replica_array
149
+
150
+ node_array = (replica_array.get)[0]
151
+ raise Aerospike::Exceptions::InvalidNamespace("namespace not found in the partition map") if !node_array
152
+
153
+ node = (node_array.get)[partition.partition_id]
154
+ raise Aerospike::Exceptions::InvalidNode if !node || !node.active?
155
+
156
+ node
157
+ end
158
+
159
+ # Returns a node on the cluster
160
+ def rack_node(partition, seq)
161
+ partition_map = partitions
162
+ replica_array = partition_map[partition.namespace]
163
+ raise Aerospike::Exceptions::InvalidNamespace("namespace not found in the partition map") if !replica_array
164
+
165
+ replica_array = replica_array.get
166
+
167
+ is_retry = seq.value > -1
168
+
169
+ node = nil
170
+ fallback = nil
171
+ for i in 1..replica_array.length
172
+ idx = (seq.update{|v| v.succ} % replica_array.size).abs
173
+ node = (replica_array[idx].get)[partition.partition_id]
174
+
175
+ next if !node
176
+
177
+ fallback = node
178
+
179
+ # If fallback exists, do not retry on node where command failed,
180
+ # even if fallback is not on the same rack.
181
+ return fallback if is_retry && fallback && i == replica_array.length
182
+
183
+ return node if node && node.active? && node.has_rack(partition.namespace, @rack_id)
184
+ end
185
+
186
+ return fallback if fallback
187
+
188
+ raise Aerospike::Exceptions::InvalidNode
189
+ end
107
190
 
108
- # Must copy hashmap reference for copy on write semantics to work.
109
- nmap = partitions
110
- if node_array = nmap[partition.namespace]
111
- node = node_array.value[partition.partition_id]
191
+ # Returns a node on the cluster for read operations
192
+ def master_proles_node(partition)
193
+ partition_map = partitions
194
+ replica_array = partition_map[partition.namespace]
195
+ raise Aerospike::Exceptions::InvalidNamespace("namespace not found in the partition map") if !replica_array
196
+
197
+ replica_array = replica_array.get
198
+
199
+ node = nil
200
+ for replica in replica_array
201
+ idx = (@replica_index.update{|v| v.succ} % replica_array.size).abs
202
+ node = (replica_array[idx].get)[partition.partition_id]
203
+
204
+ return node if node && node.active?
205
+ end
206
+
207
+ raise Aerospike::Exceptions::InvalidNode
208
+ end
209
+
210
+ # Returns a random node on the cluster
211
+ def sequence_node(partition, seq)
212
+ partition_map = partitions
213
+ replica_array = partition_map[partition.namespace]
214
+ raise Aerospike::Exceptions::InvalidNamespace("namespace not found in the partition map") if !replica_array
215
+
216
+ replica_array = replica_array.get
217
+
218
+ node = nil
219
+ for replica in replica_array
220
+ idx = (seq.update{|v| v.succ} % replica_array.size).abs
221
+ node = (replica_array[idx].get)[partition.partition_id]
112
222
 
113
223
  return node if node && node.active?
114
224
  end
115
225
 
116
- random_node
226
+ raise Aerospike::Exceptions::InvalidNode
227
+ end
228
+
229
+ def get_node_for_key(replica_policy, key)
230
+ partition = Partition.new_by_key(key)
231
+ batch_read_node(partition, replica_policy)
117
232
  end
118
233
 
119
234
  # Returns a random node on the cluster
@@ -124,8 +239,8 @@ module Aerospike
124
239
  i = 0
125
240
  while i < length
126
241
  # Must handle concurrency with other non-tending threads, so node_index is consistent.
127
- index = (@node_index.update{ |v| v+1 } % node_array.length).abs
128
- node = node_array[index]
242
+ idx = (@node_index.update{ |v| v.succ } % node_array.length).abs
243
+ node = node_array[idx]
129
244
 
130
245
  return node if node.active?
131
246
 
@@ -167,12 +282,9 @@ module Aerospike
167
282
  end
168
283
  end
169
284
 
170
- def update_partitions(tokens, node)
171
- nmap = tokens.update_partition(partitions, node)
172
- # update partition write map
285
+ def update_partitions(parser)
286
+ nmap = parser.update_partitions(partitions)
173
287
  set_partitions(nmap) if nmap
174
-
175
- Aerospike.logger.info("Partitions for node #{node.name} updated")
176
288
  end
177
289
 
178
290
  def request_info(policy, *commands)
@@ -183,6 +295,13 @@ module Aerospike
183
295
  end
184
296
  end
185
297
 
298
+ def request_node_info(node, policy, *commands)
299
+ conn = node.get_connection(policy.timeout)
300
+ Info.request(conn, *commands).tap do
301
+ node.put_connection(conn)
302
+ end
303
+ end
304
+
186
305
  def supports_feature?(feature)
187
306
  @features.get.include?(feature.to_s)
188
307
  end
@@ -279,6 +398,7 @@ module Aerospike
279
398
 
280
399
  nodes.each do |node|
281
400
  node.refresh_partitions(peers) if node.partition_generation.changed?
401
+ node.refresh_racks if node.rebalance_generation.changed?
282
402
  end
283
403
 
284
404
  if peers.generation_changed? || !peers.use_peers?
@@ -441,8 +561,10 @@ module Aerospike
441
561
  def find_node_in_partition_map(filter)
442
562
  partitions_list = partitions
443
563
 
444
- partitions_list.values.each do |node_array|
445
- return true if node_array.value.any? { |node| node == filter }
564
+ partitions_list.values.each do |replica_array|
565
+ replica_array.get.each do |node_array|
566
+ return true if node_array.value.any? { |node| node == filter }
567
+ end
446
568
  end
447
569
  false
448
570
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2014-2017 Aerospike, Inc.
3
+ # Copyright 2014-2020 Aerospike, Inc.
4
4
  #
5
5
  # Portions may be licensed to Aerospike, Inc. under one or more contributor
6
6
  # license agreements.
@@ -0,0 +1,169 @@
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
+ require 'base64'
19
+
20
+ module Aerospike
21
+
22
+ class PartitionParser #:nodoc:
23
+
24
+ attr_accessor :copied, :partition_generation
25
+
26
+ PARTITION_GENERATION = "partition-generation";
27
+ REPLICAS_ALL = "replicas-all";
28
+
29
+ def initialize(node, conn)
30
+ @node = node
31
+ @conn = conn
32
+ end
33
+
34
+ def update_partitions(current_map)
35
+ # Use low-level info methods and parse byte array directly for maximum performance.
36
+ # Receive format: replicas-all\t
37
+ # <ns1>:<count>,<base 64 encoded bitmap1>,<base 64 encoded bitmap2>...;
38
+ # <ns2>:<count>,<base 64 encoded bitmap1>,<base 64 encoded bitmap2>...;\n
39
+ info_map = Info.request(@conn, PARTITION_GENERATION, REPLICAS_ALL)
40
+
41
+ @partition_generation = info_map[PARTITION_GENERATION].to_i
42
+
43
+ info = info_map[REPLICAS_ALL]
44
+ if !info || info.length == 0
45
+ raise Aerospike::Exceptions::Connection.new("#{REPLICAS_ALL} response for node #{@node.name} is empty")
46
+ end
47
+
48
+ @buffer = info
49
+ @length = info.length
50
+ @offset = 0
51
+
52
+ new_map = nil
53
+ copied = false
54
+ beginning = @offset
55
+
56
+ while @offset < @length && @buffer[@offset] != '\n'
57
+ namespace = parse_name
58
+ replica_count = parse_replica_count
59
+
60
+ replica_array = current_map[namespace]
61
+ if !replica_array
62
+ if !copied
63
+ # Make shallow copy of map.
64
+ new_map = current_map.clone
65
+ copied = true
66
+ end
67
+
68
+ replica_array = Atomic.new(Array.new(replica_count))
69
+ new_map[namespace] = replica_array
70
+ end
71
+
72
+ for replica in 0...replica_count do
73
+ node_array = (replica_array.get)[replica]
74
+
75
+ if !node_array
76
+ if !copied
77
+ # Make shallow copy of map.
78
+ new_map = current_map.clone
79
+ copied = true
80
+ end
81
+
82
+ node_array = Atomic.new(Array.new(Aerospike::Node::PARTITIONS))
83
+ new_map[namespace].update{|v| v[replica] = node_array; v}
84
+ end
85
+
86
+ restore_buffer = parse_bitmap
87
+ i = 0
88
+ while i < Aerospike::Node::PARTITIONS
89
+ if (restore_buffer[i>>3].ord & (0x80 >> (i & 7))) != 0
90
+ node_array.update{|v| v[i] = @node; v}
91
+ end
92
+ i = i.succ
93
+ end
94
+ end
95
+ end
96
+
97
+ copied ? new_map : nil
98
+ end
99
+
100
+ private
101
+
102
+ def parse_name
103
+ beginning = @offset
104
+ while @offset < @length
105
+ break if @buffer[@offset] == ':'
106
+ @offset+=1
107
+ end
108
+
109
+ # Parse namespace.
110
+ namespace = @buffer[beginning...@offset].strip
111
+
112
+ if namespace.length <= 0 || namespace.length >= 32
113
+ response = get_truncated_response
114
+ raise Aerospike::Exceptions::Parse.new(
115
+ "Invalid partition namespace #{namespace}. Response=#{response}"
116
+ )
117
+ end
118
+
119
+ @offset+=1
120
+ namespace
121
+ end
122
+
123
+ def parse_replica_count
124
+ beginning = @offset
125
+ while @offset < @length
126
+ break if @buffer[@offset] == ','
127
+ @offset+=1
128
+ end
129
+
130
+ # Parse count
131
+ count = @buffer[beginning...@offset].strip.to_i
132
+
133
+ if count < 0 || count > 4096
134
+ response = get_truncated_response
135
+ raise Aerospike::Exceptions::Parse.new(
136
+ "Invalid partition count #{count}. Response=#{response}"
137
+ )
138
+ end
139
+
140
+ @offset+=1
141
+ count
142
+ end
143
+
144
+ def parse_bitmap
145
+ beginning = @offset
146
+ while @offset < @length
147
+ break if @buffer[@offset] == ','
148
+ break if @buffer[@offset] == ';'
149
+ @offset+=1
150
+ end
151
+
152
+ bit_map_length = @offset - beginning
153
+ restore_buffer = Base64.strict_decode64(@buffer[beginning, bit_map_length])
154
+
155
+ @offset+=1
156
+ restore_buffer
157
+ end
158
+
159
+
160
+ def get_truncated_response
161
+ max = @length
162
+ @length = max if @length > 200
163
+ @buffer[0...max]
164
+ end
165
+
166
+
167
+ end # class
168
+
169
+ end # module