tarantool 0.2.5 → 0.3.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +14 -12
- data/LICENSE +4 -4
- data/README.md +15 -9
- data/Rakefile +5 -129
- data/lib/tarantool/base_record.rb +288 -0
- data/lib/tarantool/block_db.rb +135 -0
- data/lib/tarantool/callback_db.rb +47 -0
- data/lib/tarantool/core-ext.rb +12 -0
- data/lib/tarantool/em_db.rb +37 -0
- data/lib/tarantool/exceptions.rb +52 -7
- data/lib/tarantool/fiber_db.rb +152 -0
- data/lib/tarantool/light_record.rb +68 -0
- data/lib/tarantool/query.rb +127 -0
- data/lib/tarantool/record/select.rb +59 -0
- data/lib/tarantool/record.rb +93 -282
- data/lib/tarantool/request.rb +351 -52
- data/lib/tarantool/response.rb +108 -45
- data/lib/tarantool/serializers/bson.rb +2 -2
- data/lib/tarantool/serializers.rb +32 -4
- data/lib/tarantool/space_array.rb +153 -0
- data/lib/tarantool/space_hash.rb +262 -0
- data/lib/tarantool/util.rb +182 -0
- data/lib/tarantool/version.rb +3 -0
- data/lib/tarantool.rb +79 -29
- data/test/box.pid +1 -0
- data/test/helper.rb +164 -0
- data/test/run_all.rb +3 -0
- data/test/shared_query.rb +73 -0
- data/test/shared_record.rb +474 -0
- data/test/shared_space_array.rb +284 -0
- data/test/shared_space_hash.rb +239 -0
- data/test/tarant/init.lua +22 -0
- data/test/tarantool.cfg +62 -0
- data/test/tarantool.log +6 -0
- data/{spec/tarantool.cfg → test/tarantool_repl.cfg} +11 -5
- data/test/test_light_record.rb +48 -0
- data/test/test_query_block.rb +6 -0
- data/test/test_query_fiber.rb +7 -0
- data/test/test_record.rb +88 -0
- data/{spec/tarantool/composite_primary_key_spec.rb → test/test_record_composite_pk.rb} +5 -7
- data/test/test_space_array_block.rb +6 -0
- data/test/test_space_array_callback.rb +255 -0
- data/test/test_space_array_callback_nodef.rb +190 -0
- data/test/test_space_array_fiber.rb +7 -0
- data/test/test_space_hash_block.rb +6 -0
- data/test/test_space_hash_fiber.rb +7 -0
- metadata +78 -55
- data/Gemfile.lock +0 -54
- data/examples/em_simple.rb +0 -16
- data/examples/record.rb +0 -68
- data/examples/simple.rb +0 -13
- data/lib/tarantool/requests/call.rb +0 -20
- data/lib/tarantool/requests/delete.rb +0 -18
- data/lib/tarantool/requests/insert.rb +0 -19
- data/lib/tarantool/requests/ping.rb +0 -16
- data/lib/tarantool/requests/select.rb +0 -22
- data/lib/tarantool/requests/update.rb +0 -35
- data/lib/tarantool/requests.rb +0 -19
- data/lib/tarantool/serializers/integer.rb +0 -14
- data/lib/tarantool/serializers/string.rb +0 -14
- data/lib/tarantool/space.rb +0 -39
- data/spec/helpers/let.rb +0 -11
- data/spec/helpers/truncate.rb +0 -12
- data/spec/spec_helper.rb +0 -21
- data/spec/tarantool/em_spec.rb +0 -22
- data/spec/tarantool/record_spec.rb +0 -316
- data/spec/tarantool/request_spec.rb +0 -103
- data/tarantool.gemspec +0 -69
data/lib/tarantool/record.rb
CHANGED
@@ -1,99 +1,8 @@
|
|
1
1
|
require 'active_model'
|
2
|
-
require 'tarantool'
|
3
|
-
class Tarantool
|
4
|
-
class Select
|
5
|
-
include Enumerable
|
6
|
-
attr_reader :record
|
7
|
-
def initialize(record)
|
8
|
-
@record = record
|
9
|
-
end
|
10
|
-
|
11
|
-
def space_no
|
12
|
-
record.space_no
|
13
|
-
end
|
14
|
-
|
15
|
-
def each(&blk)
|
16
|
-
to_records(record.space.select(*@tuples, index_no: @index_no, limit: @limit, offset: @offset).tuples).each do |r|
|
17
|
-
blk.call r
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def call(proc_name, *args)
|
22
|
-
to_records record.space.call(proc_name, *args, return_tuple: true).tuples
|
23
|
-
end
|
24
|
-
|
25
|
-
def limit(limit)
|
26
|
-
@limit = limit
|
27
|
-
self
|
28
|
-
end
|
29
|
-
|
30
|
-
def offset(offset)
|
31
|
-
@offset = offset
|
32
|
-
self
|
33
|
-
end
|
34
|
-
|
35
|
-
# id: 1
|
36
|
-
# id: [1, 2, 3]
|
37
|
-
# [{ name: 'a', email: 'a'}, { name: 'b', email: 'b'}]
|
38
|
-
def where(params)
|
39
|
-
raise SelectError.new('Where condition already setted') if @index_no # todo?
|
40
|
-
keys, @tuples = case params
|
41
|
-
when Hash
|
42
|
-
ordered_keys = record.ordered_keys params.keys
|
43
|
-
# name: ['a', 'b'], email: ['c', 'd'] => [['a', 'c'], ['b', 'd']]
|
44
|
-
if params.values.first.is_a?(Array)
|
45
|
-
[ordered_keys, params[ordered_keys.first].zip(*ordered_keys[1, ordered_keys.size].map { |k| params[k] })]
|
46
|
-
else
|
47
|
-
[ordered_keys, [record.hash_to_tuple(params)]]
|
48
|
-
end
|
49
|
-
when Array
|
50
|
-
[params.first.keys, params.map { |v| record.hash_to_tuple(v) }]
|
51
|
-
end
|
52
|
-
@index_no = detect_index_no keys
|
53
|
-
raise ArgumentError.new("Undefined index for keys #{keys}") unless @index_no
|
54
|
-
self
|
55
|
-
end
|
56
|
-
|
57
|
-
# # works fine only on TREE index
|
58
|
-
# def batches(count = 1000, &blk)
|
59
|
-
# raise ArgumentError.new("Only one tuple provided in batch selects") if @tuples.size > 1
|
60
|
-
|
61
|
-
# end
|
62
|
-
|
63
|
-
# def _batch_exec
|
64
|
-
# Tarantool.call proc_name: 'box.select_range', args: [space_no.to_s, @index_no.to_s, count.to_s] + @tuples.first.map(&:to_s), return_tuple: true
|
65
|
-
# end
|
66
|
-
|
67
|
-
def batches_each(&blk)
|
68
|
-
batches { |records| records.each(&blk) }
|
69
|
-
end
|
70
|
-
|
71
|
-
def all
|
72
|
-
to_a
|
73
|
-
end
|
74
|
-
|
75
|
-
def first
|
76
|
-
limit(1).all.first
|
77
|
-
end
|
2
|
+
require 'tarantool/base_record'
|
78
3
|
|
79
|
-
|
80
|
-
|
81
|
-
keys_inst = keys.dup
|
82
|
-
v.each do |index_part|
|
83
|
-
break unless keys_inst.delete(index_part)
|
84
|
-
return i if keys_inst.empty?
|
85
|
-
end
|
86
|
-
end
|
87
|
-
nil
|
88
|
-
end
|
89
|
-
|
90
|
-
def to_records(tuples)
|
91
|
-
tuples.map do |tuple|
|
92
|
-
record.from_server(tuple)
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
96
|
-
class Record
|
4
|
+
module Tarantool
|
5
|
+
class Record < BaseRecord
|
97
6
|
extend ActiveModel::Naming
|
98
7
|
include ActiveModel::AttributeMethods
|
99
8
|
include ActiveModel::Validations
|
@@ -107,195 +16,114 @@ class Tarantool
|
|
107
16
|
define_model_callbacks :save, :create, :update, :destroy
|
108
17
|
define_model_callbacks :initialize, :only => :after
|
109
18
|
|
110
|
-
class_attribute :fields
|
111
|
-
self.fields = {}
|
112
|
-
|
113
|
-
class_attribute :default_values
|
114
|
-
self.default_values = {}
|
115
|
-
|
116
|
-
class_attribute :primary_index
|
117
|
-
class_attribute :indexes
|
118
|
-
self.indexes = []
|
119
|
-
|
120
|
-
class_attribute :space_no
|
121
|
-
class_attribute :tarantool
|
122
19
|
class << self
|
123
20
|
def set_space_no(val)
|
124
21
|
self.space_no = val
|
125
22
|
end
|
126
23
|
|
127
24
|
def set_tarantool(val)
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
def field(name, type, params = {})
|
132
|
-
define_attribute_method name
|
133
|
-
self.fields = fields.merge name => { type: type, field_no: fields.size, params: params }
|
134
|
-
unless self.primary_index
|
135
|
-
self.primary_index = name
|
136
|
-
index name
|
137
|
-
end
|
138
|
-
if params[:default]
|
139
|
-
self.default_values = default_values.merge name => params[:default]
|
140
|
-
end
|
141
|
-
define_method name do
|
142
|
-
attributes[name]
|
143
|
-
end
|
144
|
-
define_method "#{name}=" do |v|
|
145
|
-
send("#{name}_will_change!") unless v == attributes[name]
|
146
|
-
attributes[name] = v
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
def index(*fields)
|
151
|
-
options = {}
|
152
|
-
options = fields.pop if Hash === fields.last
|
153
|
-
if options[:primary]
|
154
|
-
self.indexes[0] = fields
|
155
|
-
self.primary_index = fields
|
25
|
+
case val.class.name
|
26
|
+
when 'Tarantool::BlockDB', 'Tarantool::FiberDB'
|
27
|
+
self.tarantool = val
|
156
28
|
else
|
157
|
-
|
29
|
+
raise "Tarantool should be blocking of fibered!!! (i.e. of class Tarantool::BlockDB or Tarantool::FiberDB) (got #{val.class})"
|
158
30
|
end
|
159
31
|
end
|
160
32
|
|
161
|
-
def
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
from_server res.tuple
|
166
|
-
else
|
167
|
-
nil
|
33
|
+
def define_field_accessor(name, type)
|
34
|
+
generated_attribute_methods.class_eval <<-"EOF", __FILE__, __LINE__ - 1
|
35
|
+
def #{name}
|
36
|
+
@attributes[:"#{name}"]
|
168
37
|
end
|
38
|
+
EOF
|
39
|
+
|
40
|
+
if Symbol === type
|
41
|
+
convert_code = case type
|
42
|
+
when :int, :integer
|
43
|
+
"v = v.to_i if String === v"
|
44
|
+
when :str, :string, :bytes
|
45
|
+
""
|
46
|
+
else
|
47
|
+
if serializer = Serializers::MAP[type]
|
48
|
+
"v = Serializers::MAP[#{type.inspect}].decode(v) if String === v"
|
49
|
+
else
|
50
|
+
raise ArgumentError, "unknown field type #{type.inspect}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
generated_attribute_methods.class_eval <<-"EOF", __FILE__, __LINE__ - 1
|
55
|
+
def #{name}=(v)
|
56
|
+
#{convert_code}
|
57
|
+
#{name}_will_change! unless v == @attributes[:"#{name}"] || new_record?
|
58
|
+
@attributes[:"#{name}"] = v
|
59
|
+
end
|
60
|
+
EOF
|
169
61
|
else
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
end
|
177
|
-
|
178
|
-
%w{where limit offset call first}.each do |v|
|
179
|
-
define_method v do |*args|
|
180
|
-
select.send(v, *args)
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
def create(attribites = {})
|
185
|
-
new(attribites).tap { |o| o.save }
|
186
|
-
end
|
187
|
-
|
188
|
-
def from_server(tuple)
|
189
|
-
new(tuple_to_hash(tuple).merge __new_record: false)
|
190
|
-
end
|
191
|
-
|
192
|
-
def space
|
193
|
-
@space ||= tarantool.space(space_no)
|
194
|
-
end
|
195
|
-
|
196
|
-
def tuple_to_hash(tuple)
|
197
|
-
fields.keys.zip(tuple).inject({}) do |memo, (k, v)|
|
198
|
-
memo[k] = _cast(k, v) unless v.nil?
|
199
|
-
memo
|
200
|
-
end
|
201
|
-
end
|
202
|
-
|
203
|
-
def hash_to_tuple(hash, with_nils = false)
|
204
|
-
res = []
|
205
|
-
fields.keys.each do |k|
|
206
|
-
v = hash[k]
|
207
|
-
res << _cast(k, v) if with_nils || !v.nil?
|
208
|
-
end
|
209
|
-
res
|
210
|
-
end
|
211
|
-
|
212
|
-
def ordered_keys(keys)
|
213
|
-
fields.keys.inject([]) do |memo, k|
|
214
|
-
keys.each do |k2|
|
215
|
-
memo << k2 if k2 == k
|
62
|
+
generated_attribute_methods.class_eval do
|
63
|
+
define_method("#{name}=") do |v|
|
64
|
+
v = type.decode(v) if String === v
|
65
|
+
send(:"#{name}_will_change!") unless v == @attributes[name]
|
66
|
+
@attributes[name] = v
|
67
|
+
end
|
216
68
|
end
|
217
|
-
memo
|
218
69
|
end
|
219
|
-
|
220
|
-
|
221
|
-
def _cast(name, value)
|
222
|
-
type = self.fields[name][:type]
|
223
|
-
serializer = _get_serializer(type)
|
224
|
-
if value.is_a?(Field)
|
225
|
-
return nil if value.data == "\0"
|
226
|
-
serializer.decode(value)
|
227
|
-
else
|
228
|
-
return "\0" if value.nil?
|
229
|
-
serializer.encode(value)
|
230
|
-
end
|
231
|
-
end
|
232
|
-
|
233
|
-
def _get_serializer(type)
|
234
|
-
Serializers::MAP[type] || raise(TarantoolError.new("Undefind serializer #{type}"))
|
70
|
+
define_attribute_method name
|
235
71
|
end
|
236
72
|
end
|
237
73
|
|
238
|
-
attr_accessor :__new_record
|
239
74
|
def initialize(attributes = {})
|
75
|
+
@__new_record = true
|
76
|
+
@attributes = self.class.default_values.dup
|
240
77
|
run_callbacks(:initialize) do
|
241
78
|
init attributes
|
242
79
|
end
|
243
80
|
end
|
244
81
|
|
245
82
|
def init(attributes)
|
246
|
-
|
247
|
-
@__new_record = true if @__new_record.nil?
|
248
|
-
attributes.each do |k, v|
|
249
|
-
send("#{k}=", v)
|
250
|
-
end
|
83
|
+
set_attributes(attributes)
|
251
84
|
end
|
252
85
|
|
253
|
-
def
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
attributes[primary]
|
86
|
+
def __fetched(attributes)
|
87
|
+
@__new_record = false
|
88
|
+
# well, initalize callback could call #attributes
|
89
|
+
@attributes = self.class.default_values.dup
|
90
|
+
run_callbacks(:initialize) do
|
91
|
+
@attributes = attributes
|
260
92
|
end
|
93
|
+
self
|
261
94
|
end
|
262
95
|
|
263
|
-
def
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
def new_record?
|
268
|
-
@__new_record
|
269
|
-
end
|
270
|
-
|
271
|
-
def attributes
|
272
|
-
@attributes ||= self.class.default_values.dup
|
273
|
-
end
|
274
|
-
|
275
|
-
def new_record!
|
276
|
-
@__new_record = true
|
277
|
-
end
|
278
|
-
|
279
|
-
def old_record!
|
280
|
-
@__new_record = false
|
96
|
+
def _in_callbacks(&blk)
|
97
|
+
run_callbacks(:save) {
|
98
|
+
run_callbacks(new_record? ? :create : :update, &blk)
|
99
|
+
}
|
281
100
|
end
|
282
101
|
|
283
|
-
def save
|
284
|
-
|
285
|
-
run_callbacks(:save) { run_callbacks(new_record? ? :create : :update, &blk)}
|
286
|
-
end
|
287
|
-
in_callbacks do
|
102
|
+
def save(and_reload = true)
|
103
|
+
_in_callbacks do
|
288
104
|
if valid?
|
105
|
+
changes = changes()
|
289
106
|
if new_record?
|
290
|
-
|
107
|
+
if and_reload
|
108
|
+
@attributes = space.insert(@attributes, return_tuple: true)
|
109
|
+
else
|
110
|
+
space.insert(@attributes)
|
111
|
+
end
|
291
112
|
else
|
292
|
-
return true if
|
293
|
-
ops =
|
294
|
-
|
295
|
-
|
296
|
-
|
113
|
+
return true if changes.size == 0
|
114
|
+
ops = []
|
115
|
+
changes.each do |k, (old, new)|
|
116
|
+
ops << [k.to_sym, :set, new]
|
117
|
+
end
|
118
|
+
if and_reload
|
119
|
+
unless new_attrs = space.update(id, ops, return_tuple: true)
|
120
|
+
_raise_doesnt_exists
|
121
|
+
end
|
122
|
+
else
|
123
|
+
if space.update(id, ops) == 0
|
124
|
+
_raise_doesnt_exists
|
125
|
+
end
|
297
126
|
end
|
298
|
-
space.update id, ops: ops
|
299
127
|
end
|
300
128
|
@previously_changed = changes
|
301
129
|
@changed_attributes.clear
|
@@ -307,47 +135,30 @@ class Tarantool
|
|
307
135
|
end
|
308
136
|
end
|
309
137
|
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
138
|
+
# update record in db first, reload updated fileds then
|
139
|
+
# (Contrasting with LightRecord, where it reloads all fields)
|
140
|
+
# Consider that update operation does not count changes made by
|
141
|
+
# attr setters in your code, only field values in DB.
|
142
|
+
#
|
143
|
+
# record.update({:state => 'sleep', :sleep_count => [:+, 1]})
|
144
|
+
# record.update([[:state, 'sleep'], [:sleep_count, :+, 1]])
|
145
|
+
def update(ops)
|
146
|
+
raise UpdateNewRecord, "Could not call update on new record" if @__new_record
|
147
|
+
unless new_attrs = space.update(id, ops, return_tuple: true)
|
148
|
+
_raise_doesnt_exists
|
318
149
|
end
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
150
|
+
for op in ops
|
151
|
+
field = op.flatten.first
|
152
|
+
@attributes[field] = new_attrs[field]
|
153
|
+
end
|
154
|
+
self
|
324
155
|
end
|
325
156
|
|
326
157
|
def destroy
|
327
158
|
run_callbacks :destroy do
|
328
|
-
|
159
|
+
self.class.delete id
|
329
160
|
true
|
330
161
|
end
|
331
162
|
end
|
332
|
-
|
333
|
-
def to_tuple
|
334
|
-
self.class.hash_to_tuple attributes, true
|
335
|
-
end
|
336
|
-
|
337
|
-
def field_no(name)
|
338
|
-
self.class.fields[name][:field_no]
|
339
|
-
end
|
340
|
-
|
341
|
-
def reload
|
342
|
-
tuple = space.select(id).tuple
|
343
|
-
return false unless tuple
|
344
|
-
init self.class.tuple_to_hash(tuple).merge __new_record: false
|
345
|
-
self
|
346
|
-
end
|
347
|
-
|
348
|
-
def ==(other)
|
349
|
-
self.id == other.id
|
350
|
-
end
|
351
|
-
|
352
163
|
end
|
353
164
|
end
|