aerospike 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +0 -0
  3. data/LICENSE +203 -0
  4. data/README.md +123 -0
  5. data/lib/aerospike.rb +69 -0
  6. data/lib/aerospike/aerospike_exception.rb +111 -0
  7. data/lib/aerospike/bin.rb +46 -0
  8. data/lib/aerospike/client.rb +649 -0
  9. data/lib/aerospike/cluster/cluster.rb +537 -0
  10. data/lib/aerospike/cluster/connection.rb +113 -0
  11. data/lib/aerospike/cluster/node.rb +248 -0
  12. data/lib/aerospike/cluster/node_validator.rb +85 -0
  13. data/lib/aerospike/cluster/partition.rb +54 -0
  14. data/lib/aerospike/cluster/partition_tokenizer_new.rb +128 -0
  15. data/lib/aerospike/cluster/partition_tokenizer_old.rb +135 -0
  16. data/lib/aerospike/command/batch_command.rb +120 -0
  17. data/lib/aerospike/command/batch_command_exists.rb +93 -0
  18. data/lib/aerospike/command/batch_command_get.rb +150 -0
  19. data/lib/aerospike/command/batch_item.rb +69 -0
  20. data/lib/aerospike/command/batch_node.rb +82 -0
  21. data/lib/aerospike/command/command.rb +680 -0
  22. data/lib/aerospike/command/delete_command.rb +57 -0
  23. data/lib/aerospike/command/execute_command.rb +42 -0
  24. data/lib/aerospike/command/exists_command.rb +57 -0
  25. data/lib/aerospike/command/field_type.rb +44 -0
  26. data/lib/aerospike/command/operate_command.rb +37 -0
  27. data/lib/aerospike/command/read_command.rb +174 -0
  28. data/lib/aerospike/command/read_header_command.rb +63 -0
  29. data/lib/aerospike/command/single_command.rb +60 -0
  30. data/lib/aerospike/command/touch_command.rb +50 -0
  31. data/lib/aerospike/command/write_command.rb +60 -0
  32. data/lib/aerospike/host.rb +43 -0
  33. data/lib/aerospike/info.rb +96 -0
  34. data/lib/aerospike/key.rb +99 -0
  35. data/lib/aerospike/language.rb +25 -0
  36. data/lib/aerospike/ldt/large.rb +69 -0
  37. data/lib/aerospike/ldt/large_list.rb +100 -0
  38. data/lib/aerospike/ldt/large_map.rb +82 -0
  39. data/lib/aerospike/ldt/large_set.rb +78 -0
  40. data/lib/aerospike/ldt/large_stack.rb +72 -0
  41. data/lib/aerospike/loggable.rb +55 -0
  42. data/lib/aerospike/operation.rb +70 -0
  43. data/lib/aerospike/policy/client_policy.rb +37 -0
  44. data/lib/aerospike/policy/generation_policy.rb +37 -0
  45. data/lib/aerospike/policy/policy.rb +54 -0
  46. data/lib/aerospike/policy/priority.rb +34 -0
  47. data/lib/aerospike/policy/record_exists_action.rb +45 -0
  48. data/lib/aerospike/policy/write_policy.rb +61 -0
  49. data/lib/aerospike/record.rb +42 -0
  50. data/lib/aerospike/result_code.rb +353 -0
  51. data/lib/aerospike/task/index_task.rb +59 -0
  52. data/lib/aerospike/task/task.rb +71 -0
  53. data/lib/aerospike/task/udf_register_task.rb +55 -0
  54. data/lib/aerospike/task/udf_remove_task.rb +55 -0
  55. data/lib/aerospike/udf.rb +24 -0
  56. data/lib/aerospike/utils/buffer.rb +139 -0
  57. data/lib/aerospike/utils/epoc.rb +28 -0
  58. data/lib/aerospike/utils/pool.rb +65 -0
  59. data/lib/aerospike/value/particle_type.rb +45 -0
  60. data/lib/aerospike/value/value.rb +380 -0
  61. data/lib/aerospike/version.rb +4 -0
  62. metadata +132 -0
