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