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,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