tarantool 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.
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "eventmachine"
4
+ gem "em-synchrony"
5
+ gem "activemodel"
6
+
7
+ gem "rr"
8
+ gem "yajl-ruby"
9
+ gem "bson"
10
+ gem "bson_ext"
11
+
12
+ gem "ruby-debug19"
13
+
14
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,55 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ tarantool (0.1)
5
+ activemodel (>= 3.1, < 4.0)
6
+ em-synchrony (>= 1.0.0, < 2.0)
7
+ eventmachine (>= 1.0.0.beta.4, < 2.0.0)
8
+
9
+ GEM
10
+ remote: http://rubygems.org/
11
+ specs:
12
+ activemodel (3.1.1)
13
+ activesupport (= 3.1.1)
14
+ builder (~> 3.0.0)
15
+ i18n (~> 0.6)
16
+ activesupport (3.1.1)
17
+ multi_json (~> 1.0)
18
+ archive-tar-minitar (0.5.2)
19
+ bson (1.4.0)
20
+ bson_ext (1.4.0)
21
+ builder (3.0.0)
22
+ columnize (0.3.5)
23
+ em-synchrony (1.0.0)
24
+ eventmachine (>= 1.0.0.beta.1)
25
+ eventmachine (1.0.0.beta.4)
26
+ i18n (0.6.0)
27
+ linecache19 (0.5.12)
28
+ ruby_core_source (>= 0.1.4)
29
+ multi_json (1.0.3)
30
+ rr (1.0.4)
31
+ ruby-debug-base19 (0.11.25)
32
+ columnize (>= 0.3.1)
33
+ linecache19 (>= 0.5.11)
34
+ ruby_core_source (>= 0.1.4)
35
+ ruby-debug19 (0.11.6)
36
+ columnize (>= 0.3.1)
37
+ linecache19 (>= 0.5.11)
38
+ ruby-debug-base19 (>= 0.11.19)
39
+ ruby_core_source (0.1.5)
40
+ archive-tar-minitar (>= 0.5.2)
41
+ yajl-ruby (1.1.0)
42
+
43
+ PLATFORMS
44
+ ruby
45
+
46
+ DEPENDENCIES
47
+ activemodel
48
+ bson
49
+ bson_ext
50
+ em-synchrony
51
+ eventmachine
52
+ rr
53
+ ruby-debug19
54
+ tarantool!
55
+ yajl-ruby
data/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ /*
2
+ * Copyright (C) 2011 Mail.RU
3
+ *
4
+ * Redistribution and use in source and binary forms, with or without
5
+ * modification, are permitted provided that the following conditions
6
+ * are met:
7
+ * 1. Redistributions of source code must retain the above copyright
8
+ * notice, this list of conditions and the following disclaimer.
9
+ * 2. Redistributions in binary form must reproduce the above copyright
10
+ * notice, this list of conditions and the following disclaimer in the
11
+ * documentation and/or other materials provided with the distribution.
12
+ *
13
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16
+ * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
17
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23
+ * SUCH DAMAGE.
24
+ */
data/README.md ADDED
@@ -0,0 +1,116 @@
1
+ # About
2
+
3
+ Its asynchronyous EventMachine ruby client for [Tarantool Key-Value Storage](github.com/mailru/tarantool).
4
+
5
+ # Install
6
+
7
+ ```bash
8
+ gem install tarantool
9
+ ```
10
+
11
+ ```ruby
12
+ require 'tarantool'
13
+ # or
14
+ require 'tarantool/synchrony'
15
+ # or
16
+ require 'tarantool/record'
17
+ ```
18
+
19
+ # Usage
20
+
21
+ Before all requests you must configure client:
22
+
23
+ ```ruby
24
+ Tarantool.configure host: 'locahost', port: 33013, space_no: 0
25
+ ```
26
+
27
+ Low level part of client can work in two mode: EM deferrables and EM-Synchrony.
28
+
29
+ EM deferrables:
30
+
31
+ ```ruby
32
+ req = Tarantool.insert 'prepor', 'Andrew', 'ceo@prepor.ru'
33
+ req.callback do
34
+ req = Tarantool.select 'prepor'
35
+ req.callback do |res|
36
+ puts "Name: #{res.tuple[1].to_s}; Email: #{res.tuple[2].to_s}"
37
+ end
38
+ end
39
+ req.errback do |err|
40
+ puts "Error while insert: #{err}"
41
+ end
42
+ ```
43
+
44
+ Synchrony mode:
45
+
46
+ ```ruby
47
+ require 'tarantool/synchrony'
48
+
49
+ Tarantool.insert 'prepor', 'Andrew', 'ceo@prepor.ru'
50
+ res = Tarantool.select 'prepor'
51
+ puts "Name: #{res.tuple[1].to_s}; Email: #{res.tuple[2].to_s}"
52
+ ```
53
+
54
+ High level part of client implements ActiveModel API: Callbacks, Validations, Serialization, Dirty. Its autocast types, choose right index for query and something more. So code looks like this:
55
+
56
+ ```ruby
57
+ require 'tarantool/record'
58
+ require 'tarantool/serializers/bson'
59
+ class User < Tarantool::Record
60
+ field :login, :string
61
+ field :name, :string
62
+ field :email, :string
63
+ field :apples_count, :integer, default: 0
64
+ field :info, :bson
65
+ index :name, :email
66
+
67
+ validates_length_of(:login, minimum: 3)
68
+
69
+ after_create do
70
+ # after work!
71
+ end
72
+ end
73
+
74
+ # Now attribute positions are not important.
75
+ User.create login: 'prepor', email: 'ceo@prepor.ru', name: 'Andrew'
76
+ User.create login: 'ruden', name: 'Andrew', email: 'rudenkoco@gmail.com'
77
+
78
+ # find by primary key login
79
+ User.find 'prepor'
80
+ # first 2 users with name Andrew
81
+ User.where(name: 'Andrew').limit(2).all
82
+ # second user with name Andrew
83
+ User.where(name: 'Andrew').offset(1).limit(1).all
84
+ # user with name Andrew and email ceo@prepor.ru
85
+ User.where(name: 'Andrew', email: 'ceo@prepor.ru').first
86
+ # raise exception, becouse we can't select query started from not first part of index
87
+ begin
88
+ User.where(email: 'ceo@prepor.ru')
89
+ rescue Tarantool::ArgumentError => e
90
+ end
91
+ # increment field apples_count by one. Its atomic operation via native Tarantool interface
92
+ User.find('prepor').increment :apples_count
93
+
94
+ # update only dirty attributes
95
+ user = User.find('prepor')
96
+ user.name = "Petr"
97
+ user.save
98
+
99
+ # field serialization to bson
100
+ user.info = { 'bio' => "hi!", 'age' => 23, 'hobbies' => ['mufa', 'tuka'] }
101
+ user.save
102
+ User.find('prepor').info['bio'] # => 'hi!'
103
+ # delete record
104
+ user.destroy
105
+ ```
106
+
107
+ On record definition step, fields order are important, in that order they will be store in Tarantool. By default primary key is first field. `index` method just mapping to your Tarantool schema, client doesn't modify schema for you.
108
+
109
+ # TODO
110
+
111
+ * `#first`, `#all` without keys, batches requests via box.select_range
112
+ * `#where` chains
113
+ * admin-socket protocol
114
+ * safe to add fields to exist model
115
+ * Hash, Array and lambdas as default values
116
+ * timers to response, reconnect strategies
data/Rakefile ADDED
@@ -0,0 +1,131 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'date'
4
+
5
+ #############################################################################
6
+ #
7
+ # Helper functions
8
+ #
9
+ #############################################################################
10
+
11
+ def name
12
+ @name ||= Dir['*.gemspec'].first.split('.').first
13
+ end
14
+
15
+ def version
16
+ line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
17
+ line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
18
+ end
19
+
20
+ def date
21
+ Date.today.to_s
22
+ end
23
+
24
+ def rubyforge_project
25
+ name
26
+ end
27
+
28
+ def gemspec_file
29
+ "#{name}.gemspec"
30
+ end
31
+
32
+ def gem_file
33
+ "#{name}-#{version}.gem"
34
+ end
35
+
36
+ def replace_header(head, header_name)
37
+ head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
38
+ end
39
+
40
+ #############################################################################
41
+ #
42
+ # Standard tasks
43
+ #
44
+ #############################################################################
45
+
46
+
47
+ task :default => :spec
48
+ require 'rake/testtask'
49
+ Rake::TestTask.new(:spec) do |t|
50
+ t.libs << 'spec'
51
+ t.pattern = 'spec/**/*_spec.rb'
52
+ t.verbose = false
53
+ end
54
+
55
+ desc "Generate RCov test coverage and open in your browser"
56
+ task :coverage do
57
+ require 'rcov'
58
+ sh "rm -fr coverage"
59
+ sh "rcov test/test_*.rb"
60
+ sh "open coverage/index.html"
61
+ end
62
+
63
+ desc "Open an irb session preloaded with this library"
64
+ task :console do
65
+ sh "irb -rubygems -r ./lib/#{name}.rb"
66
+ end
67
+
68
+
69
+ #############################################################################
70
+ #
71
+ # Packaging tasks
72
+ #
73
+ #############################################################################
74
+
75
+ desc "Create tag v#{version} and build and push #{gem_file} to Rubygems"
76
+ task :release => :build do
77
+ unless `git branch` =~ /^\* master$/
78
+ puts "You must be on the master branch to release!"
79
+ exit!
80
+ end
81
+ sh "git commit --allow-empty -a -m 'Release #{version}'"
82
+ sh "git tag v#{version}"
83
+ sh "git push origin master"
84
+ sh "git push origin v#{version}"
85
+ sh "gem push pkg/#{name}-#{version}.gem"
86
+ end
87
+
88
+ desc "Build #{gem_file} into the pkg directory"
89
+ task :build => :gemspec do
90
+ sh "mkdir -p pkg"
91
+ sh "gem build #{gemspec_file}"
92
+ sh "mv #{gem_file} pkg"
93
+ end
94
+
95
+ desc "Generate #{gemspec_file}"
96
+ task :gemspec => :validate do
97
+ # read spec file and split out manifest section
98
+ spec = File.read(gemspec_file)
99
+ head, manifest, tail = spec.split(" # = MANIFEST =\n")
100
+
101
+ # replace name version and date
102
+ replace_header(head, :name)
103
+ replace_header(head, :version)
104
+ replace_header(head, :date)
105
+ #comment this out if your rubyforge_project has a different name
106
+ replace_header(head, :rubyforge_project)
107
+
108
+ # determine file list from git ls-files
109
+ files = `git ls-files`.
110
+ split("\n").
111
+ sort.
112
+ reject { |file| file =~ /^\./ }.
113
+ reject { |file| file =~ /^(rdoc|pkg)/ }.
114
+ map { |file| " #{file}" }.
115
+ join("\n")
116
+
117
+ # piece file back together and write
118
+ manifest = " s.files = %w[\n#{files}\n ]\n"
119
+ spec = [head, manifest, tail].join(" # = MANIFEST =\n")
120
+ File.open(gemspec_file, 'w') { |io| io.write(spec) }
121
+ puts "Updated #{gemspec_file}"
122
+ end
123
+
124
+ desc "Validate #{gemspec_file}"
125
+ task :validate do
126
+ libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
127
+ unless Dir['VERSION*'].empty?
128
+ puts "A `VERSION` file at root level violates Gem best practices."
129
+ exit!
130
+ end
131
+ end
@@ -0,0 +1,21 @@
1
+ require 'bundler'
2
+ ENV['BUNDLE_GEMFILE'] = File.expand_path('../../Gemfile', __FILE__)
3
+ Bundler.setup
4
+
5
+ require 'tarantool'
6
+
7
+ EM.run do
8
+ Tarantool.configure host: 'localhost', port: 33013, space_no: 0
9
+ req = Tarantool.insert 'prepor', 'Andrew', 'ceo@prepor.ru'
10
+ req.callback do
11
+ req = Tarantool.select 'prepor'
12
+ req.callback do |res|
13
+ puts "Name: #{res.tuple[1].to_s}; Email: #{res.tuple[2].to_s}"
14
+ EM.stop
15
+ end
16
+ end
17
+ req.errback do |err|
18
+ puts "Error while insert: #{err}"
19
+ EM.stop
20
+ end
21
+ end
@@ -0,0 +1,56 @@
1
+ require 'bundler'
2
+ ENV['BUNDLE_GEMFILE'] = File.expand_path('../../Gemfile', __FILE__)
3
+ Bundler.setup
4
+
5
+ require 'tarantool/record'
6
+ require 'tarantool/serializers/bson'
7
+ class User < Tarantool::Record
8
+ field :login, :string
9
+ field :name, :string
10
+ field :email, :string
11
+ field :apples_count, :integer, default: 0
12
+ field :info, :bson
13
+ index :name, :email
14
+
15
+ validates_length_of(:login, minimum: 3)
16
+
17
+ after_create do
18
+ # after work!
19
+ end
20
+ end
21
+
22
+ EM.synchrony do
23
+ Tarantool.configure host: 'localhost', port: 33013, space_no: 0
24
+ # Now attribute positions are not important.
25
+ User.create login: 'prepor', email: 'ceo@prepor.ru', name: 'Andrew'
26
+ User.create login: 'ruden', name: 'Andrew', email: 'rudenkoco@gmail.com'
27
+
28
+ # find by primary key login
29
+ User.find 'prepor'
30
+ # first 2 users with name Andrew
31
+ User.where(name: 'Andrew').limit(2).all
32
+ # second user with name Andrew
33
+ User.where(name: 'Andrew').offset(1).limit(1).all
34
+ # user with name Andrew and email ceo@prepor.ru
35
+ User.where(name: 'Andrew', email: 'ceo@prepor.ru').first
36
+ # raise exception, becouse we can't select from not first part of index
37
+ begin
38
+ User.where(email: 'ceo@prepor.ru')
39
+ rescue Tarantool::ArgumentError => e
40
+ end
41
+ # increment field apples_count by one. Its atomic operation vie native Tarantool interface
42
+ User.find('prepor').increment :apples_count
43
+
44
+ # update only dirty attributes
45
+ user = User.find('prepor')
46
+ user.name = "Petr"
47
+ user.save
48
+
49
+ # field serialization to bson
50
+ user.info = { 'bio' => "hi!", 'age' => 23, 'hobbies' => ['mufa', 'tuka'] }
51
+ user.save
52
+ User.find('prepor').info['bio'] # => 'hi!'
53
+ user.destroy
54
+ puts "Ok!"
55
+ EM.stop
56
+ end
@@ -0,0 +1,13 @@
1
+ require 'bundler'
2
+ ENV['BUNDLE_GEMFILE'] = File.expand_path('../../Gemfile', __FILE__)
3
+ Bundler.setup
4
+
5
+ require 'tarantool/synchrony'
6
+
7
+ EM.synchrony do
8
+ Tarantool.configure host: 'localhost', port: 33013, space_no: 0
9
+ Tarantool.insert 'prepor', 'Andrew', 'ceo@prepor.ru'
10
+ res = Tarantool.select 'prepor'
11
+ puts "Name: #{res.tuple[1].to_s}; Email: #{res.tuple[2].to_s}"
12
+ EM.stop
13
+ end
@@ -0,0 +1,67 @@
1
+ module EventMachine
2
+ module Protocols
3
+ module FixedHeaderAndBody
4
+
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ end
8
+
9
+ module ClassMethods
10
+ def header_size(size = nil)
11
+ if size
12
+ @_header_size = size
13
+ else
14
+ @_header_size
15
+ end
16
+ end
17
+ end
18
+
19
+ attr_accessor :header, :body
20
+
21
+ def receive_data(data)
22
+ @buffer ||= ''
23
+ offset = 0
24
+ while (chunk = data[offset, _needed_size - @buffer.size]).size > 0 || _needed_size == 0
25
+ @buffer += chunk
26
+ offset += chunk.size
27
+ if @buffer.size == _needed_size
28
+ case _state
29
+ when :receive_header
30
+ @_state = :receive_body
31
+ receive_header @buffer
32
+ when :receive_body
33
+ @_state = :receive_header
34
+ receive_body @buffer
35
+ end
36
+ @buffer = ''
37
+ end
38
+ end
39
+ end
40
+
41
+ def receive_header(header)
42
+ # for override
43
+ end
44
+
45
+ def body_size
46
+ # for override
47
+ end
48
+
49
+ def receive_body(body)
50
+ # for override
51
+ end
52
+
53
+ def _needed_size
54
+ case _state
55
+ when :receive_header
56
+ self.class.header_size
57
+ when :receive_body
58
+ body_size
59
+ end
60
+ end
61
+
62
+ def _state
63
+ @_state ||= :receive_header
64
+ end
65
+ end
66
+ end
67
+ end