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,150 @@
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 'aerospike/command/batch_command'
18
+
19
+ module Aerospike
20
+
21
+ private
22
+
23
+ class BatchCommandGet < BatchCommand
24
+
25
+ def initialize(node, batch_namespace, policy, key_map, bin_names, records, read_attr)
26
+ super(node)
27
+
28
+ @batch_namespace = batch_namespace
29
+ @policy = policy
30
+ @key_map = key_map
31
+ @bin_names = bin_names
32
+ @records = records
33
+ @read_attr = read_attr
34
+ end
35
+
36
+ def write_buffer
37
+ set_batch_get(@batch_namespace, @bin_names, @read_attr)
38
+ end
39
+
40
+ # Parse all results in the batch. Add records to shared list.
41
+ # If the record was not found, the bins will be nil.
42
+ def parse_record_results(receive_size)
43
+ #Parse each message response and add it to the result array
44
+ @data_offset = 0
45
+
46
+ while @data_offset < receive_size
47
+ read_bytes(MSG_REMAINING_HEADER_SIZE)
48
+ result_code = @data_buffer.read(5).ord & 0xFF
49
+
50
+ # The only valid server return codes are "ok" and "not found".
51
+ # If other return codes are received, then abort the batch.
52
+ if result_code != 0 && result_code != Aerospike::ResultCode::KEY_NOT_FOUND_ERROR
53
+ raise Aerospike::Exceptions::Aerospike.new(result_code)
54
+ end
55
+
56
+ info3 = @data_buffer.read(3).ord
57
+
58
+ # If cmd is the end marker of the response, do not proceed further
59
+ return false if (info3 & INFO3_LAST) == INFO3_LAST
60
+
61
+ generation = @data_buffer.read_int32(6).ord
62
+ expiration = @data_buffer.read_int32(10).ord
63
+ field_count = @data_buffer.read_int16(18).ord
64
+ op_count = @data_buffer.read_int16(20).ord
65
+ key = parse_key(field_count)
66
+ item = @key_map[key.digest]
67
+
68
+ if item
69
+ if result_code == 0
70
+ index = item.index
71
+ @records[index] = parse_record(key, op_count, generation, expiration)
72
+ end
73
+ else
74
+ Aerospike.logger.debug("Unexpected batch key returned: #{key.namespace}, #{key.digest}")
75
+ end
76
+
77
+ end # while
78
+
79
+ true
80
+ end
81
+
82
+ # Parses the given byte buffer and populate the result object.
83
+ # Returns the number of bytes that were parsed from the given buffer.
84
+ def parse_record(key, op_count, generation, expiration)
85
+ bins = nil
86
+ duplicates = nil
87
+
88
+ for i in 0...op_count
89
+ raise Aerospike::Exceptions::QueryTerminated.new unless valid?
90
+
91
+ read_bytes(8)
92
+
93
+ op_size = @data_buffer.read_int32(0).ord
94
+ particle_type = @data_buffer.read(5).ord
95
+ version = @data_buffer.read(6).ord
96
+ name_size = @data_buffer.read(7).ord
97
+
98
+ read_bytes(name_size)
99
+ name = @data_buffer.read(0, name_size).force_encoding('utf-8')
100
+
101
+ particle_bytes_size = op_size - (4 + name_size)
102
+ read_bytes(particle_bytes_size)
103
+ value = Aerospike.bytes_to_particle(particle_type, @data_buffer, 0, particle_bytes_size)
104
+
105
+ # Currently, the batch command returns all the bins even if a subset of
106
+ # the bins are requested. We have to filter it on the client side.
107
+ # TODO: Filter batch bins on server!
108
+ if !@bin_names || @bin_names.any?{|bn| bn == name}
109
+ vmap = nil
110
+
111
+ if version > 0 || duplicates
112
+ unless duplicates
113
+ duplicates = []
114
+ duplicates << bins
115
+ bins = nil
116
+
117
+ for j in 0...version
118
+ duplicates << nil
119
+ end
120
+ else
121
+ for j in duplicates.length..version
122
+ duplicates << nil
123
+ end
124
+ end
125
+
126
+ vmap = duplicates[version]
127
+ unless vmap
128
+ vmap = {}
129
+ duplicates[version] = vmap
130
+ end
131
+ else
132
+ unless bins
133
+ bins = {}
134
+ end
135
+ vmap = bins
136
+ end
137
+ vmap[name] = value
138
+ end
139
+ end
140
+
141
+ # Remove nil duplicates just in case there were holes in the version number space.
142
+ # TODO: this seems to be a bad idea; O(n) algorithm after another O(n) algorithm
143
+ duplicates.compact! if duplicates
144
+
145
+ Record.new(@node, key, bins, duplicates, generation, expiration)
146
+ end
147
+
148
+ end # class
149
+
150
+ end # module
@@ -0,0 +1,69 @@
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 'thread'
18
+
19
+ require 'aerospike/record'
20
+
21
+ require 'aerospike/command/command'
22
+
23
+ module Aerospike
24
+
25
+ private
26
+
27
+ class BatchItem
28
+
29
+ def self.generate_map(keys)
30
+ key_map = {}
31
+ keys.each_with_index do |key, i|
32
+ item = key_map[key.digest]
33
+ unless item
34
+ item = BatchItem.new(i)
35
+ key_map[key.digest] = item
36
+ else
37
+ item.add_duplicate(i)
38
+ end
39
+ end
40
+
41
+ key_map
42
+ end
43
+
44
+
45
+ def initialize(index)
46
+ @index = index
47
+ end
48
+
49
+ def add_duplicate(idx)
50
+ unless @duplicates
51
+ @duplicates = []
52
+ @duplicates << @index
53
+ @index = 0
54
+ end
55
+
56
+ @duplicates << idx
57
+ end
58
+
59
+ def index
60
+ return @index unless @duplicates
61
+
62
+ r = @duplicates[@index]
63
+ @index+=1
64
+ return r
65
+ end
66
+
67
+ end # class
68
+
69
+ end # module
@@ -0,0 +1,82 @@
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 'thread'
18
+
19
+ require 'aerospike/record'
20
+
21
+ require 'aerospike/command/command'
22
+
23
+ module Aerospike
24
+
25
+ private
26
+
27
+ BatchNamespace = Struct.new :namespace, :keys
28
+
29
+ class BatchNode
30
+
31
+ attr_accessor :node, :batch_namespaces, :key_capacity
32
+
33
+ def self.generate_list(cluster, keys)
34
+ nodes = cluster.nodes
35
+
36
+ if nodes.length == 0
37
+ raise Aerospike::Exceptions::Connection.new("command failed because cluster is empty.")
38
+ end
39
+
40
+ node_count = nodes.length
41
+ keys_per_node = (keys.length/node_count).to_i + 10
42
+
43
+ # Split keys by server node.
44
+ batch_nodes = []
45
+
46
+ keys.each do |key|
47
+ partition = Partition.new_by_key(key)
48
+
49
+ # error not required
50
+ node = cluster.get_node(partition)
51
+ batch_node = batch_nodes.detect{|bn| bn.node == node}
52
+
53
+ unless batch_node
54
+ batch_nodes << BatchNode.new(node, keys_per_node, key)
55
+ else
56
+ batch_node.add_key(key)
57
+ end
58
+ end
59
+
60
+ batch_nodes
61
+ end
62
+
63
+
64
+ def initialize(node, key_capacity, key)
65
+ @node = node
66
+ @key_capacity = key_capacity
67
+ @batch_namespaces = [BatchNamespace.new(key.namespace, [key])]
68
+ end
69
+
70
+ def add_key(key)
71
+ batch_namespace = @batch_namespaces.detect{|bn| bn.namespace == key.namespace }
72
+
73
+ unless batch_namespace
74
+ @batch_namespaces << BatchNamespace.new(key.namespace, [key])
75
+ else
76
+ batch_namespace.keys << key
77
+ end
78
+ end
79
+
80
+ end # class
81
+
82
+ end # module
@@ -0,0 +1,680 @@
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 'time'
18
+
19
+
20
+ require 'msgpack'
21
+ require 'aerospike/result_code'
22
+ require 'aerospike/command/field_type'
23
+
24
+ module Aerospike
25
+
26
+ private
27
+
28
+ # Flags commented out are not supported by cmd client.
29
+ # Contains a read operation.
30
+ INFO1_READ = Integer(1 << 0)
31
+ # Get all bins.
32
+ INFO1_GET_ALL = Integer(1 << 1)
33
+
34
+ # Do not read the bins
35
+ INFO1_NOBINDATA = Integer(1 << 5)
36
+
37
+ # Create or update record
38
+ INFO2_WRITE = Integer(1 << 0)
39
+ # Fling a record into the belly of Moloch.
40
+ INFO2_DELETE = Integer(1 << 1)
41
+ # Update if expected generation == old.
42
+ INFO2_GENERATION = Integer(1 << 2)
43
+ # Update if new generation >= old, good for restore.
44
+ INFO2_GENERATION_GT = Integer(1 << 3)
45
+ # Create a duplicate on a generation collision.
46
+ INFO2_GENERATION_DUP = Integer(1 << 4)
47
+ # Create only. Fail if record already exists.
48
+ INFO2_CREATE_ONLY = Integer(1 << 5)
49
+
50
+ # This is the last of a multi-part message.
51
+ INFO3_LAST = Integer(1 << 0)
52
+ # Update only. Merge bins.
53
+ INFO3_UPDATE_ONLY = Integer(1 << 3)
54
+
55
+ # Create or completely replace record.
56
+ INFO3_CREATE_OR_REPLACE = Integer(1 << 4)
57
+ # Completely replace existing record only.
58
+ INFO3_REPLACE_ONLY = Integer(1 << 5)
59
+
60
+ MSG_TOTAL_HEADER_SIZE = 30
61
+ FIELD_HEADER_SIZE = 5
62
+ OPERATION_HEADER_SIZE = 8
63
+ MSG_REMAINING_HEADER_SIZE = 22
64
+ DIGEST_SIZE = 20
65
+ CL_MSG_VERSION = 2
66
+ AS_MSG_TYPE = 3
67
+
68
+ class Command
69
+
70
+ def initialize(node)
71
+ @node = node
72
+
73
+ self
74
+ end
75
+
76
+ # Writes the command for write operations
77
+ def set_write(policy, operation, key, bins)
78
+ begin_cmd
79
+ field_count = estimate_key_size(key)
80
+
81
+ if policy.send_key
82
+ # field header size + key size
83
+ @data_offset += key.user_key_as_value.estimate_size + FIELD_HEADER_SIZE
84
+ field_count += 1
85
+ end
86
+
87
+ bins.each do |bin|
88
+ estimate_operation_size_for_bin(bin)
89
+ end
90
+
91
+ size_buffer
92
+
93
+ write_header_with_policy(policy, 0, INFO2_WRITE, field_count, bins.length)
94
+ write_key(key)
95
+
96
+ if policy.send_key
97
+ write_field_value(key.user_key_as_value, Aerospike::FieldType::KEY)
98
+ end
99
+
100
+ bins.each do |bin|
101
+ write_operation_for_bin(bin, operation)
102
+ end
103
+
104
+ end_cmd
105
+ end
106
+
107
+ # Writes the command for delete operations
108
+ def set_delete(policy, key)
109
+ begin_cmd
110
+ field_count = estimate_key_size(key)
111
+ size_buffer
112
+ write_header_with_policy(policy, 0, INFO2_WRITE|INFO2_DELETE, field_count, 0)
113
+ write_key(key)
114
+ end_cmd
115
+ end
116
+
117
+ # Writes the command for touch operations
118
+ def set_touch(policy, key)
119
+ begin_cmd
120
+ field_count = estimate_key_size(key)
121
+ estimate_operation_size
122
+ size_buffer
123
+ write_header_with_policy(policy, 0, INFO2_WRITE, field_count, 1)
124
+ write_key(key)
125
+ write_operation_for_operation_type(Aerospike::Operation::TOUCH)
126
+ end_cmd
127
+ end
128
+
129
+ # Writes the command for exist operations
130
+ def set_exists(key)
131
+ begin_cmd
132
+ field_count = estimate_key_size(key)
133
+ size_buffer
134
+ write_header(INFO1_READ|INFO1_NOBINDATA, 0, field_count, 0)
135
+ write_key(key)
136
+ end_cmd
137
+ end
138
+
139
+ # Writes the command for get operations (all bins)
140
+ def set_read_for_key_only(key)
141
+ begin_cmd
142
+ field_count = estimate_key_size(key)
143
+ size_buffer
144
+ write_header(INFO1_READ|INFO1_GET_ALL, 0, field_count, 0)
145
+ write_key(key)
146
+ end_cmd
147
+ end
148
+
149
+ # Writes the command for get operations (specified bins)
150
+ def set_read(key, bin_names)
151
+ if bin_names && bin_names.length > 0
152
+ begin_cmd
153
+ field_count = estimate_key_size(key)
154
+
155
+ bin_names.each do |bin_name|
156
+ estimate_operation_size_for_bin_name(bin_name)
157
+ end
158
+
159
+ size_buffer
160
+ write_header(INFO1_READ, 0, field_count, bin_names.length)
161
+ write_key(key)
162
+
163
+ bin_names.each do |bin_name|
164
+ write_operation_for_bin_name(bin_name, Aerospike::Operation::READ)
165
+ end
166
+
167
+ end_cmd
168
+ else
169
+ set_read_for_key_only(key)
170
+ end
171
+ end
172
+
173
+ # Writes the command for getting metadata operations
174
+ def set_read_header(key)
175
+ begin_cmd
176
+ field_count = estimate_key_size(key)
177
+ estimate_operation_size_for_bin_name('')
178
+ size_buffer
179
+
180
+ # The server does not currently return record header data with _INFO1_NOBINDATA attribute set.
181
+ # The workaround is to request a non-existent bin.
182
+ # TODO: Fix this on server.
183
+ #command.set_read(INFO1_READ | _INFO1_NOBINDATA);
184
+ write_header(INFO1_READ, 0, field_count, 1)
185
+
186
+ write_key(key)
187
+ write_operation_for_bin_name('', Aerospike::Operation::READ)
188
+ end_cmd
189
+ end
190
+
191
+ # Implements different command operations
192
+ def set_operate(policy, key, operations)
193
+ begin_cmd
194
+ field_count = estimate_key_size(key)
195
+ read_attr = 0
196
+ write_attr = 0
197
+ read_header = false
198
+
199
+ operations.each do |operation|
200
+ case operation.op_type
201
+ when Aerospike::Operation::READ
202
+ read_attr |= INFO1_READ
203
+
204
+ # Read all bins if no bin is specified.
205
+ read_attr |= INFO1_GET_ALL unless operation.bin_name
206
+
207
+ when Aerospike::Operation::READ_HEADER
208
+ # The server does not currently return record header data with _INFO1_NOBINDATA attribute set.
209
+ # The workaround is to request a non-existent bin.
210
+ # TODO: Fix this on server.
211
+ # read_attr |= _INFO1_READ | _INFO1_NOBINDATA
212
+ read_attr |= INFO1_READ
213
+ read_header = true
214
+
215
+ else
216
+ write_attr = INFO2_WRITE
217
+ end
218
+
219
+ estimate_operation_size_for_operation(operation)
220
+ end
221
+ size_buffer
222
+
223
+ if write_attr != 0
224
+ write_header_with_policy(policy, read_attr, write_attr, field_count, operations.length)
225
+ else
226
+ write_header(read_attr, write_attr, field_count, operations.length)
227
+ end
228
+ write_key(key)
229
+
230
+ operations.each do |operation|
231
+ write_operation_for_operation(operation)
232
+ end
233
+
234
+ write_operation_for_bin(nil, Aerospike::Operation::READ) if read_header
235
+
236
+ end_cmd
237
+ end
238
+
239
+ def set_udf(key, package_name, function_name, args)
240
+ begin_cmd
241
+ field_count = estimate_key_size(key)
242
+ arg_bytes = args.to_bytes
243
+
244
+ field_count += estimate_udf_size(package_name, function_name, arg_bytes)
245
+ size_buffer
246
+
247
+ write_header(0, INFO2_WRITE, field_count, 0)
248
+ write_key(key)
249
+ write_field_string(package_name, Aerospike::FieldType::UDF_PACKAGE_NAME)
250
+ write_field_string(function_name, Aerospike::FieldType::UDF_FUNCTION)
251
+ write_field_bytes(arg_bytes, Aerospike::FieldType::UDF_ARGLIST)
252
+
253
+ end_cmd
254
+ end
255
+
256
+ def set_batch_exists(batch_namespace)
257
+ # Estimate buffer size
258
+ begin_cmd
259
+ keys = batch_namespace.keys
260
+ byte_size = keys.length * DIGEST_SIZE
261
+
262
+ @data_offset += (batch_namespace ? batch_namespace.namespace.bytesize : 0) +
263
+ FIELD_HEADER_SIZE + byte_size + FIELD_HEADER_SIZE
264
+
265
+ size_buffer
266
+
267
+ write_header(INFO1_READ|INFO1_NOBINDATA, 0, 2, 0)
268
+ write_field_string(batch_namespace.namespace, Aerospike::FieldType::NAMESPACE)
269
+ write_field_header(byte_size, Aerospike::FieldType::DIGEST_RIPE_ARRAY)
270
+
271
+ keys.each do |key|
272
+ @data_buffer.write_binary(key.digest, @data_offset)
273
+ @data_offset += key.digest.bytesize
274
+ end
275
+ end_cmd
276
+ end
277
+
278
+ def set_batch_get(batch_namespace, bin_names, read_attr)
279
+ # Estimate buffer size
280
+ begin_cmd
281
+ byte_size = batch_namespace.keys.length * DIGEST_SIZE
282
+
283
+ @data_offset += batch_namespace.namespace.bytesize +
284
+ FIELD_HEADER_SIZE + byte_size + FIELD_HEADER_SIZE
285
+
286
+ if bin_names
287
+ bin_names.each do |bin_name|
288
+ estimate_operation_size_for_bin_name(bin_name)
289
+ end
290
+ end
291
+
292
+ size_buffer
293
+
294
+ operation_count = 0
295
+ if bin_names
296
+ operation_count = bin_names.length
297
+ end
298
+
299
+ write_header(read_attr, 0, 2, operation_count)
300
+ write_field_string(batch_namespace.namespace, Aerospike::FieldType::NAMESPACE)
301
+ write_field_header(byte_size, Aerospike::FieldType::DIGEST_RIPE_ARRAY)
302
+
303
+ batch_namespace.keys.each do |key|
304
+ @data_buffer.write_binary(key.digest, @data_offset)
305
+ @data_offset += key.digest.bytesize
306
+ end
307
+
308
+ if bin_names
309
+ bin_names.each do |bin_name|
310
+ write_operation_for_bin_name(bin_name, Aerospike::Operation::READ)
311
+ end
312
+ end
313
+
314
+ end_cmd
315
+ end
316
+
317
+ def execute
318
+ iterations = 0
319
+
320
+ # set timeout outside the loop
321
+ limit = Time.now + @policy.timeout
322
+
323
+ # Execute command until successful, timed out or maximum iterations have been reached.
324
+ while true
325
+ # too many retries
326
+ iterations += 1
327
+ break if (@policy.max_retries > 0) && (iterations > @policy.max_retries+1)
328
+
329
+ # Sleep before trying again, after the first iteration
330
+ sleep(@policy.sleep_between_retries) if iterations > 1 && @policy.sleep_between_retries > 0
331
+
332
+ # check for command timeout
333
+ break if @policy.timeout > 0 && Time.now > limit
334
+
335
+ begin
336
+ @conn = @node.get_connection(@policy.timeout)
337
+ rescue => e
338
+ # Socket connection error has occurred. Decrease health and retry.
339
+ @node.decrease_health
340
+
341
+ Aerospike.logger.warn("Node #{@node.to_s}: #{e}")
342
+ next
343
+ end
344
+
345
+ # Draw a buffer from buffer pool, and make sure it will be put back
346
+ begin
347
+ @data_buffer = Buffer.get
348
+
349
+ # Set command buffer.
350
+ begin
351
+ write_buffer
352
+ rescue => e
353
+ # All runtime exceptions are considered fatal. Do not retry.
354
+ # Close socket to flush out possible garbage. Do not put back in pool.
355
+ @conn.close
356
+ raise e
357
+ end
358
+
359
+ # Reset timeout in send buffer (destined for server) and socket.
360
+ @data_buffer.write_int32((@policy.timeout * 1000).to_i, 22)
361
+
362
+ # Send command.
363
+ begin
364
+ @conn.write(@data_buffer, @data_offset)
365
+ rescue => e
366
+ # IO errors are considered temporary anomalies. Retry.
367
+ # Close socket to flush out possible garbage. Do not put back in pool.
368
+ @conn.close
369
+
370
+ Aerospike.logger.warn("Node #{@node.to_s}: #{e}")
371
+ # IO error means connection to server @node is unhealthy.
372
+ # Reflect cmd status.
373
+ @node.decrease_health
374
+ next
375
+ end
376
+
377
+ # Parse results.
378
+ begin
379
+ parse_result
380
+ rescue => e
381
+ # close the connection
382
+ # cancelling/closing the batch/multi commands will return an error, which will
383
+ # close the connection to throw away its data and signal the server about the
384
+ # situation. We will not put back the connection in the buffer.
385
+ @conn.close
386
+ raise e
387
+ end
388
+
389
+ # Reflect healthy status.
390
+ @node.restore_health
391
+
392
+ # Put connection back in pool.
393
+ @node.put_connection(@conn)
394
+
395
+ # command has completed successfully. Exit method.
396
+ return
397
+ ensure
398
+ Buffer.put(@data_buffer)
399
+ end
400
+
401
+ end # while
402
+
403
+ # execution timeout
404
+ raise Aerospike::Exceptions::Timeout.new(limit, iterations)
405
+ end
406
+
407
+ protected
408
+
409
+
410
+ def estimate_key_size(key)
411
+ field_count = 0
412
+
413
+ if key.namespace
414
+ @data_offset += key.namespace.length + FIELD_HEADER_SIZE
415
+ field_count += 1
416
+ end
417
+
418
+ if key.set_name
419
+ @data_offset += key.set_name.length + FIELD_HEADER_SIZE
420
+ field_count += 1
421
+ end
422
+
423
+ @data_offset += key.digest.length + FIELD_HEADER_SIZE
424
+ field_count += 1
425
+
426
+ return field_count
427
+ end
428
+
429
+ def estimate_udf_size(package_name, function_name, bytes)
430
+ @data_offset += package_name.bytesize + FIELD_HEADER_SIZE
431
+ @data_offset += function_name.bytesize + FIELD_HEADER_SIZE
432
+ @data_offset += bytes.bytesize + FIELD_HEADER_SIZE
433
+ return 3
434
+ end
435
+
436
+ def estimate_operation_size_for_bin(bin)
437
+ @data_offset += bin.name.length + OPERATION_HEADER_SIZE
438
+ @data_offset += bin.value_object.estimate_size
439
+ end
440
+
441
+ def estimate_operation_size_for_operation(operation)
442
+ bin_len = 0
443
+
444
+ if operation.bin_name
445
+ bin_len = operation.bin_name.length
446
+ end
447
+
448
+ @data_offset += bin_len + OPERATION_HEADER_SIZE
449
+
450
+ if operation.bin_value
451
+ @data_offset += operation.bin_value.estimate_size
452
+ end
453
+ end
454
+
455
+ def estimate_operation_size_for_bin_name(bin_name)
456
+ @data_offset += bin_name.length + OPERATION_HEADER_SIZE
457
+ end
458
+
459
+ def estimate_operation_size
460
+ @data_offset += OPERATION_HEADER_SIZE
461
+ end
462
+
463
+ # Generic header write.
464
+ def write_header(read_attr, write_attr, field_count, operation_count)
465
+ # Write all header data except total size which must be written last.
466
+ @data_buffer.write_byte(MSG_REMAINING_HEADER_SIZE, 8) # Message heade.length.
467
+ @data_buffer.write_byte(read_attr, 9)
468
+ @data_buffer.write_byte(write_attr, 10)
469
+
470
+ for i in 11..25
471
+ @data_buffer.write_byte(0, i)
472
+ end
473
+
474
+ @data_buffer.write_int16(field_count, 26)
475
+ @data_buffer.write_int16(operation_count, 28)
476
+
477
+ @data_offset = MSG_TOTAL_HEADER_SIZE
478
+ end
479
+
480
+ # Header write for write operations.
481
+ def write_header_with_policy(policy, read_attr, write_attr, field_count, operation_count)
482
+ # Set flags.
483
+ generation = Integer(0)
484
+ info_attr = Integer(0)
485
+
486
+ case policy.record_exists_action
487
+ when Aerospike::RecordExistsAction::UPDATE
488
+ when Aerospike::RecordExistsAction::UPDATE_ONLY
489
+ info_attr |= INFO3_UPDATE_ONLY
490
+ when Aerospike::RecordExistsAction::REPLACE
491
+ info_attr |= INFO3_CREATE_OR_REPLACE
492
+ when Aerospike::RecordExistsAction::REPLACE_ONLY
493
+ info_attr |= INFO3_REPLACE_ONLY
494
+ when Aerospike::RecordExistsAction::CREATE_ONLY
495
+ write_attr |= INFO2_CREATE_ONLY
496
+ end
497
+
498
+ case policy.generation_policy
499
+ when Aerospike::GenerationPolicy::NONE
500
+ when Aerospike::GenerationPolicy::EXPECT_GEN_EQUAL
501
+ generation = policy.generation
502
+ write_attr |= INFO2_GENERATION
503
+ when Aerospike::GenerationPolicy::EXPECT_GEN_GT
504
+ generation = policy.generation
505
+ write_attr |= INFO2_GENERATION_GT
506
+ when Aerospike::GenerationPolicy::DUPLICATE
507
+ generation = policy.generation
508
+ write_attr |= INFO2_GENERATION_DUP
509
+ end
510
+
511
+ # Write all header data except total size which must be written last.
512
+ @data_buffer.write_byte(MSG_REMAINING_HEADER_SIZE, 8) # Message heade.length.
513
+ @data_buffer.write_byte(read_attr, 9)
514
+ @data_buffer.write_byte(write_attr, 10)
515
+ @data_buffer.write_byte(info_attr, 11)
516
+ @data_buffer.write_byte(0, 12) # unused
517
+ @data_buffer.write_byte(0, 13) # clear the result code
518
+ # Buffer.Int32ToBytes(generation, @data_buffer, 14)
519
+ @data_buffer.write_int32(generation, 14)
520
+ # Buffer.Int32ToBytes(policy.expiration, @data_buffer, 18)
521
+ @data_buffer.write_int32(policy.expiration, 18)
522
+
523
+ # Initialize timeout. It will be written later.
524
+ @data_buffer.write_byte(0, 22)
525
+ @data_buffer.write_byte(0, 23)
526
+ @data_buffer.write_byte(0, 24)
527
+ @data_buffer.write_byte(0, 25)
528
+
529
+
530
+ # Buffer.Int16ToBytes(field_count, @data_buffer, 26)
531
+ @data_buffer.write_int16(field_count, 26)
532
+ # Buffer.Int16ToBytes(operation_count, @data_buffer, 28)
533
+ @data_buffer.write_int16(operation_count, 28)
534
+
535
+ @data_offset = MSG_TOTAL_HEADER_SIZE
536
+ end
537
+
538
+ def write_key(key)
539
+ # Write key into buffer.
540
+ if key.namespace
541
+ write_field_string(key.namespace, Aerospike::FieldType::NAMESPACE)
542
+ end
543
+
544
+ if key.set_name
545
+ write_field_string(key.set_name, Aerospike::FieldType::TABLE)
546
+ end
547
+
548
+ write_field_bytes(key.digest, Aerospike::FieldType::DIGEST_RIPE)
549
+ end
550
+
551
+ def write_operation_for_bin(bin, operation)
552
+ name_length = @data_buffer.write_binary(bin.name, @data_offset+OPERATION_HEADER_SIZE)
553
+ value_length = bin.value_object.write(@data_buffer, @data_offset+OPERATION_HEADER_SIZE+name_length)
554
+
555
+ # Buffer.Int32ToBytes(name_length+value_length+4, @data_buffer, @data_offset)
556
+ @data_buffer.write_int32(name_length+value_length+4, @data_offset)
557
+
558
+ @data_offset += 4
559
+ @data_buffer.write_byte(operation, @data_offset)
560
+ @data_offset += 1
561
+ @data_buffer.write_byte(bin.value_object.type, @data_offset)
562
+ @data_offset += 1
563
+ @data_buffer.write_byte(0, @data_offset)
564
+ @data_offset += 1
565
+ @data_buffer.write_byte(name_length, @data_offset)
566
+ @data_offset += 1
567
+ @data_offset += name_length + value_length
568
+ end
569
+
570
+ def write_operation_for_operation(operation)
571
+ name_length = 0
572
+ if operation.bin_name
573
+ name_length = @data_buffer.write_binary(operation.bin_name, @data_offset+OPERATION_HEADER_SIZE)
574
+ end
575
+
576
+ value_length = operation.bin_value.write(@data_buffer, @data_offset+OPERATION_HEADER_SIZE+name_length)
577
+
578
+ # Buffer.Int32ToBytes(name_length+value_length+4, @data_buffer, @data_offset)
579
+ @data_buffer.write_int32(name_length+value_length+4, @data_offset)
580
+
581
+ @data_offset += 4
582
+ @data_buffer.write_byte(operation.op_type, @data_offset)
583
+ @data_offset += 1
584
+ @data_buffer.write_byte(operation.bin_value.type, @data_offset)
585
+ @data_offset += 1
586
+ @data_buffer.write_byte(0, @data_offset)
587
+ @data_offset += 1
588
+ @data_buffer.write_byte(name_length, @data_offset)
589
+ @data_offset += 1
590
+ @data_offset += name_length + value_length
591
+ end
592
+
593
+ def write_operation_for_bin_name(name, operation)
594
+ name_length = @data_buffer.write_binary(name, @data_offset+OPERATION_HEADER_SIZE)
595
+ # Buffer.Int32ToBytes(name_length+4, @data_buffer, @data_offset)
596
+ @data_buffer.write_int32(name_length+4, @data_offset)
597
+
598
+ @data_offset += 4
599
+ @data_buffer.write_byte(operation, @data_offset)
600
+ @data_offset += 1
601
+ @data_buffer.write_byte(0, @data_offset)
602
+ @data_offset += 1
603
+ @data_buffer.write_byte(0, @data_offset)
604
+ @data_offset += 1
605
+ @data_buffer.write_byte(name_length, @data_offset)
606
+ @data_offset += 1
607
+ @data_offset += name_length
608
+ end
609
+
610
+ def write_operation_for_operation_type(operation)
611
+ # Buffer.Int32ToBytes(4), @data_buffer, @data_offset
612
+ @data_buffer.write_int32(4, @data_offset)
613
+ @data_offset += 4
614
+ @data_buffer.write_byte(operation, @data_offset)
615
+ @data_offset += 1
616
+ @data_buffer.write_byte(0, @data_offset)
617
+ @data_offset += 1
618
+ @data_buffer.write_byte(0, @data_offset)
619
+ @data_offset += 1
620
+ @data_buffer.write_byte(0, @data_offset)
621
+ @data_offset += 1
622
+ end
623
+
624
+ def write_field_value(value, ftype)
625
+ offset = @data_offset + FIELD_HEADER_SIZE
626
+ @data_buffer.write_byte(value.type, offset)
627
+ offset += 1
628
+ len = value.write(@data_buffer, offset)
629
+ len += 1
630
+ write_field_header(len, ftype)
631
+ @data_offset += len
632
+ end
633
+
634
+ def write_field_string(str, ftype)
635
+ len = @data_buffer.write_binary(str, @data_offset+FIELD_HEADER_SIZE)
636
+ write_field_header(len, ftype)
637
+ @data_offset += len
638
+ end
639
+
640
+ def write_field_bytes(bytes, ftype)
641
+ @data_buffer.write_binary(bytes, @data_offset+FIELD_HEADER_SIZE)
642
+
643
+ write_field_header(bytes.bytesize, ftype)
644
+ @data_offset += bytes.bytesize
645
+ end
646
+
647
+ def write_field_header(size, ftype)
648
+ # Buffer.Int32ToBytes(size+1), @data_buffer, @data_offset
649
+ @data_buffer.write_int32(size+1, @data_offset)
650
+ @data_offset += 4
651
+ @data_buffer.write_byte(ftype, @data_offset)
652
+ @data_offset += 1
653
+ end
654
+
655
+ def begin_cmd
656
+ @data_offset = MSG_TOTAL_HEADER_SIZE
657
+ end
658
+
659
+ def size_buffer
660
+ size_buffer_sz(@data_offset)
661
+ end
662
+
663
+ def size_buffer_sz(size)
664
+ # Corrupted data streams can result in a hug.length.
665
+ # Do a sanity check here.
666
+ if size > Buffer::MAX_BUFFER_SIZE
667
+ raise Aerospike::Exceptions::Parse.new("Invalid size for buffer: #{size}")
668
+ end
669
+
670
+ @data_buffer.resize(size)
671
+ end
672
+
673
+ def end_cmd
674
+ size = (@data_offset-8) | Integer(CL_MSG_VERSION << 56) | Integer(AS_MSG_TYPE << 48)
675
+ @data_buffer.write_int64(size, 0)
676
+ end
677
+
678
+ end # class
679
+
680
+ end # module