tarantool16 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7438bad2e35b3f56d1949664f4e51a1c866550ed
4
+ data.tar.gz: 802d8dd75d560a73b7b30bf8815908d49a484262
5
+ SHA512:
6
+ metadata.gz: 8cae0fe3c55291a6f2c503f7a4611f950e28d2679e8950b149697090ec185d6f0bfd875ccaf002899aa9b6020cda218c12578ad10a547ea00b3264d67e409d46
7
+ data.tar.gz: 7281531d361f6bd6032c36d37c40527aacf0e9607efbc14cd1da6615d952f4e37994b35f1f7930cf62d8c67f7cafab895b72679dce367f8a678d84a7e51cb15c
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in tarantool16.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 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.
data/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # Tarantool16
2
+
3
+ This is adapter for (tarantool)[http://tarantool.org] version 1.6.
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
+
33
+ # select from '_space' space info about 'test' table
34
+ # returns array of tuples as an array
35
+ tar.get(272, ['test'], index: 2)
36
+
37
+ # same, but return tuples as a hashes
38
+ tar.get(272, ['test'], index: 2, hash: true)
39
+
40
+ # same with names
41
+ # Names and index descriptions are fetched from tarantool.
42
+ # Index is autodetected by key names
43
+ tar.get(:_space, {name: 'test'})
44
+
45
+ # get all spaces
46
+ tar.select(:_space, nil, iterator: :all)
47
+ tar.select(:_space, nil, iterator: :all, hash: true)
48
+
49
+ tar.select(:_space, [512], index: 0, iterator: :>=, hash: true)
50
+
51
+ # override tuple field definition
52
+ tar.define_fields(:test, [:id, :name, :value])
53
+
54
+ tar.insert(:test, [1, 'buddy', [1,2,3]])
55
+ tar.replace(:test, [1, 'buddy!', [2,3,4]])
56
+ tar.update(:test, [1], [[':', 1, 6, 6, '?']])
57
+ #tar.update(:test, [1], [[':', 1, 6, 6, '?']], index: 0)
58
+ tar.delete(:test, [1])
59
+ #tar.delete(:test, [1], index: 0)
60
+
61
+ tar.insert(:test, {id: 1, name: 'buddy', value: [1,2,3]})
62
+ tar.replace(:test, {id: 1, name: 'buddy!', value: [2,3,4]})
63
+ tar.update(:test, {id: 1}, {name: [':', 6,6,'?']})
64
+ tar.delete(:test, {id: 1})
65
+
66
+ # note: currenlty there is no documented way to store field definition in an tarantool
67
+ # but actually you can do it with this
68
+ tar.update(:_space, {name: 'test'}, {format: [:=, [{name: :id, type: :num}, {name: :name, type: :str}, {name: :value, type: '*'}]]})
69
+
70
+ ```
71
+
72
+ ## Contributing
73
+
74
+ 1. Fork it ( https://github.com/funny-falcon/tarantool16/fork )
75
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
76
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
77
+ 4. Push to the branch (`git push origin my-new-feature`)
78
+ 5. Create a new Pull Request
79
+
80
+ Or simply fill an issue
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,196 @@
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 Retry < ConnectionError; end
14
+ class UnexpectedResponse < Error; end
15
+
16
+ module Common
17
+ DEFAULT_RECONNECT = 0.2
18
+ attr :host, :user
19
+ def _init_common(host, opts)
20
+ @host = host
21
+ @user = opts[:user]
22
+ if opts[:password]
23
+ @passwd = ::OpenSSL::Digest::SHA1.digest(opts[:password])
24
+ end
25
+ if opts[:reconnect].nil?
26
+ @reconnect_timeout = DEFAULT_RECONNECT
27
+ @reconnect = true
28
+ elsif Numeric === opts[:reconnect]
29
+ @reconnect_timeout = opts[:reconnect]
30
+ @reconnect = true
31
+ else
32
+ @reconnect = false
33
+ end
34
+ @p = MessagePack::Packer.new
35
+ @u = MessagePack::Unpacker.new
36
+ @s = 0
37
+ end
38
+
39
+ if ::Process.respond_to?(:clock_gettime)
40
+ if defined?(::Process::CLOCK_MONOTONIC_COARSE)
41
+ CLOCK_KIND = ::Process::CLOCK_MONOTONIC_COARSE
42
+ elsif defined?(::Process::CLOCK_MONOTONIC_FAST)
43
+ CLOCK_KIND = ::Process::CLOCK_MONOTONIC_FAST
44
+ elsif defined?(::Process::CLOCK_MONOTONIC)
45
+ CLOCK_KIND = ::Process::CLOCK_MONOTONIC
46
+ else
47
+ CLOCK_KIND = ::Process::CLOCK_REALTIME
48
+ end
49
+ def now_f
50
+ ::Process.clock_gettime(CLOCK_KIND)
51
+ end
52
+ else
53
+ def now_f
54
+ Time.now.to_f
55
+ end
56
+ end
57
+
58
+ def next_sync
59
+ @s = @s % 0x3fffffff + 1
60
+ end
61
+
62
+ def format_request(code, sync, body)
63
+ @p.write(0x01020304).
64
+ write_map_header(2).
65
+ write(IPROTO_CODE).write(code).
66
+ write(IPROTO_SYNC).write(sync).
67
+ write(body)
68
+ sz = @p.size - 5
69
+ str = @p.to_s
70
+ @p.clear
71
+ # fix bigendian size
72
+ str.setbyte(4, sz)
73
+ str.setbyte(3, sz>>8)
74
+ str.setbyte(2, sz>>16)
75
+ str.setbyte(1, sz>>24)
76
+ str
77
+ end
78
+
79
+ def format_authenticate(user, pass1, salt)
80
+ pass2 = ::OpenSSL::Digest::SHA1.digest(pass1)
81
+ scramble = ::OpenSSL::Digest::SHA1.new(salt).update(pass2).digest
82
+ pints = pass1.unpack('L*')
83
+ sints = scramble.unpack('L*')
84
+ pints.size.times{|i| sints[i] ^= pints[i] }
85
+ format_request(REQUEST_TYPE_AUTHENTICATE, next_sync, {
86
+ IPROTO_USER_NAME => user,
87
+ IPROTO_TUPLE => [ 'chap-sha1', sints.pack('L*') ]
88
+ })
89
+ end
90
+
91
+ def parse_greeting(greeting)
92
+ @greeting = greeting[0, 64]
93
+ @salt = greeting[64..-1].unpack('m')[0][0,20]
94
+ end
95
+
96
+ def parse_size(str)
97
+ @u.feed(str)
98
+ n = @u.read
99
+ unless Integer === n
100
+ return UnexpectedResponse.new("wanted response size, got #{n.inspect}")
101
+ end
102
+ n
103
+ rescue ::MessagePack::UnpackError, ::MessagePack::TypeError => e
104
+ e
105
+ end
106
+
107
+ def parse_response(str)
108
+ sync = nil
109
+ @u.feed(str)
110
+ n = @u.read_map_header
111
+ while n > 0
112
+ cd = @u.read
113
+ vl = @u.read
114
+ case cd
115
+ when IPROTO_SYNC
116
+ sync = vl
117
+ when IPROTO_CODE
118
+ code = vl
119
+ end
120
+ n -= 1
121
+ end
122
+ if sync == nil
123
+ return Option.error(nil, UnexpectedResponse, "Mailformed response: no sync")
124
+ elsif code == nil
125
+ return Option.error(nil, UnexpectedResponse, "Mailformed response: no code for sync=#{sync}")
126
+ end
127
+ unless @u.buffer.empty?
128
+ bmap = @u.read
129
+ body = bmap[IPROTO_DATA] || bmap[IPROTO_ERROR]
130
+ else
131
+ body = nil
132
+ end
133
+ Option.ok(sync, code, body)
134
+ rescue ::MessagePack::UnpackError, ::MessagePack::TypeError => e
135
+ Option.error(sync, e, nil)
136
+ end
137
+
138
+ def host_port
139
+ h, p = @host.split(':')
140
+ [h, p.to_i]
141
+ end
142
+
143
+ def _insert(space_no, tuple, cb)
144
+ req = {IPROTO_SPACE_ID => space_no,
145
+ IPROTO_TUPLE => tuple}
146
+ send_request(REQUEST_TYPE_INSERT, req, cb)
147
+ end
148
+
149
+ def _replace(space_no, tuple, cb)
150
+ req = {IPROTO_SPACE_ID => space_no,
151
+ IPROTO_TUPLE => tuple}
152
+ send_request(REQUEST_TYPE_REPLACE, req, cb)
153
+ end
154
+
155
+ def _delete(space_no, index_no, key, cb)
156
+ req = {IPROTO_SPACE_ID => space_no,
157
+ IPROTO_INDEX_ID => index_no,
158
+ IPROTO_KEY => key}
159
+ send_request(REQUEST_TYPE_DELETE, req, cb)
160
+ end
161
+
162
+ def _select(space_no, index_no, key, offset, limit, iterator, cb)
163
+ iterator ||= ::Tarantool16::ITERATOR_EQ
164
+ unless Integer === iterator
165
+ iterator = ::Tarantool16.iter(iterator)
166
+ end
167
+ req = {IPROTO_SPACE_ID => space_no,
168
+ IPROTO_INDEX_ID => index_no,
169
+ IPROTO_KEY => key || [],
170
+ IPROTO_OFFSET => offset,
171
+ IPROTO_LIMIT => limit,
172
+ IPROTO_ITERATOR => iterator}
173
+ send_request(REQUEST_TYPE_SELECT, req, cb)
174
+ end
175
+
176
+ def _update(space_no, index_no, key, ops, cb)
177
+ req = {IPROTO_SPACE_ID => space_no,
178
+ IPROTO_INDEX_ID => index_no,
179
+ IPROTO_KEY => key,
180
+ IPROTO_TUPLE => ops}
181
+ send_request(REQUEST_TYPE_UPDATE, req, cb)
182
+ end
183
+
184
+ def _call(name, args, cb)
185
+ req = {IPROTO_FUNCTION_NAME => name,
186
+ IPROTO_TUPLE => args}
187
+ send_request(REQUEST_TYPE_CALL, req, cb)
188
+ end
189
+
190
+ REQ_EMPTY = {}.freeze
191
+ def _ping(cb)
192
+ send_request(REQUEST_TYPE_PING, REQ_EMPTY, cb)
193
+ end
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,122 @@
1
+ require 'socket'
2
+ require_relative 'common'
3
+ module Tarantool16
4
+ module Connection
5
+ class Dumb
6
+ include Common
7
+
8
+ def initialize(host, opts = {})
9
+ _init_common(host, opts)
10
+ @nbuf = "\x00".b * 5
11
+ @reconnect_time = now_f - 1
12
+ @socket = nil
13
+ _connect
14
+ end
15
+
16
+ def send_request(code, body, cb)
17
+ _connect
18
+ syswrite format_request(code, next_sync, body)
19
+ written = true
20
+ response = _read_response
21
+ rescue ::Errno::EPIPE, Retry => e
22
+ @socket = nil
23
+ if !written && @retry && @reconnect
24
+ @retry = false
25
+ retry
26
+ end
27
+ cb.call Option.error(nil, Disconnected, e.message)
28
+ rescue StandardError => e
29
+ cb.call Option.error(nil, e, nil)
30
+ else
31
+ cb.call response
32
+ end
33
+
34
+ def disconnect
35
+ if @socket
36
+ @socket.close rescue nil
37
+ @socket = nil
38
+ @s = 0
39
+ end
40
+ end
41
+
42
+ def close
43
+ @reconnect = false
44
+ if @socket
45
+ @socket.close rescue nil
46
+ @socket = false
47
+ @s = 0
48
+ end
49
+ end
50
+
51
+ def connected?
52
+ @socket
53
+ end
54
+
55
+ def could_be_connected?
56
+ @socket || (@socket.nil? && (@reconnect || @reconnect_time < now_f))
57
+ end
58
+
59
+ private
60
+ def _connect
61
+ return if @socket
62
+ unless could_be_connected?
63
+ raise Disconnected, "connection is closed"
64
+ end
65
+ @socket = TCPSocket.new(*host_port)
66
+ @socket.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
67
+ @retry = @reconnect
68
+ greeting = @socket.read(IPROTO_GREETING_SIZE)
69
+ unless greeting && greeting.bytesize == IPROTO_GREETING_SIZE
70
+ raise Disconnected, "mailformed greeting #{greeting.inspect}"
71
+ end
72
+ parse_greeting greeting
73
+ authenticate if @user
74
+ rescue ::Errno::ECONNREFUSED, ::Errno::EPIPE, Disconnected => e
75
+ @socket = nil
76
+ if !@reconnect
77
+ @socket = false
78
+ @s = 0
79
+ else
80
+ @reconnect_time = now_f + @reconnect_timeout
81
+ end
82
+ raise CouldNotConnect, e.message
83
+ end
84
+
85
+ def syswrite(req)
86
+ if @socket.syswrite(req) != req.bytesize
87
+ raise Retry, "Could not write message"
88
+ end
89
+ end
90
+
91
+ def authenticate
92
+ syswrite format_authenticate(@user, @passwd, @salt)
93
+ _read_response.raise_if_error!
94
+ end
95
+
96
+ def _read_response
97
+ str = @socket.read(5, @nbuf)
98
+ unless str && str.bytesize == 5
99
+ # check if we sent request or not
100
+ begin
101
+ @socket.send("\x00", 0)
102
+ rescue ::Errno::EPIPE
103
+ # if OS knows that socket is closed, then request were not sent
104
+ raise Retry
105
+ else
106
+ # otherwise request were sent
107
+ raise Disconnected, "disconnected while read length"
108
+ end
109
+ end
110
+ n = parse_size(str)
111
+ raise n unless ::Integer === n
112
+ resp = @socket.read(n)
113
+ raise Disconnected, "disconnected while read response" unless resp && resp.bytesize == n
114
+ r = parse_response(resp)
115
+ if r.ok? && r.sync != @s
116
+ raise UnexpectedResponse, "sync mismatch: #{@s} != #{r.sync}"
117
+ end
118
+ r
119
+ end
120
+ end
121
+ end
122
+ end