aerospike 0.1.6 → 1.0.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 +4 -4
- data/CHANGELOG.md +22 -0
- data/lib/aerospike.rb +5 -0
- data/lib/aerospike/atomic/atomic.rb +3 -1
- data/lib/aerospike/client.rb +98 -32
- data/lib/aerospike/cluster/cluster.rb +25 -8
- data/lib/aerospike/cluster/connection.rb +1 -1
- data/lib/aerospike/cluster/node.rb +16 -3
- data/lib/aerospike/cluster/node_validator.rb +16 -4
- data/lib/aerospike/cluster/partition.rb +1 -1
- data/lib/aerospike/cluster/partition_tokenizer_new.rb +4 -2
- data/lib/aerospike/cluster/partition_tokenizer_old.rb +1 -1
- data/lib/aerospike/command/admin_command.rb +353 -0
- data/lib/aerospike/command/batch_command.rb +12 -42
- data/lib/aerospike/command/batch_command_exists.rb +1 -1
- data/lib/aerospike/command/batch_command_get.rb +1 -1
- data/lib/aerospike/command/batch_item.rb +1 -1
- data/lib/aerospike/command/batch_node.rb +1 -1
- data/lib/aerospike/command/command.rb +9 -14
- data/lib/aerospike/command/delete_command.rb +1 -1
- data/lib/aerospike/command/execute_command.rb +1 -1
- data/lib/aerospike/command/exists_command.rb +1 -1
- data/lib/aerospike/command/operate_command.rb +1 -1
- data/lib/aerospike/command/read_command.rb +12 -38
- data/lib/aerospike/command/read_header_command.rb +2 -2
- data/lib/aerospike/command/roles.rb +36 -0
- data/lib/aerospike/command/single_command.rb +1 -1
- data/lib/aerospike/command/touch_command.rb +1 -1
- data/lib/aerospike/command/write_command.rb +1 -1
- data/lib/aerospike/info.rb +1 -1
- data/lib/aerospike/loggable.rb +1 -1
- data/lib/aerospike/policy/admin_policy.rb +33 -0
- data/lib/aerospike/policy/batch_policy.rb +5 -5
- data/lib/aerospike/policy/client_policy.rb +15 -4
- data/lib/aerospike/policy/generation_policy.rb +0 -5
- data/lib/aerospike/policy/policy.rb +6 -6
- data/lib/aerospike/policy/query_policy.rb +2 -2
- data/lib/aerospike/policy/scan_policy.rb +6 -6
- data/lib/aerospike/policy/write_policy.rb +8 -8
- data/lib/aerospike/query/query_command.rb +1 -1
- data/lib/aerospike/query/scan_command.rb +1 -1
- data/lib/aerospike/query/stream_command.rb +1 -1
- data/lib/aerospike/record.rb +2 -3
- data/lib/aerospike/result_code.rb +11 -1
- data/lib/aerospike/user_role.rb +30 -0
- data/lib/aerospike/utils/buffer.rb +22 -2
- data/lib/aerospike/utils/epoc.rb +3 -1
- data/lib/aerospike/utils/pool.rb +1 -1
- data/lib/aerospike/value/value.rb +12 -13
- data/lib/aerospike/version.rb +1 -1
- metadata +6 -2
@@ -18,11 +18,12 @@ module Aerospike
|
|
18
18
|
|
19
19
|
private
|
20
20
|
|
21
|
-
class NodeValidator
|
21
|
+
class NodeValidator # :nodoc:
|
22
22
|
|
23
23
|
attr_reader :host, :aliases, :name, :use_new_info
|
24
24
|
|
25
|
-
def initialize(host, timeout)
|
25
|
+
def initialize(cluster, host, timeout)
|
26
|
+
@cluster = cluster
|
26
27
|
@use_new_info = true
|
27
28
|
@host = host
|
28
29
|
|
@@ -47,8 +48,19 @@ module Aerospike
|
|
47
48
|
def set_address(timeout)
|
48
49
|
@aliases.each do |aliass|
|
49
50
|
begin
|
50
|
-
conn = Connection.new(aliass.name, aliass.port,
|
51
|
-
|
51
|
+
conn = Connection.new(aliass.name, aliass.port, timeout)
|
52
|
+
|
53
|
+
# need to authenticate
|
54
|
+
if @cluster.user && @cluster.user != ''
|
55
|
+
begin
|
56
|
+
command = AdminCommand.new
|
57
|
+
command.authenticate(conn, @cluster.user, @cluster.password)
|
58
|
+
rescue => e
|
59
|
+
# Socket not authenticated. Do not put back into pool.
|
60
|
+
conn.close if conn
|
61
|
+
raise e
|
62
|
+
end
|
63
|
+
end
|
52
64
|
|
53
65
|
info_map= Info.request(conn, 'node', 'build')
|
54
66
|
if node_name = info_map['node']
|
@@ -22,7 +22,7 @@ module Aerospike
|
|
22
22
|
|
23
23
|
REPLICAS_NAME = 'replicas-master'
|
24
24
|
|
25
|
-
class PartitionTokenizerNew
|
25
|
+
class PartitionTokenizerNew #:nodoc:
|
26
26
|
|
27
27
|
def initialize(conn)
|
28
28
|
# Use low-level info methods and parse byte array directly for maximum performance.
|
@@ -97,11 +97,13 @@ module Aerospike
|
|
97
97
|
|
98
98
|
bit_map_length = @offset - beginning
|
99
99
|
restore_buffer = Base64.strict_decode64(@buffer[beginning, bit_map_length])
|
100
|
-
|
100
|
+
i = 0
|
101
|
+
while i < Aerospike::Node::PARTITIONS
|
101
102
|
if (restore_buffer[i>>3].ord & (0x80 >> (i & 7))) != 0
|
102
103
|
# Logger.Info("Map: `" + namespace + "`," + strconv.Itoa(i) + "," + node.String)
|
103
104
|
node_array.update{|v| v[i] = node; v}
|
104
105
|
end
|
106
|
+
i = i.succ
|
105
107
|
end
|
106
108
|
|
107
109
|
@offset+=1
|
@@ -0,0 +1,353 @@
|
|
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
|
+
private
|
20
|
+
# Commands
|
21
|
+
AUTHENTICATE = 0
|
22
|
+
CREATE_USER = 1
|
23
|
+
DROP_USER = 2
|
24
|
+
SET_PASSWORD = 3
|
25
|
+
CHANGE_PASSWORD = 4
|
26
|
+
GRANT_ROLES = 5
|
27
|
+
REVOKE_ROLES = 6
|
28
|
+
REPLACE_ROLES = 7
|
29
|
+
#CREATE_ROLE = 8
|
30
|
+
QUERY_USERS = 9
|
31
|
+
#QUERY_ROLES = 10
|
32
|
+
|
33
|
+
# Field IDs
|
34
|
+
USER = 0
|
35
|
+
PASSWORD = 1
|
36
|
+
OLD_PASSWORD = 2
|
37
|
+
CREDENTIAL = 3
|
38
|
+
ROLES = 10
|
39
|
+
#PRIVILEGES = 11
|
40
|
+
|
41
|
+
# Misc
|
42
|
+
MSG_VERSION = 0
|
43
|
+
MSG_TYPE = 2
|
44
|
+
|
45
|
+
HEADER_SIZE = 24
|
46
|
+
HEADER_REMAINING = 16
|
47
|
+
RESULT_CODE = 9
|
48
|
+
QUERY_END = 50
|
49
|
+
|
50
|
+
class AdminCommand #:nodoc:
|
51
|
+
|
52
|
+
def initialize
|
53
|
+
@data_buffer = Buffer.get
|
54
|
+
@data_offset = 8
|
55
|
+
end
|
56
|
+
|
57
|
+
def authenticate(conn, user, password)
|
58
|
+
begin
|
59
|
+
set_authenticate(user, password)
|
60
|
+
conn.write(@data_buffer, @data_offset)
|
61
|
+
conn.read(@data_buffer, HEADER_SIZE)
|
62
|
+
|
63
|
+
result = @data_buffer.read(RESULT_CODE)
|
64
|
+
raise Exceptions::Aerospike.new(result, "Authentication failed") if result != 0
|
65
|
+
ensure
|
66
|
+
Buffer.put(@data_buffer)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def set_authenticate(user, password)
|
71
|
+
write_header(AUTHENTICATE, 2)
|
72
|
+
write_field_str(USER, user)
|
73
|
+
write_field_bytes(CREDENTIAL, password)
|
74
|
+
write_size
|
75
|
+
|
76
|
+
return @data_offset
|
77
|
+
end
|
78
|
+
|
79
|
+
def create_user(cluster, policy, user, password, roles)
|
80
|
+
write_header(CREATE_USER, 3)
|
81
|
+
write_field_str(USER, user)
|
82
|
+
write_field_bytes(PASSWORD, password)
|
83
|
+
write_roles(roles)
|
84
|
+
execute_command(cluster, policy)
|
85
|
+
end
|
86
|
+
|
87
|
+
def drop_user(cluster, policy, user)
|
88
|
+
write_header(DROP_USER, 1)
|
89
|
+
write_field_str(USER, user)
|
90
|
+
execute_command(cluster, policy)
|
91
|
+
end
|
92
|
+
|
93
|
+
def set_password(cluster, policy, user, password)
|
94
|
+
write_header(SET_PASSWORD, 2)
|
95
|
+
write_field_str(USER, user)
|
96
|
+
write_field_bytes(PASSWORD, password)
|
97
|
+
execute_command(cluster, policy)
|
98
|
+
end
|
99
|
+
|
100
|
+
def change_password(cluster, policy, user, password)
|
101
|
+
write_header(CHANGE_PASSWORD, 3)
|
102
|
+
write_field_str(USER, user)
|
103
|
+
write_field_bytes(OLD_PASSWORD, cluster.password)
|
104
|
+
write_field_bytes(PASSWORD, password)
|
105
|
+
execute_command(cluster, policy)
|
106
|
+
end
|
107
|
+
|
108
|
+
def grant_roles(cluster, policy, user, roles)
|
109
|
+
write_header(GRANT_ROLES, 2)
|
110
|
+
write_field_str(USER, user)
|
111
|
+
write_roles(roles)
|
112
|
+
execute_command(cluster, policy)
|
113
|
+
end
|
114
|
+
|
115
|
+
def revoke_roles(cluster, policy, user, roles)
|
116
|
+
write_header(REVOKE_ROLES, 2)
|
117
|
+
write_field_str(USER, user)
|
118
|
+
write_roles(roles)
|
119
|
+
execute_command(cluster, policy)
|
120
|
+
end
|
121
|
+
|
122
|
+
def replace_roles(cluster, policy, user, roles)
|
123
|
+
write_header(REPLACE_ROLES, 2)
|
124
|
+
write_field_str(USER, user)
|
125
|
+
write_roles(roles)
|
126
|
+
execute_command(cluster, policy)
|
127
|
+
end
|
128
|
+
|
129
|
+
def query_user(cluster, policy, user)
|
130
|
+
# TODO: Remove the workaround in the future
|
131
|
+
sleep(0.010)
|
132
|
+
|
133
|
+
list = []
|
134
|
+
begin
|
135
|
+
write_header(QUERY_USERS, 1)
|
136
|
+
write_field_str(USER, user)
|
137
|
+
list = read_users(cluster, policy)
|
138
|
+
return (list.is_a?(Array) && list.length > 0 ? list.first : nil)
|
139
|
+
ensure
|
140
|
+
Buffer.put(@data_buffer)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def query_users(cluster, policy)
|
145
|
+
# TODO: Remove the workaround in the future
|
146
|
+
sleep(0.010)
|
147
|
+
begin
|
148
|
+
write_header(QUERY_USERS, 0)
|
149
|
+
return read_users(cluster, policy)
|
150
|
+
ensure
|
151
|
+
Buffer.put(@data_buffer)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def write_roles(roles)
|
156
|
+
offset = @data_offset + FIELD_HEADER_SIZE
|
157
|
+
@data_buffer.write_byte(roles.length.ord, offset)
|
158
|
+
offset += 1
|
159
|
+
|
160
|
+
roles.each do |role|
|
161
|
+
len = @data_buffer.write_binary(role, offset+1)
|
162
|
+
@data_buffer.write_byte(len, offset)
|
163
|
+
offset += len + 1
|
164
|
+
end
|
165
|
+
|
166
|
+
size = offset - @data_offset - FIELD_HEADER_SIZE
|
167
|
+
write_field_header(ROLES, size)
|
168
|
+
@data_offset = offset
|
169
|
+
end
|
170
|
+
|
171
|
+
def write_size
|
172
|
+
# Write total size of message which is the current offset.
|
173
|
+
size = Integer(@data_offset-8) | Integer(MSG_VERSION << 56) | Integer(MSG_TYPE << 48)
|
174
|
+
@data_buffer.write_int64(size, 0)
|
175
|
+
end
|
176
|
+
|
177
|
+
def write_header(command, field_count)
|
178
|
+
# Authenticate header is almost all zeros
|
179
|
+
i = @data_offset
|
180
|
+
while i < @data_offset+16
|
181
|
+
@data_buffer.write_byte(0, i)
|
182
|
+
i = i.succ
|
183
|
+
end
|
184
|
+
@data_buffer.write_byte(command, @data_offset+2)
|
185
|
+
@data_buffer.write_byte(field_count, @data_offset+3)
|
186
|
+
@data_offset += 16
|
187
|
+
end
|
188
|
+
|
189
|
+
def write_field_str(id, str)
|
190
|
+
len = @data_buffer.write_binary(str, @data_offset+FIELD_HEADER_SIZE)
|
191
|
+
write_field_header(id, len)
|
192
|
+
@data_offset += len
|
193
|
+
end
|
194
|
+
|
195
|
+
def write_field_bytes(id, bytes)
|
196
|
+
@data_buffer.write_binary(bytes, @data_offset+FIELD_HEADER_SIZE)
|
197
|
+
write_field_header(id, bytes.bytesize)
|
198
|
+
@data_offset += bytes.bytesize
|
199
|
+
end
|
200
|
+
|
201
|
+
def write_field_header(id, size)
|
202
|
+
@data_buffer.write_int32(size+1, @data_offset)
|
203
|
+
@data_offset += 4
|
204
|
+
@data_buffer.write_byte(id, @data_offset)
|
205
|
+
@data_offset += 1
|
206
|
+
end
|
207
|
+
|
208
|
+
def execute_command(cluster, policy)
|
209
|
+
# TODO: Remove the workaround in the future
|
210
|
+
sleep(0.010)
|
211
|
+
|
212
|
+
write_size
|
213
|
+
node = cluster.random_node
|
214
|
+
|
215
|
+
timeout = 1
|
216
|
+
timeout = policy.timeout if policy && policy.timeout > 0
|
217
|
+
|
218
|
+
conn = node.get_connection(timeout)
|
219
|
+
|
220
|
+
begin
|
221
|
+
conn.write(@data_buffer, @data_offset)
|
222
|
+
conn.read(@data_buffer, HEADER_SIZE)
|
223
|
+
node.put_connection(conn)
|
224
|
+
rescue => e
|
225
|
+
conn.close if conn
|
226
|
+
raise e
|
227
|
+
end
|
228
|
+
|
229
|
+
result = @data_buffer.read(RESULT_CODE)
|
230
|
+
raise Exceptions::Aerospike.new(result) if result != 0
|
231
|
+
|
232
|
+
Buffer.put(@data_buffer)
|
233
|
+
end
|
234
|
+
|
235
|
+
def read_users(cluster, policy)
|
236
|
+
write_size
|
237
|
+
node = cluster.random_node
|
238
|
+
|
239
|
+
timeout = 1
|
240
|
+
timeout = policy.timeout if policy != nil && policy.timeout > 0
|
241
|
+
|
242
|
+
status = -1
|
243
|
+
list = []
|
244
|
+
begin
|
245
|
+
conn = node.get_connection(timeout)
|
246
|
+
conn.write(@data_buffer, @data_offset)
|
247
|
+
status, list = read_user_blocks(conn)
|
248
|
+
node.put_connection(conn)
|
249
|
+
rescue => e
|
250
|
+
conn.close if conn
|
251
|
+
raise e
|
252
|
+
end
|
253
|
+
|
254
|
+
raise Exceptions::Aerospike.new(result) if status > 0
|
255
|
+
|
256
|
+
return list
|
257
|
+
end
|
258
|
+
|
259
|
+
def read_user_blocks(conn)
|
260
|
+
rlist = []
|
261
|
+
status = 0
|
262
|
+
begin
|
263
|
+
while status == 0
|
264
|
+
conn.read(@data_buffer, 8)
|
265
|
+
size = @data_buffer.read_int64(0)
|
266
|
+
receive_size = (size & 0xFFFFFFFFFFFF)
|
267
|
+
|
268
|
+
if receive_size > 0
|
269
|
+
@data_buffer.resize(receive_size) if receive_size > @data_buffer.size
|
270
|
+
|
271
|
+
conn.read(@data_buffer, receive_size)
|
272
|
+
status, list = parse_users(receive_size)
|
273
|
+
rlist.concat(list.to_a)
|
274
|
+
else
|
275
|
+
break
|
276
|
+
end
|
277
|
+
end
|
278
|
+
return status, rlist
|
279
|
+
rescue => e
|
280
|
+
return -1, []
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
def parse_users(receive_size)
|
285
|
+
@data_offset = 0
|
286
|
+
list = []
|
287
|
+
|
288
|
+
while @data_offset < receive_size
|
289
|
+
result_code = @data_buffer.read(@data_offset+1)
|
290
|
+
|
291
|
+
if result_code != 0
|
292
|
+
return (result_code == QUERY_END ? -1 : result_code)
|
293
|
+
end
|
294
|
+
|
295
|
+
userRoles = UserRoles.new
|
296
|
+
field_count = @data_buffer.read(@data_offset+3)
|
297
|
+
@data_offset += HEADER_REMAINING
|
298
|
+
|
299
|
+
i = 0
|
300
|
+
while i < field_count
|
301
|
+
len = @data_buffer.read_int32(@data_offset)
|
302
|
+
@data_offset += 4
|
303
|
+
id = @data_buffer.read(@data_offset)
|
304
|
+
@data_offset += 1
|
305
|
+
len -= 1
|
306
|
+
|
307
|
+
case id
|
308
|
+
when USER
|
309
|
+
userRoles.user = @data_buffer.read(@data_offset, len)
|
310
|
+
@data_offset += len
|
311
|
+
when ROLES
|
312
|
+
parse_roles(userRoles)
|
313
|
+
else
|
314
|
+
@data_offset += len
|
315
|
+
end
|
316
|
+
|
317
|
+
i = i.succ
|
318
|
+
end
|
319
|
+
|
320
|
+
next if userRoles.user == "" && userRoles.roles == nil
|
321
|
+
|
322
|
+
userRoles.roles = [] if userRoles.roles == nil
|
323
|
+
list << userRoles
|
324
|
+
end
|
325
|
+
|
326
|
+
return 0, list
|
327
|
+
end
|
328
|
+
|
329
|
+
def parse_roles(userRoles)
|
330
|
+
size = @data_buffer.read(@data_offset)
|
331
|
+
@data_offset += 1
|
332
|
+
userRoles.roles = []
|
333
|
+
|
334
|
+
i = 0
|
335
|
+
while i < size
|
336
|
+
len = @data_buffer.read(@data_offset)
|
337
|
+
@data_offset += 1
|
338
|
+
role = @data_buffer.read(@data_offset, len)
|
339
|
+
@data_offset += len
|
340
|
+
userRoles.roles << role
|
341
|
+
|
342
|
+
i = i.succ
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
SALT = '$2a$10$7EqJtq98hPqEX7fNZaFWoO'
|
347
|
+
def self.hash_password(password)
|
348
|
+
# Hashing the password with the cost of 10, with a static salt
|
349
|
+
return BCrypt::Engine.hash_secret(password, SALT, :cost => 10)
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
@@ -24,7 +24,7 @@ module Aerospike
|
|
24
24
|
|
25
25
|
private
|
26
26
|
|
27
|
-
class BatchCommand < Command
|
27
|
+
class BatchCommand < Command #:nodoc:
|
28
28
|
|
29
29
|
def initialize(node)
|
30
30
|
super(node)
|
@@ -61,7 +61,8 @@ module Aerospike
|
|
61
61
|
set_name = nil
|
62
62
|
user_key = nil
|
63
63
|
|
64
|
-
|
64
|
+
i = 0
|
65
|
+
while i < field_count
|
65
66
|
read_bytes(4)
|
66
67
|
|
67
68
|
fieldlen = @data_buffer.read_int32(0)
|
@@ -80,6 +81,8 @@ module Aerospike
|
|
80
81
|
when Aerospike::FieldType::KEY
|
81
82
|
user_key = Aerospike::bytes_to_key_value(@data_buffer.read(1).ord, @data_buffer, 2, size-1)
|
82
83
|
end
|
84
|
+
|
85
|
+
i = i.succ
|
83
86
|
end
|
84
87
|
|
85
88
|
Aerospike::Key.new(namespace, set_name, user_key, digest)
|
@@ -88,17 +91,15 @@ module Aerospike
|
|
88
91
|
# Parses the given byte buffer and populate the result object.
|
89
92
|
# Returns the number of bytes that were parsed from the given buffer.
|
90
93
|
def parse_record(key, op_count, generation, expiration)
|
91
|
-
bins = nil
|
92
|
-
|
93
|
-
|
94
|
-
for i in 0...op_count
|
94
|
+
bins = op_count > 0 ? {} : nil
|
95
|
+
i = 0
|
96
|
+
while i < op_count
|
95
97
|
raise Aerospike::Exceptions::QueryTerminated.new unless valid?
|
96
98
|
|
97
99
|
read_bytes(8)
|
98
100
|
|
99
101
|
op_size = @data_buffer.read_int32(0).ord
|
100
102
|
particle_type = @data_buffer.read(5).ord
|
101
|
-
version = @data_buffer.read(6).ord
|
102
103
|
name_size = @data_buffer.read(7).ord
|
103
104
|
|
104
105
|
read_bytes(name_size)
|
@@ -114,44 +115,13 @@ module Aerospike
|
|
114
115
|
# if !@bin_names || @bin_names.any?{|bn| bn == name}
|
115
116
|
# if !@bin_names || (@bin_names == []) || @bin_names.any?{|bn| bn == name}
|
116
117
|
if !@bin_names || (@bin_names.empty?) || @bin_names.any?{|bn| bn == name}
|
117
|
-
|
118
|
-
vmap = nil
|
119
|
-
|
120
|
-
if version > 0 || duplicates
|
121
|
-
unless duplicates
|
122
|
-
duplicates = []
|
123
|
-
duplicates << bins
|
124
|
-
bins = nil
|
125
|
-
|
126
|
-
for j in 0...version
|
127
|
-
duplicates << nil
|
128
|
-
end
|
129
|
-
else
|
130
|
-
for j in duplicates.length..version
|
131
|
-
duplicates << nil
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
vmap = duplicates[version]
|
136
|
-
unless vmap
|
137
|
-
vmap = {}
|
138
|
-
duplicates[version] = vmap
|
139
|
-
end
|
140
|
-
else
|
141
|
-
unless bins
|
142
|
-
bins = {}
|
143
|
-
end
|
144
|
-
vmap = bins
|
145
|
-
end
|
146
|
-
vmap[name] = value
|
118
|
+
bins[name] = value
|
147
119
|
end
|
148
|
-
end
|
149
120
|
|
150
|
-
|
151
|
-
|
152
|
-
duplicates.compact! if duplicates
|
121
|
+
i = i.succ
|
122
|
+
end
|
153
123
|
|
154
|
-
Record.new(@node, key, bins,
|
124
|
+
Record.new(@node, key, bins, generation, expiration)
|
155
125
|
end
|
156
126
|
|
157
127
|
def read_bytes(length)
|