@@ -0,0 +1,46 @@
1
+ # encoding: utf-8
2
+ # Copyright 2014 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
+ module Aerospike
18
+
19
+ class Bin
20
+
21
+ attr_accessor :name
22
+
23
+ def initialize(bin_name, bin_value)
24
+ @name = bin_name
25
+ @value = Value.of(bin_value)
26
+ end
27
+
28
+ def value=(val)
29
+ @value = Value.of(val)
30
+ end
31
+
32
+ def value
33
+ @value.get if @value
34
+ end
35
+
36
+ def value_object
37
+ @value
38
+ end
39
+
40
+ def to_s
41
+ "#{@name}:#{@value.to_s}"
42
+ end
43
+
44
+ end # class
45
+
46
+ end # module
@@ -0,0 +1,649 @@
1
+ # encoding: utf-8
2
+ # Copyright 2014 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
+ require 'digest'
18
+ require 'base64'
19
+
20
+ module Aerospike
21
+
22
+ ##
23
+ # Client class manages the Aerospike cluster nodes under the hood, and
24
+ # provides methods to access the database.
25
+ #
26
+ # All internal code is thread-safe, and can be used from multiple threads
27
+ # without any need for synchronization.
28
+
29
+ # Examples:
30
+ #
31
+ # # connect to the database
32
+ # client = Client.new('192.168.0.1', 3000)
33
+ #
34
+ # #=> raises Aerospike::Exceptions::Timeout if a +:timeout+ is specified and
35
+ # +:fail_if_not_connected+ set to true
36
+
37
+ class Client
38
+
39
+ attr_accessor :default_policy, :default_write_policy
40
+
41
+ def initialize(host, port, options={})
42
+ @default_policy = Policy.new
43
+ @default_write_policy = WritePolicy.new
44
+
45
+ policy = opt_to_client_policy(options)
46
+
47
+ @cluster = Cluster.new(policy, Host.new(host, port))
48
+
49
+ self
50
+ end
51
+
52
+ ##
53
+ # Closes all client connections to database server nodes.
54
+
55
+ def close
56
+ @cluster.close
57
+ end
58
+
59
+ ##
60
+ # Determines if there are active connections to the database server cluster.
61
+ # Returns +true+ if connections exist.
62
+
63
+ def connected?
64
+ @cluster.connected?
65
+ end
66
+
67
+ ##
68
+ # Returns array of active server nodes in the cluster.
69
+
70
+ def nodes
71
+ @cluster.nodes
72
+ end
73
+
74
+ ##
75
+ # Returns list of active server node names in the cluster.
76
+
77
+ def node_names
78
+ @cluster.nodes.map do |node|
79
+ node.get_name
80
+ end
81
+ end
82
+
83
+ #-------------------------------------------------------
84
+ # Write Record Operations
85
+ #-------------------------------------------------------
86
+
87
+ ##
88
+ # Writes record bin(s).
89
+ # The policy options specifiy the transaction timeout, record expiration
90
+ # and how the transaction is handled when the record already exists.
91
+ #
92
+ # If no policy options are provided, +@default_write_policy+ will be used.
93
+
94
+ # Examples:
95
+ #
96
+ # client.put key, {'bin', 'value string'}, :timeout => 0.001
97
+
98
+ def put(key, bins, options={})
99
+ policy = opt_to_write_policy(options)
100
+ command = WriteCommand.new(@cluster, policy, key, hash_to_bins(bins), Aerospike::Operation::WRITE)
101
+ command.execute
102
+ end
103
+
104
+ #-------------------------------------------------------
105
+ # Operations string
106
+ #-------------------------------------------------------
107
+
108
+ ##
109
+ # Appends bin values string to existing record bin values.
110
+ # The policy specifies the transaction timeout, record expiration and
111
+ # how the transaction is handled when the record already exists.
112
+ #
113
+ # This call only works for string values.
114
+ #
115
+ # If no policy options are provided, +@default_write_policy+ will be used.
116
+
117
+ # Examples:
118
+ #
119
+ # client.append key, {'bin', 'value to append'}, :timeout => 0.001
120
+
121
+ def append(key, bins, options={})
122
+ policy = opt_to_write_policy(options)
123
+ command = WriteCommand.new(@cluster, policy, key, hash_to_bins(bins), Aerospike::Operation::APPEND)
124
+ command.execute
125
+ end
126
+
127
+ ##
128
+ # Prepends bin values string to existing record bin values.
129
+ # The policy specifies the transaction timeout, record expiration and
130
+ # how the transaction is handled when the record already exists.
131
+ #
132
+ # This call works only for string values.
133
+ #
134
+ # If no policy options are provided, +@default_write_policy+ will be used.
135
+
136
+ # Examples:
137
+ #
138
+ # client.prepend key, {'bin', 'value to prepend'}, :timeout => 0.001
139
+
140
+ def prepend(key, bins, options={})
141
+ policy = opt_to_write_policy(options)
142
+ command = WriteCommand.new(@cluster, policy, key, hash_to_bins(bins), Aerospike::Operation::PREPEND)
143
+ command.execute
144
+ end
145
+
146
+ #-------------------------------------------------------
147
+ # Arithmetic Operations
148
+ #-------------------------------------------------------
149
+
150
+ ##
151
+ # Adds integer bin values to existing record bin values.
152
+ # The policy specifies the transaction timeout, record expiration and
153
+ # how the transaction is handled when the record already exists.
154
+ #
155
+ # This call only works for integer values.
156
+ #
157
+ # If no policy options are provided, +@default_write_policy+ will be used.
158
+
159
+ # Examples:
160
+ #
161
+ # client.add key, {'bin', -1}, :timeout => 0.001
162
+
163
+ def add(key, bins, options={})
164
+ policy = opt_to_write_policy(options)
165
+ command = WriteCommand.new(@cluster, policy, key, hash_to_bins(bins), Aerospike::Operation::ADD)
166
+ command.execute
167
+ end
168
+
169
+ #-------------------------------------------------------
170
+ # Delete Operations
171
+ #-------------------------------------------------------
172
+
173
+ ##
174
+ # Deletes record for specified key.
175
+ #
176
+ # The policy specifies the transaction timeout.
177
+ #
178
+ # If no policy options are provided, +@default_write_policy+ will be used.
179
+ # Returns +true+ if a record with corresponding +key+ existed.
180
+
181
+ # Examples:
182
+ #
183
+ # existed = client.delete key, :timeout => 0.001
184
+
185
+ def delete(key, options={})
186
+ policy = opt_to_write_policy(options)
187
+ command = DeleteCommand.new(@cluster, policy, key)
188
+ command.execute
189
+ command.existed
190
+ end
191
+
192
+ #-------------------------------------------------------
193
+ # Touch Operations
194
+ #-------------------------------------------------------
195
+
196
+ ##
197
+ # Creates record if it does not already exist. If the record exists,
198
+ # the record's time to expiration will be reset to the policy's expiration.
199
+ #
200
+ # If no policy options are provided, +@default_write_policy+ will be used.
201
+
202
+ # Examples:
203
+ #
204
+ # client.touch key, :timeout => 0.001
205
+
206
+ def touch(key, options={})
207
+ policy = opt_to_write_policy(options)
208
+ command = TouchCommand.new(@cluster, policy, key)
209
+ command.execute
210
+ end
211
+
212
+ #-------------------------------------------------------
213
+ # Existence-Check Operations
214
+ #-------------------------------------------------------
215
+
216
+ ##
217
+ # Determines if a record key exists.
218
+ # The policy can be used to specify timeouts.
219
+ def exists(key, options={})
220
+ policy = opt_to_policy(options)
221
+ command = ExistsCommand.new(@cluster, policy, key)
222
+ command.execute
223
+ command.exists
224
+ end
225
+
226
+ # Check if multiple record keys exist in one batch call.
227
+ # The returned array bool is in positional order with the original key array order.
228
+ # The policy can be used to specify timeouts.
229
+ def batch_exists(keys, options={})
230
+ policy = opt_to_policy(options)
231
+
232
+ # same array can be used without sychronization;
233
+ # when a key exists, the corresponding index will be marked true
234
+ exists_array = Array.new(keys.length)
235
+
236
+ key_map = BatchItem.generate_map(keys)
237
+
238
+ cmd_gen = Proc.new do |node, bns|
239
+ BatchCommandExists.new(node, bns, policy, key_map, exists_array)
240
+ end
241
+
242
+ batch_execute(keys, &cmd_gen)
243
+ exists_array
244
+ end
245
+
246
+ #-------------------------------------------------------
247
+ # Read Record Operations
248
+ #-------------------------------------------------------
249
+
250
+ # Read record header and bins for specified key.
251
+ # The policy can be used to specify timeouts.
252
+ def get(key, bin_names=[], options={})
253
+ policy = opt_to_policy(options)
254
+
255
+ command = ReadCommand.new(@cluster, policy, key, bin_names)
256
+ command.execute
257
+ command.record
258
+ end
259
+
260
+ # Read record generation and expiration only for specified key. Bins are not read.
261
+ # The policy can be used to specify timeouts.
262
+ def get_header(key, options={})
263
+ policy = opt_to_policy(options)
264
+ command = ReadHeaderCommand.new(@cluster, policy, key)
265
+ command.execute
266
+ command.record
267
+ end
268
+
269
+ #-------------------------------------------------------
270
+ # Batch Read Operations
271
+ #-------------------------------------------------------
272
+
273
+ # Read multiple record headers and bins for specified keys in one batch call.
274
+ # The returned records are in positional order with the original key array order.
275
+ # If a key is not found, the positional record will be nil.
276
+ # The policy can be used to specify timeouts.
277
+ def batch_get(keys, bin_names=[], options={})
278
+ policy = opt_to_policy(options)
279
+
280
+ # wait until all migrations are finished
281
+ # TODO: implement
282
+ # @cluster.WaitUntillMigrationIsFinished(policy.timeout)
283
+
284
+ # same array can be used without sychronization;
285
+ # when a key exists, the corresponding index will be set to record
286
+ records = Array.new(keys.length)
287
+
288
+ key_map = BatchItem.generate_map(keys)
289
+
290
+ cmd_gen = Proc.new do |node, bns|
291
+ BatchCommandGet.new(node, bns, policy, key_map, bin_names.uniq, records, INFO1_READ)
292
+ end
293
+
294
+ batch_execute(keys, &cmd_gen)
295
+ records
296
+ end
297
+
298
+ # Read multiple record header data for specified keys in one batch call.
299
+ # The returned records are in positional order with the original key array order.
300
+ # If a key is not found, the positional record will be nil.
301
+ # The policy can be used to specify timeouts.
302
+ def batch_get_header(keys, options={})
303
+ policy = opt_to_policy(options)
304
+
305
+ # wait until all migrations are finished
306
+ # TODO: Fix this and implement
307
+ # @cluster.WaitUntillMigrationIsFinished(policy.timeout)
308
+
309
+ # same array can be used without sychronization;
310
+ # when a key exists, the corresponding index will be set to record
311
+ records = Array.new(keys.length)
312
+
313
+ key_map = BatchItem.generate_map(keys)
314
+
315
+ cmd_gen = Proc.new do |node, bns|
316
+ BatchCommandGet.new(node, bns, policy, key_map, nil, records, INFO1_READ | INFO1_NOBINDATA)
317
+ end
318
+
319
+ batch_execute(keys, &cmd_gen)
320
+ records
321
+ end
322
+
323
+ #-------------------------------------------------------
324
+ # Generic Database Operations
325
+ #-------------------------------------------------------
326
+
327
+ # Perform multiple read/write operations on a single key in one batch call.
328
+ # An example would be to add an integer value to an existing record and then
329
+ # read the result, all in one database call.
330
+ #
331
+ # Write operations are always performed first, regardless of operation order
332
+ # relative to read operations.
333
+ def operate(key, operations, options={})
334
+ policy = opt_to_write_policy(options)
335
+
336
+ command = OperateCommand.new(@cluster, policy, key, operations)
337
+ command.execute
338
+ command.record
339
+ end
340
+
341
+ #-------------------------------------------------------------------
342
+ # Large collection functions (Supported by Aerospike 3 servers only)
343
+ #-------------------------------------------------------------------
344
+
345
+ # Initialize large list operator. This operator can be used to create and manage a list
346
+ # within a single bin.
347
+ #
348
+ # This method is only supported by Aerospike 3 servers.
349
+ def get_large_list(key, bin_name, user_module=nil, options={})
350
+ LargeList.new(self, opt_to_write_policy(options), key, bin_name, user_module)
351
+ end
352
+
353
+ # Initialize large map operator. This operator can be used to create and manage a map
354
+ # within a single bin.
355
+ #
356
+ # This method is only supported by Aerospike 3 servers.
357
+ def get_large_map(key, bin_name, user_module=nil, options={})
358
+ LargeMap.new(self, opt_to_write_policy(options), key, bin_name, user_module)
359
+ end
360
+
361
+ # Initialize large set operator. This operator can be used to create and manage a set
362
+ # within a single bin.
363
+ #
364
+ # This method is only supported by Aerospike 3 servers.
365
+ def get_large_set(key, bin_name, user_module=nil, options={})
366
+ LargeSet.new(self, opt_to_write_policy(options), key, bin_name, user_module)
367
+ end
368
+
369
+ # Initialize large stack operator. This operator can be used to create and manage a stack
370
+ # within a single bin.
371
+ #
372
+ # This method is only supported by Aerospike 3 servers.
373
+ def get_large_stack(key, bin_name, user_module=nil, options={})
374
+ LargeStack.new(self, opt_to_write_policy(options), key, bin_name, user_module)
375
+ end
376
+
377
+ #---------------------------------------------------------------
378
+ # User defined functions (Supported by Aerospike 3 servers only)
379
+ #---------------------------------------------------------------
380
+
381
+ # Register package containing user defined functions with server.
382
+ # This asynchronous server call will return before command is complete.
383
+ # The user can optionally wait for command completion by using the returned
384
+ # RegisterTask instance.
385
+ #
386
+ # This method is only supported by Aerospike 3 servers.
387
+ def register_udf_from_file(client_path, server_path, language, options={})
388
+ udf_body = File.read(client_path)
389
+ register_udf(udf_body, server_path, language, options={})
390
+ end
391
+
392
+ # Register package containing user defined functions with server.
393
+ # This asynchronous server call will return before command is complete.
394
+ # The user can optionally wait for command completion by using the returned
395
+ # RegisterTask instance.
396
+ #
397
+ # This method is only supported by Aerospike 3 servers.
398
+ def register_udf(udf_body, server_path, language, options={})
399
+ content = Base64.strict_encode64(udf_body).force_encoding('binary')
400
+
401
+ str_cmd = "udf-put:filename=#{server_path};content=#{content};"
402
+ str_cmd << "content-len=#{content.length};udf-type=#{language};"
403
+ # Send UDF to one node. That node will distribute the UDF to other nodes.
404
+ response_map = @cluster.request_info(@default_policy, str_cmd)
405
+ response, _ = response_map.first
406
+
407
+ res = {}
408
+ vals = response.split(';')
409
+ vals.each do |pair|
410
+ k, v = pair.split("=", 2)
411
+ res[k] = v
412
+ end
413
+
414
+ if res['error']
415
+ raise Aerospike::Exceptions::CommandRejected.new("Registration failed: #{res['error']}\nFile: #{res['file']}\nLine: #{res['line']}\nMessage: #{res['message']}")
416
+ end
417
+
418
+ UdfRegisterTask.new(@cluster, server_path)
419
+ end
420
+
421
+ # RemoveUDF removes a package containing user defined functions in the server.
422
+ # This asynchronous server call will return before command is complete.
423
+ # The user can optionally wait for command completion by using the returned
424
+ # RemoveTask instance.
425
+ #
426
+ # This method is only supported by Aerospike 3 servers.
427
+ def remove_udf(udf_name, options={})
428
+ str_cmd = "udf-remove:filename=#{udf_name};"
429
+
430
+ # Send command to one node. That node will distribute it to other nodes.
431
+ # Send UDF to one node. That node will distribute the UDF to other nodes.
432
+ response_map = @cluster.request_info(@default_policy, str_cmd)
433
+ _, response = response_map.first
434
+
435
+ if response == 'ok'
436
+ UdfRemoveTask.new(@cluster, udf_name)
437
+ else
438
+ raise Aerospike::Exceptions::Aerospike.new(Aerospike::ResultCode::SERVER_ERROR, response)
439
+ end
440
+ end
441
+
442
+ # ListUDF lists all packages containing user defined functions in the server.
443
+ # This method is only supported by Aerospike 3 servers.
444
+ def list_udf(options={})
445
+ str_cmd = 'udf-list'
446
+
447
+ # Send command to one node. That node will distribute it to other nodes.
448
+ response_map = @cluster.request_info(@default_policy, str_cmd)
449
+ _, response = response_map.first
450
+
451
+ vals = response.split(';')
452
+
453
+ vals.map do |udf_info|
454
+ next if udf_info.strip! == ''
455
+
456
+ udf_parts = udf_info.split(',')
457
+ udf = UDF.new
458
+ udf_parts.each do |values|
459
+ k, v = values.split('=', 2)
460
+ case k
461
+ when 'filename'
462
+ udf.filename = v
463
+ when 'hash'
464
+ udf.hash = v
465
+ when 'type'
466
+ udf.language = v
467
+ end
468
+ end
469
+ udf
470
+ end
471
+ end
472
+
473
+ # Execute user defined function on server and return results.
474
+ # The function operates on a single record.
475
+ # The package name is used to locate the udf file location:
476
+ #
477
+ # udf file = <server udf dir>/<package name>.lua
478
+ #
479
+ # This method is only supported by Aerospike 3 servers.
480
+ def execute_udf(key, package_name, function_name, args=[], options={})
481
+ policy = opt_to_write_policy(options)
482
+
483
+ command = ExecuteCommand.new(@cluster, policy, key, package_name, function_name, args)
484
+ command.execute
485
+
486
+ record = command.record
487
+
488
+ return nil if !record || record.bins.length == 0
489
+
490
+ result_map = record.bins
491
+
492
+ # User defined functions don't have to return a value.
493
+ key, obj = result_map.detect{|k, v| k.include?('SUCCESS')}
494
+ if key
495
+ return obj
496
+ end
497
+
498
+ key, obj = result_map.detect{|k, v| k.include?('FAILURE')}
499
+ if key
500
+ raise Aerospike::Exceptions::Aerospike.new(UDF_BAD_RESPONSE, "#{obj}")
501
+ end
502
+
503
+ raise Aerospike::Exception::Aerospike.new(UDF_BAD_RESPONSE, "Invalid UDF return value")
504
+ end
505
+
506
+ # Create secondary index.
507
+ # This asynchronous server call will return before command is complete.
508
+ # The user can optionally wait for command completion by using the returned
509
+ # IndexTask instance.
510
+ #
511
+ # This method is only supported by Aerospike 3 servers.
512
+ # index_type should be between :string or :numeric
513
+ def create_index(namespace, set_name, index_name, bin_name, index_type, options={})
514
+ policy = opt_to_write_policy(options)
515
+ str_cmd = "sindex-create:ns=#{namespace}"
516
+ str_cmd << ";set=#{set_name}" unless set_name.to_s.strip.empty?
517
+ str_cmd << ";indexname=#{index_name};numbins=1;indexdata=#{bin_name},#{index_type.to_s.upcase}"
518
+ str_cmd << ";priority=normal"
519
+
520
+ # Send index command to one node. That node will distribute the command to other nodes.
521
+ response_map = send_info_command(policy, str_cmd)
522
+ _, response = response_map.first
523
+ response = response.to_s.upcase
524
+
525
+ if response == 'OK'
526
+ # Return task that could optionally be polled for completion.
527
+ return IndexTask.new(@cluster, namespace, index_name)
528
+ end
529
+
530
+ if response.start_with?('FAIL:200')
531
+ # Index has already been created. Do not need to poll for completion.
532
+ return IndexTask.new(@cluster, namespace, index_name, true)
533
+ end
534
+
535
+ raise Aerospike::Exceptions::Aerospike.new(Aerospike::ResultCode::INDEX_GENERIC, "Create index failed: #{response}")
536
+ end
537
+
538
+ # Delete secondary index.
539
+ # This method is only supported by Aerospike 3 servers.
540
+ def drop_index(namespace, set_name, index_name, options={})
541
+ policy = opt_to_write_policy(options)
542
+ str_cmd = "sindex-delete:ns=#{namespace}"
543
+ str_cmd << ";set=#{set_name}" unless set_name.to_s.strip.empty?
544
+ str_cmd << ";indexname=#{index_name}"
545
+
546
+ # Send index command to one node. That node will distribute the command to other nodes.
547
+ response_map = send_info_command(policy, str_cmd)
548
+ _, response = response_map.first
549
+ response = response.to_s.upcase
550
+
551
+ return if response == 'OK'
552
+
553
+ # Index did not previously exist. Return without error.
554
+ return if response.start_with?('FAIL:201')
555
+
556
+ raise Aerospike::Exceptions::Aerospike.new(Aerospike::ResultCode::INDEX_GENERICINDEX_GENERIC, "Drop index failed: #{response}")
557
+ end
558
+
559
+ def request_info(*commands)
560
+ @cluster.request_info(@default_policy, *commands)
561
+ end
562
+
563
+ private
564
+
565
+ def send_info_command(policy, command)
566
+ @cluster.request_info(@default_policy, command)
567
+ end
568
+
569
+ def hash_to_bins(hash)
570
+ if hash.is_a?(Bin)
571
+ [hash]
572
+ elsif hash.is_a?(Array)
573
+ hash # it is a list of bins
574
+ else
575
+ hash.map do |k, v|
576
+ raise Aerospike::Exceptions::Parse("bin name `#{k}` is not a string.") unless k.is_a?(String)
577
+ Bin.new(k, v)
578
+ end
579
+ end
580
+ end
581
+
582
+ def opt_to_client_policy(options)
583
+ if options == {} || options.nil?
584
+ ClientPolicy.new
585
+ elsif options.is_a?(ClientPolicy)
586
+ options
587
+ elsif options.is_a?(Hash)
588
+ ClientPolicy.new(
589
+ options[:timeout],
590
+ options[:connection_queue_size],
591
+ options[:fail_if_not_connected],
592
+ )
593
+ end
594
+ end
595
+
596
+ def opt_to_policy(options)
597
+ if options == {} || options.nil?
598
+ @default_policy
599
+ elsif options.is_a?(Policy)
600
+ options
601
+ elsif options.is_a?(Hash)
602
+ Policy.new(
603
+ options[:priority],
604
+ options[:timeout],
605
+ options[:max_retiries],
606
+ options[:sleep_between_retries],
607
+ )
608
+ end
609
+ end
610
+
611
+ def opt_to_write_policy(options)
612
+ if options == {} || options.nil?
613
+ @default_write_policy
614
+ elsif options.is_a?(WritePolicy)
615
+ options
616
+ elsif options.is_a?(Hash)
617
+ WritePolicy.new(
618
+ options[:record_exists_action],
619
+ options[:gen_policy],
620
+ options[:generation],
621
+ options[:expiration],
622
+ options[:send_key]
623
+ )
624
+ end
625
+ end
626
+
627
+ def batch_execute(keys, &cmd_gen)
628
+ batch_nodes = BatchNode.generate_list(@cluster, keys)
629
+ threads = []
630
+
631
+ # Use a thread per namespace per node
632
+ batch_nodes.each do |batch_node|
633
+ # copy to avoid race condition
634
+ bn = batch_node
635
+ bn.batch_namespaces.each do |bns|
636
+ threads << Thread.new do
637
+ abort_on_exception=true
638
+ command = cmd_gen.call(bn.node, bns)
639
+ command.execute
640
+ end
641
+ end
642
+ end
643
+
644
+ threads.each { |thr| thr.join }
645
+ end
646
+
647
+ end # class
648
+
649
+ end #module