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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +0 -0
- data/LICENSE +203 -0
- data/README.md +123 -0
- data/lib/aerospike.rb +69 -0
- data/lib/aerospike/aerospike_exception.rb +111 -0
- data/lib/aerospike/bin.rb +46 -0
- data/lib/aerospike/client.rb +649 -0
- data/lib/aerospike/cluster/cluster.rb +537 -0
- data/lib/aerospike/cluster/connection.rb +113 -0
- data/lib/aerospike/cluster/node.rb +248 -0
- data/lib/aerospike/cluster/node_validator.rb +85 -0
- data/lib/aerospike/cluster/partition.rb +54 -0
- data/lib/aerospike/cluster/partition_tokenizer_new.rb +128 -0
- data/lib/aerospike/cluster/partition_tokenizer_old.rb +135 -0
- data/lib/aerospike/command/batch_command.rb +120 -0
- data/lib/aerospike/command/batch_command_exists.rb +93 -0
- data/lib/aerospike/command/batch_command_get.rb +150 -0
- data/lib/aerospike/command/batch_item.rb +69 -0
- data/lib/aerospike/command/batch_node.rb +82 -0
- data/lib/aerospike/command/command.rb +680 -0
- data/lib/aerospike/command/delete_command.rb +57 -0
- data/lib/aerospike/command/execute_command.rb +42 -0
- data/lib/aerospike/command/exists_command.rb +57 -0
- data/lib/aerospike/command/field_type.rb +44 -0
- data/lib/aerospike/command/operate_command.rb +37 -0
- data/lib/aerospike/command/read_command.rb +174 -0
- data/lib/aerospike/command/read_header_command.rb +63 -0
- data/lib/aerospike/command/single_command.rb +60 -0
- data/lib/aerospike/command/touch_command.rb +50 -0
- data/lib/aerospike/command/write_command.rb +60 -0
- data/lib/aerospike/host.rb +43 -0
- data/lib/aerospike/info.rb +96 -0
- data/lib/aerospike/key.rb +99 -0
- data/lib/aerospike/language.rb +25 -0
- data/lib/aerospike/ldt/large.rb +69 -0
- data/lib/aerospike/ldt/large_list.rb +100 -0
- data/lib/aerospike/ldt/large_map.rb +82 -0
- data/lib/aerospike/ldt/large_set.rb +78 -0
- data/lib/aerospike/ldt/large_stack.rb +72 -0
- data/lib/aerospike/loggable.rb +55 -0
- data/lib/aerospike/operation.rb +70 -0
- data/lib/aerospike/policy/client_policy.rb +37 -0
- data/lib/aerospike/policy/generation_policy.rb +37 -0
- data/lib/aerospike/policy/policy.rb +54 -0
- data/lib/aerospike/policy/priority.rb +34 -0
- data/lib/aerospike/policy/record_exists_action.rb +45 -0
- data/lib/aerospike/policy/write_policy.rb +61 -0
- data/lib/aerospike/record.rb +42 -0
- data/lib/aerospike/result_code.rb +353 -0
- data/lib/aerospike/task/index_task.rb +59 -0
- data/lib/aerospike/task/task.rb +71 -0
- data/lib/aerospike/task/udf_register_task.rb +55 -0
- data/lib/aerospike/task/udf_remove_task.rb +55 -0
- data/lib/aerospike/udf.rb +24 -0
- data/lib/aerospike/utils/buffer.rb +139 -0
- data/lib/aerospike/utils/epoc.rb +28 -0
- data/lib/aerospike/utils/pool.rb +65 -0
- data/lib/aerospike/value/particle_type.rb +45 -0
- data/lib/aerospike/value/value.rb +380 -0
- data/lib/aerospike/version.rb +4 -0
- 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
|