tarantool16 0.0.1
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 +7 -0
- data/.gitignore +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +80 -0
- data/Rakefile +2 -0
- data/lib/tarantool16/connection/common.rb +196 -0
- data/lib/tarantool16/connection/dumb.rb +122 -0
- data/lib/tarantool16/connection/response.rb +47 -0
- data/lib/tarantool16/consts.rb +94 -0
- data/lib/tarantool16/db.rb +198 -0
- data/lib/tarantool16/dumb_db.rb +118 -0
- data/lib/tarantool16/errors.rb +82 -0
- data/lib/tarantool16/query.rb +8 -0
- data/lib/tarantool16/response.rb +36 -0
- data/lib/tarantool16/schema.rb +269 -0
- data/lib/tarantool16/version.rb +3 -0
- data/lib/tarantool16.rb +17 -0
- data/tarantool16.gemspec +25 -0
- metadata +104 -0
@@ -0,0 +1,82 @@
|
|
1
|
+
|
2
|
+
module Tarantool16
|
3
|
+
class Error < ::StandardError; end
|
4
|
+
class ConnectionError < Error; end
|
5
|
+
DBErrors = {}
|
6
|
+
class SchemaError < Error; end
|
7
|
+
class DBError < Error
|
8
|
+
class KnownDBError < DBError
|
9
|
+
class << self
|
10
|
+
attr_accessor :return_code
|
11
|
+
end
|
12
|
+
def return_code
|
13
|
+
self.class.return_code
|
14
|
+
end
|
15
|
+
end
|
16
|
+
class UnknownDBError < DBError
|
17
|
+
attr_accessor :return_code
|
18
|
+
end
|
19
|
+
|
20
|
+
{
|
21
|
+
1=> :ER_ILLEGAL_PARAMS,
|
22
|
+
2=> :ER_MEMORY_ISSUE,
|
23
|
+
3=> :ER_TUPLE_FOUND,
|
24
|
+
4=> :ER_TUPLE_NOT_FOUND,
|
25
|
+
5=> :ER_UNSUPPORTED,
|
26
|
+
6=> :ER_NONMASTER,
|
27
|
+
7=> :ER_SECONDARY,
|
28
|
+
8=> :ER_INJECTION,
|
29
|
+
9=> :ER_CREATE_SPACE,
|
30
|
+
10=> :ER_SPACE_EXISTS,
|
31
|
+
11=> :ER_DROP_SPACE,
|
32
|
+
12=> :ER_ALTER_SPACE,
|
33
|
+
13=> :ER_INDEX_TYPE,
|
34
|
+
14=> :ER_MODIFY_INDEX,
|
35
|
+
15=> :ER_LAST_DROP,
|
36
|
+
16=> :ER_TUPLE_FORMAT_LIMIT,
|
37
|
+
17=> :ER_DROP_PRIMARY_KEY,
|
38
|
+
18=> :ER_KEY_FIELD_TYPE,
|
39
|
+
19=> :ER_EXACT_MATCH,
|
40
|
+
20=> :ER_INVALID_MSGPACK,
|
41
|
+
21=> :ER_PROC_RET,
|
42
|
+
22=> :ER_TUPLE_NOT_ARRAY,
|
43
|
+
23=> :ER_FIELD_TYPE,
|
44
|
+
24=> :ER_FIELD_TYPE_MISMATCH,
|
45
|
+
25=> :ER_SPLICE,
|
46
|
+
26=> :ER_ARG_TYPE,
|
47
|
+
27=> :ER_TUPLE_IS_TOO_LONG,
|
48
|
+
28=> :ER_UNKNOWN_UPDATE_OP,
|
49
|
+
29=> :ER_UPDATE_FIELD,
|
50
|
+
30=> :ER_FIBER_STACK,
|
51
|
+
31=> :ER_KEY_PART_COUNT,
|
52
|
+
32=> :ER_PROC_LUA,
|
53
|
+
33=> :ER_NO_SUCH_PROC,
|
54
|
+
34=> :ER_NO_SUCH_TRIGGER,
|
55
|
+
35=> :ER_NO_SUCH_INDEX,
|
56
|
+
36=> :ER_NO_SUCH_SPACE,
|
57
|
+
37=> :ER_NO_SUCH_FIELD,
|
58
|
+
38=> :ER_SPACE_ARITY,
|
59
|
+
39=> :ER_INDEX_ARITY,
|
60
|
+
40=> :ER_WAL_IO,
|
61
|
+
41=> :ER_MORE_THAN_ONE_TUPLE,
|
62
|
+
}.each do |n, s|
|
63
|
+
klass = Class.new(KnownDBError)
|
64
|
+
klass.return_code = n
|
65
|
+
Tarantool16::DBErrors[n] = klass
|
66
|
+
Tarantool16.const_set(s, klass)
|
67
|
+
end
|
68
|
+
def self.with_code_message(n, m="")
|
69
|
+
if klass = DBErrors[n]
|
70
|
+
klass.new(m)
|
71
|
+
else
|
72
|
+
e = UnknownDBError.new(m)
|
73
|
+
e.return_code = n
|
74
|
+
e
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def inspect
|
79
|
+
"<#{self.class.name} return_code=#{return_code} message=#{message}>"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Tarantool16
|
2
|
+
class Option
|
3
|
+
attr :error, :data
|
4
|
+
def initialize(err, data)
|
5
|
+
@error = err
|
6
|
+
@data = data
|
7
|
+
end
|
8
|
+
|
9
|
+
def ok?
|
10
|
+
!@error
|
11
|
+
end
|
12
|
+
|
13
|
+
def raise_if_error!
|
14
|
+
raise @error if @error
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.ok(data)
|
18
|
+
new(nil, data)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.error(err, message = nil)
|
22
|
+
if err.is_a? Class
|
23
|
+
err = err.new message
|
24
|
+
end
|
25
|
+
new(err, nil)
|
26
|
+
end
|
27
|
+
|
28
|
+
def inspect
|
29
|
+
if ok?
|
30
|
+
"<Option data=#{@data.inspect}>"
|
31
|
+
else
|
32
|
+
"<Option error=#{@error.inspect}>"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,269 @@
|
|
1
|
+
require_relative 'response'
|
2
|
+
require_relative 'consts'
|
3
|
+
module Tarantool16
|
4
|
+
class SchemaSpace
|
5
|
+
attr :sid, :name, :indices, :fields
|
6
|
+
def initialize(sid, name, fields)
|
7
|
+
@sid = sid
|
8
|
+
@name = name
|
9
|
+
@has_tail = false
|
10
|
+
self.fields = fields
|
11
|
+
end
|
12
|
+
|
13
|
+
# imitate Option
|
14
|
+
def ok?; true; end
|
15
|
+
def data; self; end
|
16
|
+
|
17
|
+
def fields=(flds)
|
18
|
+
@field_names = {}
|
19
|
+
@fields = []
|
20
|
+
@has_tail = false
|
21
|
+
flds.each_with_index do |fld, i|
|
22
|
+
if @has_tail
|
23
|
+
raise "no fields allowed after tail: #{flds}"
|
24
|
+
end
|
25
|
+
case fld
|
26
|
+
when String, Symbol
|
27
|
+
name = fld.to_s
|
28
|
+
type = nil
|
29
|
+
when Array
|
30
|
+
name, type = fld
|
31
|
+
when Hash
|
32
|
+
name = fld['name'] || fld[:name]
|
33
|
+
type = fld['type'] || fld[:type]
|
34
|
+
tail = fld['tail'] || fld[:tail]
|
35
|
+
end
|
36
|
+
name_s = name.to_sym
|
37
|
+
field = Field.new(name_s, i, type)
|
38
|
+
@field_names[name] = field
|
39
|
+
@field_names[name_s] = field
|
40
|
+
@field_names[i] = field
|
41
|
+
@fields << field
|
42
|
+
@has_tail = true if tail
|
43
|
+
end
|
44
|
+
if @index_defs
|
45
|
+
self.indices= @index_defs
|
46
|
+
end
|
47
|
+
flds
|
48
|
+
end
|
49
|
+
|
50
|
+
def indices=(inds)
|
51
|
+
@index_defs = inds
|
52
|
+
@index_names = {}
|
53
|
+
@indices = []
|
54
|
+
@_fields_2_ino = {}
|
55
|
+
inds.each do |name, nom, type, parts|
|
56
|
+
if @fields && @fields.size > parts.max
|
57
|
+
part_names = parts.map{|p| @fields[p].name}
|
58
|
+
else
|
59
|
+
part_names = []
|
60
|
+
end
|
61
|
+
index = Index.new(name, nom, type, parts, part_names)
|
62
|
+
@index_names[name] = index
|
63
|
+
@index_names[name.to_sym] = index
|
64
|
+
@index_names[nom] = index
|
65
|
+
@indices[nom] = index
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def indices?
|
70
|
+
@indices && !@indices.empty?
|
71
|
+
end
|
72
|
+
|
73
|
+
def get_ino(ino, key, iter, cb)
|
74
|
+
if ino.nil?
|
75
|
+
unless key.is_a?(Hash)
|
76
|
+
opt = Option.error(SchemaError, "Could not detect index without field names and iterator: #{key.inspect} in #{name_sid}")
|
77
|
+
return cb.call(opt)
|
78
|
+
end
|
79
|
+
unless iter.is_a?(Integer)
|
80
|
+
iter = ::Tarantool16.iter(iter)
|
81
|
+
end
|
82
|
+
# key should be Hash here
|
83
|
+
keys = key.keys
|
84
|
+
_ino = @_fields_2_ino[keys]
|
85
|
+
if _ino
|
86
|
+
ind = @indices[_ino]
|
87
|
+
return yield(_ino, ind.map_key(key))
|
88
|
+
elsif _ino == false
|
89
|
+
opt = Option.error(SchemaError, "Could not detect index for fields #{key.keys} in #{name_sid}")
|
90
|
+
return cb.call(opt)
|
91
|
+
end
|
92
|
+
|
93
|
+
fields = keys.map{|fld|
|
94
|
+
case fld
|
95
|
+
when Integer
|
96
|
+
fld
|
97
|
+
when Symbol, String
|
98
|
+
@field_names[fld].pos
|
99
|
+
else
|
100
|
+
return cb.call(Option.error(SchemaError, "Unknown field #{fld.inspect} in query key #{key.inspect}"))
|
101
|
+
end
|
102
|
+
}
|
103
|
+
|
104
|
+
index = nil
|
105
|
+
for ind in @indices
|
106
|
+
next unless ind
|
107
|
+
first_fields = ind.parts[0,fields.size]
|
108
|
+
if ind.can_iterator?(iter)
|
109
|
+
if fields == first_fields
|
110
|
+
index = ind
|
111
|
+
break
|
112
|
+
elsif (fields - first_fields).empty?
|
113
|
+
index = ind
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
if index
|
118
|
+
@_fields_2_ino[keys.freeze] = index.pos
|
119
|
+
yield index.pos, index.map_key(key)
|
120
|
+
else
|
121
|
+
@_fields_2_ino[keys.freeze] = false
|
122
|
+
cb.call(Option.error(SchemaError, "Could not detect index for fields #{key.keys} in #{name_sid}"))
|
123
|
+
end
|
124
|
+
elsif index = @index_names[ino]
|
125
|
+
yield index.pos, index.map_key(key)
|
126
|
+
else
|
127
|
+
cb.call(Option.error(SchemaError, "Could not find index #{ino} for spacefor fields #{key.keys}"))
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def tuple2hash(ar)
|
132
|
+
raise "No fields defined for #{name_sid}" unless @fields && !@fields.empty?
|
133
|
+
res = {}
|
134
|
+
i = 0
|
135
|
+
flds = @fields
|
136
|
+
s = flds.size - (@has_tail ? 1 : 0)
|
137
|
+
while i < s
|
138
|
+
res[flds[i].name] = ar[i]
|
139
|
+
i += 1
|
140
|
+
end
|
141
|
+
if @has_tail
|
142
|
+
tail = flds[s]
|
143
|
+
unless tail.type.is_a?(Array)
|
144
|
+
res[tail.name] = ar[s..-1]
|
145
|
+
else
|
146
|
+
res[tail.name] = ar[s..-1].each_slice(tail.type.size).to_a
|
147
|
+
end
|
148
|
+
end
|
149
|
+
res
|
150
|
+
end
|
151
|
+
|
152
|
+
def name_sid
|
153
|
+
@_np ||= "space #{@name}:#{@sid}"
|
154
|
+
end
|
155
|
+
|
156
|
+
def map_tuple(tuple)
|
157
|
+
row = []
|
158
|
+
unless @has_tail
|
159
|
+
tuple.each_key do |k|
|
160
|
+
field = @field_names[k]
|
161
|
+
row[field.pos] = tuple[k]
|
162
|
+
end
|
163
|
+
else
|
164
|
+
tail = @fields.last
|
165
|
+
tuple.each do |k|
|
166
|
+
field = @field_names[k]
|
167
|
+
val = tuple[k]
|
168
|
+
if field.equal? tail
|
169
|
+
unless tail.type.is_a?(Array)
|
170
|
+
row[field.pos,0] = val
|
171
|
+
else
|
172
|
+
row[field.pos,0] = val.flatten(1)
|
173
|
+
end
|
174
|
+
else
|
175
|
+
row[field.pos] = tuple[k]
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
row
|
180
|
+
end
|
181
|
+
|
182
|
+
def map_ops(ops)
|
183
|
+
ops.map do |op|
|
184
|
+
case _1 = op[1]
|
185
|
+
when Integer
|
186
|
+
op
|
187
|
+
when Symbol, String
|
188
|
+
_op = op.dup
|
189
|
+
_op[1] = @field_names[_1].pos
|
190
|
+
_op
|
191
|
+
when Array
|
192
|
+
_1.dup.insert(1, @field_names[op[0]].pos)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def wrap_cb(cb)
|
198
|
+
CallbackWrapper.new(self, cb)
|
199
|
+
end
|
200
|
+
|
201
|
+
class Field
|
202
|
+
attr :name, :pos, :type
|
203
|
+
def initialize(name, pos, type)
|
204
|
+
@name = name
|
205
|
+
@pos = pos
|
206
|
+
@type = type
|
207
|
+
end
|
208
|
+
|
209
|
+
def to_s
|
210
|
+
"<Fields #{@name}@#{pos}>"
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
class Index
|
215
|
+
attr :name, :pos, :parts, :type, :part_names, :part_positions
|
216
|
+
ITERS = {
|
217
|
+
tree: (ITERATOR_EQ..ITERATOR_GT).freeze,
|
218
|
+
hash: [ITERATOR_ALL, ITERATOR_EQ, ITERATOR_GT].freeze,
|
219
|
+
bitset: [ITERATOR_ALL, ITERATOR_EQ, ITERATOR_BITS_ALL_SET,
|
220
|
+
ITERATOR_BITS_ANY_SET, ITERATOR_BITS_ALL_NOT_SET].freeze,
|
221
|
+
rtree: [ITERATOR_ALL, ITERATOR_EQ, ITERATOR_GT, ITERATOR_GE, ITERATOR_LT, ITERATOR_LE,
|
222
|
+
ITERATOR_RTREE_OVERLAPS, ITERATOR_RTREE_NEIGHBOR].freeze
|
223
|
+
}
|
224
|
+
def initialize(name, pos, type, parts, part_names)
|
225
|
+
@name = name
|
226
|
+
@pos = pos
|
227
|
+
@type = type.downcase.to_sym
|
228
|
+
@iters = ITERS[@type] or raise "Unknown index type #{type.inspect}"
|
229
|
+
@parts = parts
|
230
|
+
@part_names = part_names
|
231
|
+
@part_positions = {}
|
232
|
+
parts.each_with_index{|p, i| @part_positions[p] = i}
|
233
|
+
part_names.each_with_index{|p, i|
|
234
|
+
@part_positions[p.to_s] = i
|
235
|
+
@part_positions[p.to_sym] = i
|
236
|
+
}
|
237
|
+
end
|
238
|
+
|
239
|
+
def can_iterator?(iter)
|
240
|
+
@iters.include?(iter)
|
241
|
+
end
|
242
|
+
|
243
|
+
def map_key(key)
|
244
|
+
return key if key.is_a?(Array)
|
245
|
+
res = []
|
246
|
+
positions = @part_positions
|
247
|
+
key.each_key do |k|
|
248
|
+
res[positions[k]] = key[k]
|
249
|
+
end
|
250
|
+
res
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
class CallbackWrapper
|
255
|
+
def initialize(space, cb)
|
256
|
+
@space = space
|
257
|
+
@cb = cb
|
258
|
+
end
|
259
|
+
|
260
|
+
def call(r)
|
261
|
+
if r.ok?
|
262
|
+
sp = @space
|
263
|
+
r = Option.ok(r.data.map{|row| sp.tuple2hash(row)})
|
264
|
+
end
|
265
|
+
@cb.call r
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
data/lib/tarantool16.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require "tarantool16/version"
|
2
|
+
require "tarantool16/db"
|
3
|
+
|
4
|
+
module Tarantool16
|
5
|
+
autoload :DumbDB, 'tarantool16/dumb_db'
|
6
|
+
def self.new(opts = {})
|
7
|
+
opts = opts.dup
|
8
|
+
hosts = opts[:host]
|
9
|
+
type = opts[:type] && opts[:type].to_s || 'dumb'
|
10
|
+
case type
|
11
|
+
when 'dumb'
|
12
|
+
DumbDB.new hosts, opts
|
13
|
+
else
|
14
|
+
raise "Unknown DB type"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/tarantool16.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'tarantool16/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "tarantool16"
|
8
|
+
spec.version = Tarantool16::VERSION
|
9
|
+
spec.authors = ["Sokolov Yura aka funny_falcon"]
|
10
|
+
spec.email = ["funny.falcon@gmail.com"]
|
11
|
+
spec.summary = %q{adapter for Tarantool 1.6}
|
12
|
+
spec.description = %q{adapter for Tarantool 1.6}
|
13
|
+
spec.homepage = "https://github.com/funny-falcon/tarantool16-ruby"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
|
24
|
+
spec.add_dependency "msgpack"
|
25
|
+
end
|