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.
Files changed (68) hide show
  1. data/Gemfile +14 -12
  2. data/LICENSE +4 -4
  3. data/README.md +15 -9
  4. data/Rakefile +5 -129
  5. data/lib/tarantool/base_record.rb +288 -0
  6. data/lib/tarantool/block_db.rb +135 -0
  7. data/lib/tarantool/callback_db.rb +47 -0
  8. data/lib/tarantool/core-ext.rb +12 -0
  9. data/lib/tarantool/em_db.rb +37 -0
  10. data/lib/tarantool/exceptions.rb +52 -7
  11. data/lib/tarantool/fiber_db.rb +152 -0
  12. data/lib/tarantool/light_record.rb +68 -0
  13. data/lib/tarantool/query.rb +127 -0
  14. data/lib/tarantool/record/select.rb +59 -0
  15. data/lib/tarantool/record.rb +93 -282
  16. data/lib/tarantool/request.rb +351 -52
  17. data/lib/tarantool/response.rb +108 -45
  18. data/lib/tarantool/serializers/bson.rb +2 -2
  19. data/lib/tarantool/serializers.rb +32 -4
  20. data/lib/tarantool/space_array.rb +153 -0
  21. data/lib/tarantool/space_hash.rb +262 -0
  22. data/lib/tarantool/util.rb +182 -0
  23. data/lib/tarantool/version.rb +3 -0
  24. data/lib/tarantool.rb +79 -29
  25. data/test/box.pid +1 -0
  26. data/test/helper.rb +164 -0
  27. data/test/run_all.rb +3 -0
  28. data/test/shared_query.rb +73 -0
  29. data/test/shared_record.rb +474 -0
  30. data/test/shared_space_array.rb +284 -0
  31. data/test/shared_space_hash.rb +239 -0
  32. data/test/tarant/init.lua +22 -0
  33. data/test/tarantool.cfg +62 -0
  34. data/test/tarantool.log +6 -0
  35. data/{spec/tarantool.cfg → test/tarantool_repl.cfg} +11 -5
  36. data/test/test_light_record.rb +48 -0
  37. data/test/test_query_block.rb +6 -0
  38. data/test/test_query_fiber.rb +7 -0
  39. data/test/test_record.rb +88 -0
  40. data/{spec/tarantool/composite_primary_key_spec.rb → test/test_record_composite_pk.rb} +5 -7
  41. data/test/test_space_array_block.rb +6 -0
  42. data/test/test_space_array_callback.rb +255 -0
  43. data/test/test_space_array_callback_nodef.rb +190 -0
  44. data/test/test_space_array_fiber.rb +7 -0
  45. data/test/test_space_hash_block.rb +6 -0
  46. data/test/test_space_hash_fiber.rb +7 -0
  47. metadata +78 -55
  48. data/Gemfile.lock +0 -54
  49. data/examples/em_simple.rb +0 -16
  50. data/examples/record.rb +0 -68
  51. data/examples/simple.rb +0 -13
  52. data/lib/tarantool/requests/call.rb +0 -20
  53. data/lib/tarantool/requests/delete.rb +0 -18
  54. data/lib/tarantool/requests/insert.rb +0 -19
  55. data/lib/tarantool/requests/ping.rb +0 -16
  56. data/lib/tarantool/requests/select.rb +0 -22
  57. data/lib/tarantool/requests/update.rb +0 -35
  58. data/lib/tarantool/requests.rb +0 -19
  59. data/lib/tarantool/serializers/integer.rb +0 -14
  60. data/lib/tarantool/serializers/string.rb +0 -14
  61. data/lib/tarantool/space.rb +0 -39
  62. data/spec/helpers/let.rb +0 -11
  63. data/spec/helpers/truncate.rb +0 -12
  64. data/spec/spec_helper.rb +0 -21
  65. data/spec/tarantool/em_spec.rb +0 -22
  66. data/spec/tarantool/record_spec.rb +0 -316
  67. data/spec/tarantool/request_spec.rb +0 -103
  68. data/tarantool.gemspec +0 -69
@@ -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
- def detect_index_no(keys)
80
- record.indexes.each.with_index do |v, i|
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
- self.tarantool = val
129
- end
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
- self.indexes = (indexes.dup << fields)
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 find(*keys)
162
- res = space.select(*keys)
163
- if keys.size == 1
164
- if res.tuple
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
- res.tuples.map { |tuple| from_server tuple }
171
- end
172
- end
173
-
174
- def select
175
- Select.new(self)
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
- end
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
- @__new_record = attributes.delete(:__new_record)
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 id
254
- primary = self.class.primary_index
255
- case primary
256
- when Array
257
- primary.map{ |p| attributes[p] }
258
- else
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 space
264
- self.class.space
265
- end
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
- def in_callbacks(&blk)
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
- space.insert(*to_tuple)
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 changed.size == 0
293
- ops = changed.inject([]) do |memo, k|
294
- k = k.to_sym
295
- memo << [field_no(k), :set, self.class._cast(k, attributes[k])] if attributes[k]
296
- memo
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
- def update_attribute(field, value)
311
- self.send("#{field}=", value)
312
- save
313
- end
314
-
315
- def update_attributes(attributes)
316
- attributes.each do |k, v|
317
- self.send("#{k}=", v)
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
- save
320
- end
321
-
322
- def increment(field, by = 1)
323
- space.update id, ops: [[field_no(field), :add, by]]
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
- space.delete id
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