tarantool 0.2.5 → 0.3.0.7
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.
- 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
|