├── .gitignore
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── lib
├── tarantool16.rb
└── tarantool16
│ ├── connection
│ ├── common.rb
│ ├── dumb.rb
│ └── response.rb
│ ├── consts.rb
│ ├── db.rb
│ ├── dumb_db.rb
│ ├── errors.rb
│ ├── query.rb
│ ├── response.rb
│ ├── schema.rb
│ └── version.rb
├── tarantool16.gemspec
└── test
├── bench_timeout.rb
├── config.lua
├── helper.rb
└── test_dumb.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle/
2 | /.yardoc
3 | /Gemfile.lock
4 | /_yardoc/
5 | /coverage/
6 | /doc/
7 | /pkg/
8 | /spec/reports/
9 | /test/*.log
10 | /test/run/
11 | /tmp/
12 | *.bundle
13 | *.so
14 | *.o
15 | *.a
16 | mkmf.log
17 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # Specify your gem's dependencies in tarantool16.gemspec
4 | gemspec
5 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014-2016 Sokolov Yura aka funny_falcon
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Tarantool16
2 |
3 | This is adapter for [tarantool](http://tarantool.org) version 1.6 and 1.7.
4 |
5 | (adapter for version <=1.5 is called [tarantool](https://github.org/tarantoool/tarantool-ruby))
6 |
7 | ## Installation
8 |
9 | Add this line to your application's Gemfile:
10 |
11 | ```ruby
12 | gem 'tarantool16'
13 | ```
14 |
15 | And then execute:
16 |
17 | $ bundle
18 |
19 | Or install it yourself as:
20 |
21 | $ gem install tarantool16
22 |
23 | ## Usage
24 |
25 | Currently only simple single threaded one-request-at-time connection implemented.
26 |
27 | ```ruby
28 | require 'tarantool16'
29 |
30 | db = Tarantool16.new host:'localhost:33013'
31 | #db = Tarantool16.new host:'localhost:33013', user:'tester', password:'testpass'
32 | #db = Tarantool16.new host:['tcp','localhost:33013']
33 | #db = Tarantool16.new host:['unix','path/to.sock']
34 | #db = Tarantool16.new unix:'path/to.sock'
35 |
36 | # select from '_space' space info about 'test' table
37 | # returns array of tuples as an array
38 | tar.get(272, ['test'], index: 2)
39 |
40 | # same, but return tuples as a hashes
41 | tar.get(272, ['test'], index: 2, hash: true)
42 |
43 | # same with names
44 | # Names and index descriptions are fetched from tarantool.
45 | # Index is autodetected by key names
46 | tar.get(:_space, {name: 'test'})
47 |
48 | # get all spaces
49 | tar.select(:_space, nil, iterator: :all)
50 | tar.select(:_space, nil, iterator: :all, hash: true)
51 |
52 | tar.select(:_space, [512], index: 0, iterator: :>=, hash: true)
53 |
54 | # override tuple field definition
55 | tar.define_fields(:test, [:id, :name, :value])
56 |
57 | tar.insert(:test, [1, 'buddy', [1,2,3]])
58 | tar.replace(:test, [1, 'buddy!', [2,3,4]])
59 | tar.update(:test, [1], [[':', 1, 6, 6, '?']])
60 | #tar.update(:test, [1], [[':', 1, 6, 6, '?']], index: 0)
61 | tar.delete(:test, [1])
62 | #tar.delete(:test, [1], index: 0)
63 |
64 | tar.insert(:test, {id: 1, name: 'buddy', value: [1,2,3]})
65 | tar.replace(:test, {id: 1, name: 'buddy!', value: [2,3,4]})
66 | tar.update(:test, {id: 1}, {name: [':', 6,6,'?']})
67 | tar.delete(:test, {id: 1})
68 |
69 | # note: currenlty there is no documented way to store field definition in an tarantool
70 | # but actually you can do it with this
71 | tar.update(:_space, {name: 'test'}, {format: [:=, [{name: :id, type: :num}, {name: :name, type: :str}, {name: :value, type: '*'}]]})
72 |
73 | ```
74 |
75 | ## Changelog
76 |
77 | 0.1.0 - breaking change: call now issues request in Tarantool17 format.
78 | use call16 to issue `call` request to Tarantool16.
79 | see tests for difference in returned results.
80 | 0.0.11 - change a way of unix socket option.
81 | since previos scheme were introduced 12 hours ago,
82 | i think it is still safe to change it.
83 | 0.0.10 - a bit of fixes
84 | 0.0.9 - Fix detection of update operation shape
85 | Add unix socket connection.
86 | 0.0.8 - Fix schema read from new tarantool versions
87 | Implement UPSERT
88 | 0.0.7 - Implement EVAL, fix REPLACE
89 | 0.0.6 - DumbConnection supports timeouts
90 |
91 | ## Contributing
92 |
93 | 1. Fork it ( https://github.com/funny-falcon/tarantool16/fork )
94 | 2. Create your feature branch (`git checkout -b my-new-feature`)
95 | 3. Commit your changes (`git commit -am 'Add some feature'`)
96 | 4. Push to the branch (`git push origin my-new-feature`)
97 | 5. Create a new Pull Request
98 |
99 | Or simply fill an issue
100 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "bundler/gem_tasks"
2 | require "rake/testtask"
3 |
4 | Rake::TestTask.new
5 |
6 |
--------------------------------------------------------------------------------
/lib/tarantool16.rb:
--------------------------------------------------------------------------------
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 | if opts[:unix] && opts[:host]
9 | raise "`:host` and `:unix` options are mutually exclusive"
10 | elsif opts[:unix]
11 | hosts = ["unix", opts[:unix]]
12 | elsif opts[:host]
13 | host = opts[:host]
14 | if Array === host
15 | hosts = host
16 | else
17 | host = [host, opts[:port]].compact.join(':')
18 | hosts = ["tcp", host]
19 | end
20 | end
21 | type = opts[:type] && opts[:type].to_s || 'dumb'
22 | case type
23 | when 'dumb'
24 | DumbDB.new hosts, opts
25 | else
26 | raise "Unknown DB type"
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/lib/tarantool16/connection/common.rb:
--------------------------------------------------------------------------------
1 | require 'msgpack'
2 | require 'openssl'
3 | require 'openssl/digest'
4 | require 'tarantool16/consts'
5 | require_relative 'response'
6 |
7 | module Tarantool16
8 | module Connection
9 | class Error < ::StandardError; end
10 | class ConnectionError < Error; end
11 | class CouldNotConnect < ConnectionError; end
12 | class Disconnected < ConnectionError; end
13 | class Timeout < ConnectionError; end
14 | class Retry < ConnectionError; end
15 | class UnexpectedResponse < Error; end
16 |
17 | module Common
18 | DEFAULT_RECONNECT = 0.2
19 | attr :host, :user
20 | def _init_common(host, opts)
21 | @host = host
22 | @user = opts[:user]
23 | if opts[:password]
24 | @passwd = ::OpenSSL::Digest::SHA1.digest(opts[:password])
25 | end
26 | if opts[:reconnect].nil?
27 | @reconnect_timeout = DEFAULT_RECONNECT
28 | @reconnect = true
29 | elsif Numeric === opts[:reconnect]
30 | @reconnect_timeout = opts[:reconnect]
31 | @reconnect = true
32 | else
33 | @reconnect = false
34 | end
35 | @timeout = opts[:timeout]
36 | @p = MessagePack::Packer.new
37 | @u = MessagePack::Unpacker.new
38 | @s = 0
39 | end
40 |
41 | if ::Process.respond_to?(:clock_gettime)
42 | if defined?(::Process::CLOCK_MONOTONIC_COARSE)
43 | CLOCK_KIND = ::Process::CLOCK_MONOTONIC_COARSE
44 | elsif defined?(::Process::CLOCK_MONOTONIC_FAST)
45 | CLOCK_KIND = ::Process::CLOCK_MONOTONIC_FAST
46 | elsif defined?(::Process::CLOCK_MONOTONIC)
47 | CLOCK_KIND = ::Process::CLOCK_MONOTONIC
48 | else
49 | CLOCK_KIND = ::Process::CLOCK_REALTIME
50 | end
51 | def now_f
52 | ::Process.clock_gettime(CLOCK_KIND)
53 | end
54 | else
55 | def now_f
56 | Time.now.to_f
57 | end
58 | end
59 |
60 | def next_sync
61 | @s = @s % 0x3fffffff + 1
62 | end
63 |
64 | def format_request(code, sync, body)
65 | @p.write(0x01020304).
66 | write_map_header(2).
67 | write(IPROTO_CODE).write(code).
68 | write(IPROTO_SYNC).write(sync).
69 | write(body)
70 | sz = @p.size - 5
71 | str = @p.to_s
72 | @p.clear
73 | # fix bigendian size
74 | str.setbyte(4, sz)
75 | str.setbyte(3, sz>>8)
76 | str.setbyte(2, sz>>16)
77 | str.setbyte(1, sz>>24)
78 | str
79 | ensure
80 | @p.clear
81 | end
82 |
83 | def format_authenticate(user, pass1, salt)
84 | pass2 = ::OpenSSL::Digest::SHA1.digest(pass1)
85 | scramble = ::OpenSSL::Digest::SHA1.new(salt).update(pass2).digest
86 | pints = pass1.unpack('L*')
87 | sints = scramble.unpack('L*')
88 | pints.size.times{|i| sints[i] ^= pints[i] }
89 | packed = sints.pack('L*')
90 | # tarantool waits packed as a string, so that force msgpack to pack as string
91 | packed.force_encoding('utf-8')
92 | format_request(REQUEST_TYPE_AUTHENTICATE, next_sync, {
93 | IPROTO_USER_NAME => user,
94 | IPROTO_TUPLE => [ 'chap-sha1', packed ]
95 | })
96 | end
97 |
98 | def parse_greeting(greeting)
99 | @greeting = greeting[0, 64]
100 | @salt = greeting[64..-1].unpack('m')[0][0,20]
101 | end
102 |
103 | def parse_size(str)
104 | @u.feed(str)
105 | n = @u.read
106 | unless Integer === n
107 | return UnexpectedResponse.new("wanted response size, got #{n.inspect}")
108 | end
109 | n
110 | rescue ::MessagePack::UnpackError, ::MessagePack::TypeError => e
111 | @u.reset
112 | e
113 | end
114 |
115 | def parse_response(str)
116 | sync = nil
117 | @u.feed(str)
118 | n = @u.read_map_header
119 | while n > 0
120 | cd = @u.read
121 | vl = @u.read
122 | case cd
123 | when IPROTO_SYNC
124 | sync = vl
125 | when IPROTO_CODE
126 | code = vl
127 | end
128 | n -= 1
129 | end
130 | if sync == nil
131 | return Option.error(nil, UnexpectedResponse, "Mailformed response: no sync")
132 | elsif code == nil
133 | return Option.error(nil, UnexpectedResponse, "Mailformed response: no code for sync=#{sync}")
134 | end
135 | begin
136 | bmap = @u.read
137 | body = bmap[IPROTO_DATA] || bmap[IPROTO_ERROR]
138 | rescue EOFError
139 | body = nil
140 | end
141 | Option.ok(sync, code, body)
142 | rescue ::MessagePack::UnpackError, ::MessagePack::TypeError => e
143 | @u.reset
144 | Option.error(sync, e, nil)
145 | end
146 |
147 | def _unix?
148 | @host[0] == 'unix' || @host[0] == :unix
149 | end
150 |
151 | def _tcp?
152 | @host[0] == 'tcp' || @host[0] == :tcp
153 | end
154 |
155 | def _unix_sock_path
156 | @host[1]
157 | end
158 |
159 | def host_port
160 | @host[1] =~ /^(.*):([^:]+)$/
161 | [$1, $2.to_i]
162 | end
163 |
164 | def _ipv6?
165 | @host[1].count(':') > 1
166 | end
167 |
168 | def _insert(space_no, tuple, cb)
169 | req = {IPROTO_SPACE_ID => space_no,
170 | IPROTO_TUPLE => tuple}
171 | send_request(REQUEST_TYPE_INSERT, req, cb)
172 | end
173 |
174 | def _replace(space_no, tuple, cb)
175 | req = {IPROTO_SPACE_ID => space_no,
176 | IPROTO_TUPLE => tuple}
177 | send_request(REQUEST_TYPE_REPLACE, req, cb)
178 | end
179 |
180 | def _delete(space_no, index_no, key, cb)
181 | req = {IPROTO_SPACE_ID => space_no,
182 | IPROTO_INDEX_ID => index_no,
183 | IPROTO_KEY => key}
184 | send_request(REQUEST_TYPE_DELETE, req, cb)
185 | end
186 |
187 | def _select(space_no, index_no, key, offset, limit, iterator, cb)
188 | iterator ||= ::Tarantool16::ITERATOR_EQ
189 | unless Integer === iterator
190 | iterator = ::Tarantool16.iter(iterator)
191 | end
192 | req = {IPROTO_SPACE_ID => space_no,
193 | IPROTO_INDEX_ID => index_no,
194 | IPROTO_KEY => key || [],
195 | IPROTO_OFFSET => offset,
196 | IPROTO_LIMIT => limit,
197 | IPROTO_ITERATOR => iterator}
198 | send_request(REQUEST_TYPE_SELECT, req, cb)
199 | end
200 |
201 | def _update(space_no, index_no, key, ops, cb)
202 | req = {IPROTO_SPACE_ID => space_no,
203 | IPROTO_INDEX_ID => index_no,
204 | IPROTO_KEY => key,
205 | IPROTO_TUPLE => ops}
206 | send_request(REQUEST_TYPE_UPDATE, req, cb)
207 | end
208 |
209 | def _upsert(space_no, index_no, tuple_key, ops, cb)
210 | req = {IPROTO_SPACE_ID => space_no,
211 | IPROTO_INDEX_ID => index_no,
212 | IPROTO_TUPLE => tuple_key,
213 | IPROTO_DEF_DUPLE => ops}
214 | send_request(REQUEST_TYPE_UPSERT, req, cb)
215 | end
216 |
217 | def _call(name, args, cb)
218 | req = {IPROTO_FUNCTION_NAME => name,
219 | IPROTO_TUPLE => args}
220 | send_request(REQUEST_TYPE_CALL17, req, cb)
221 | end
222 |
223 | def _call16(name, args, cb)
224 | req = {IPROTO_FUNCTION_NAME => name,
225 | IPROTO_TUPLE => args}
226 | send_request(REQUEST_TYPE_CALL16, req, cb)
227 | end
228 |
229 | def _eval(expr, args, cb)
230 | req = {IPROTO_EXPR => expr,
231 | IPROTO_TUPLE => args}
232 | send_request(REQUEST_TYPE_EVAL, req, cb)
233 | end
234 |
235 | REQ_EMPTY = {}.freeze
236 | def _ping(cb)
237 | send_request(REQUEST_TYPE_PING, REQ_EMPTY, cb)
238 | end
239 | end
240 | end
241 | end
242 |
--------------------------------------------------------------------------------
/lib/tarantool16/connection/dumb.rb:
--------------------------------------------------------------------------------
1 | require 'socket'
2 | require 'io/wait'
3 | begin
4 | #require 'kgio'
5 | rescue LoadError
6 | end
7 |
8 | require_relative 'common'
9 | module Tarantool16
10 | module Connection
11 | class Dumb
12 | include Common
13 |
14 | def initialize(host, opts = {})
15 | _init_common(host, opts)
16 | @reconnect_time = now_f - 1
17 | @socket = nil
18 | _connect
19 | end
20 |
21 | def send_request(code, body, cb)
22 | _connect
23 | syswrite format_request(code, next_sync, body)
24 | written = true
25 | response = _read_response
26 | rescue ::Errno::EPIPE, Retry => e
27 | @socket.close rescue nil
28 | @socket = nil
29 | if !written && @retry && @reconnect
30 | @retry = false
31 | retry
32 | end
33 | cb.call Option.error(nil, Disconnected, e.message)
34 | rescue StandardError => e
35 | cb.call Option.error(nil, e, nil)
36 | else
37 | cb.call response
38 | end
39 |
40 | def disconnect
41 | if @socket
42 | @socket.close rescue nil
43 | @socket = nil
44 | @s = 0
45 | end
46 | end
47 |
48 | def close
49 | @reconnect = false
50 | if @socket
51 | @socket.close rescue nil
52 | @socket = false
53 | @s = 0
54 | end
55 | end
56 |
57 | def connected?
58 | @socket
59 | end
60 |
61 | def could_be_connected?
62 | @socket || (@socket.nil? && (@reconnect || @reconnect_time < now_f))
63 | end
64 |
65 | private
66 | def _connect
67 | return if @socket
68 | unless could_be_connected?
69 | raise Disconnected, "connection is closed"
70 | end
71 | if _unix?
72 | @socket = Socket.unix(_unix_sock_path)
73 | elsif _tcp?
74 | @socket = Socket.new((_ipv6? ? Socket::AF_INET6 : Socket::AF_INET), Socket::SOCK_STREAM)
75 | @socket.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
76 | @socket.sync = true
77 | sockaddr = Socket.pack_sockaddr_in(*host_port.reverse)
78 | @retry = @reconnect
79 | if @timeout
80 | _connect_nonblock(sockaddr)
81 | else
82 | @socket.connect(sockaddr)
83 | end
84 | else
85 | raise "unsupported host option: #{@host.inspect}"
86 | end
87 |
88 | greeting = _read(IPROTO_GREETING_SIZE)
89 | unless greeting && greeting.bytesize == IPROTO_GREETING_SIZE
90 | raise Disconnected, "mailformed greeting #{greeting.inspect}"
91 | end
92 | @nbuf = "\x00\x00\x00\x00\x00".force_encoding('BINARY')
93 | parse_greeting greeting
94 | authenticate if @user
95 | rescue ::Errno::ECONNREFUSED, ::Errno::EPIPE, Disconnected, Timeout => e
96 | @socket.close rescue nil
97 | @socket = nil
98 | if !@reconnect
99 | @socket = false
100 | @s = 0
101 | else
102 | @reconnect_time = now_f + @reconnect_timeout
103 | end
104 | raise CouldNotConnect, e.message
105 | end
106 |
107 | def _connect_nonblock(sockaddr)
108 | expire = now_f + @timeout
109 | begin
110 | @socket.connect_nonblock(sockaddr)
111 | rescue IO::WaitWritable
112 | t = [@socket]
113 | IO.select(t, t, nil, expire - now_f)
114 | begin
115 | @socket.connect_nonblock(sockaddr)
116 | rescue Errno::EISCONN
117 | end
118 | end
119 | end
120 |
121 | def syswrite(req)
122 | unless @timeout
123 | if @socket.syswrite(req) != req.bytesize
124 | raise Retry, "Could not write message"
125 | end
126 | else
127 | expire = now_f
128 | begin
129 | until req.empty?
130 | n = @socket.write_nonblock(req)
131 | req = req[n..-1]
132 | end
133 | rescue IO::WaitWritable
134 | nf = now_f
135 | if expire <= nf
136 | raise Timeout, "response timeouted"
137 | else
138 | _wait_writable(expire - nf)
139 | retry
140 | end
141 | rescue Errno::EINTR
142 | retry
143 | end
144 | end
145 | end
146 |
147 | def authenticate
148 | syswrite format_authenticate(@user, @passwd, @salt)
149 | _read_response.raise_if_error!
150 | end
151 |
152 | def _read_response
153 | str = _read(5, @nbuf)
154 | unless str && str.bytesize == 5
155 | # check if we sent request or not
156 | begin
157 | @socket.send("\x00", 0)
158 | rescue ::Errno::EPIPE
159 | # if OS knows that socket is closed, then request were not sent
160 | raise Retry
161 | else
162 | # otherwise request were sent
163 | raise Disconnected, "disconnected while read length"
164 | end
165 | end
166 | n = parse_size(str)
167 | raise n unless ::Integer === n
168 | resp = _read(n)
169 | raise Disconnected, "disconnected while read response" unless resp && resp.bytesize == n
170 | r = parse_response(resp)
171 | if r.ok? && r.sync != @s
172 | raise UnexpectedResponse, "sync mismatch: #{@s} != #{r.sync}"
173 | end
174 | r
175 | end
176 |
177 | def _read(n, buf=nil)
178 | unless @timeout
179 | buf ? @socket.read(n, buf) : @socket.read(n)
180 | else
181 | expire = now_f + @timeout
182 | rbuf = nil
183 | while n > 0
184 | case tbuf = _read_nonblock(n, buf)
185 | when String
186 | if rbuf
187 | rbuf << tbuf
188 | else
189 | rbuf = tbuf
190 | end
191 | buf = nil
192 | n -= tbuf.size
193 | when :wait_readable
194 | nf = now_f
195 | if expire <= nf
196 | raise Timeout, "response timeouted"
197 | else
198 | _wait_readable(expire - nf)
199 | end
200 | when nil
201 | raise EOFError
202 | end
203 | end
204 | return rbuf
205 | end
206 | end
207 |
208 | if (RUBY_VERSION.split('.').map(&:to_i) <=> [2,1]) >= 0
209 | EXCEPTION_FALSE = {exception: false}.freeze
210 | def _read_nonblock(n, buf)
211 | if buf
212 | @socket.read_nonblock(n, buf, **EXCEPTION_FALSE)
213 | else
214 | @socket.read_nonblock(n, **EXCEPTION_FALSE)
215 | end
216 | end
217 | elsif defined?(Kgio)
218 | def _read_nonblock(n, buf)
219 | return buf ? Kgio.tryread(@socket, n, buf) : Kgio.tryread(@socket, n)
220 | end
221 | else
222 | def _read_nonblock(n, buf)
223 | begin
224 | if buf
225 | @socket.read_nonblock(n, buf)
226 | else
227 | @socket.read_nonblock(n)
228 | end
229 | rescue IO::WaitReadable
230 | return :wait_readable
231 | rescue Errno::EINTR
232 | retry
233 | end
234 | end
235 | end
236 |
237 | if RUBY_ENGINE == 'jruby'
238 | def _wait_readable(timeout)
239 | IO.select([@socket], nil, nil, timeout)
240 | end
241 | else
242 | def _wait_readable(timeout)
243 | @socket.wait(timeout)
244 | end
245 | end
246 | if IO.instance_methods.include?(:wait_writable)
247 | def _wait_writable(timeout)
248 | @socket.wait_writable(timeout)
249 | end
250 | else
251 | def _wait_writable(timeout)
252 | t = [@socket]
253 | IO.select(t, t, nil, expire - now_f)
254 | end
255 | end
256 | end
257 | end
258 | end
259 |
--------------------------------------------------------------------------------
/lib/tarantool16/connection/response.rb:
--------------------------------------------------------------------------------
1 | require 'tarantool16/errors'
2 | require 'tarantool16/response'
3 |
4 | module Tarantool16
5 | module Connection
6 | class Option
7 | attr :sync, :error, :data
8 | def initialize(sync, err, data)
9 | @sync = sync
10 | @error = err
11 | @data = data
12 | end
13 |
14 | def ok?
15 | !@error
16 | end
17 |
18 | def raise_if_error!
19 | raise @error if @error
20 | end
21 |
22 | def self.ok(sync, code, data)
23 | if code == 0
24 | new(sync, nil, data)
25 | else
26 | new(sync, ::Tarantool16::DBError.with_code_message(code&(REQUEST_TYPE_ERROR-1), data), nil)
27 | end
28 | end
29 |
30 | def self.error(sync, err, message = nil)
31 | if err.is_a? Class
32 | err = err.new message
33 | end
34 | new(sync, err, nil)
35 | end
36 |
37 | def inspect
38 | s = @sync ? " sync=#{sync}" : ""
39 | if ok?
40 | "