aerospike 0.1.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 (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