em-handlersocket 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
data/.rspec ADDED
File without changes
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in em-handlersocket.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,27 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ em-handlersocket (0.1.0)
5
+ eventmachine
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ diff-lcs (1.1.2)
11
+ eventmachine (0.12.11)
12
+ rspec (2.4.0)
13
+ rspec-core (~> 2.4.0)
14
+ rspec-expectations (~> 2.4.0)
15
+ rspec-mocks (~> 2.4.0)
16
+ rspec-core (2.4.0)
17
+ rspec-expectations (2.4.0)
18
+ diff-lcs (~> 1.1.2)
19
+ rspec-mocks (2.4.0)
20
+
21
+ PLATFORMS
22
+ ruby
23
+
24
+ DEPENDENCIES
25
+ em-handlersocket!
26
+ eventmachine
27
+ rspec
data/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # EM-HandlerSocket
2
+
3
+ EventMachine client for HandlerSocket MySQL plugin for direct read/write of InnoDB tables. This plugin bypasses any/all SQL parsing, query planner, and other MySQL locks, while giving you the advantage of InnoDB persistence, speed, and the network daemon! Best of all, HandlerSocket plugin runs alongside your regular MySQL engine, which means you still have the full advantage of a SQL console at your disposal.
4
+
5
+ [HandlerSocket: The NoSQL MySQL & Ruby](http://www.igvita.com/2011/01/14/handlersocket-the-nosql-mysql-ruby/)
6
+
7
+ ## Features
8
+
9
+ - Plays nicely with the (EventMachine) reactor
10
+ - No native extensions
11
+ - Pipelined processing
12
+
13
+ ## Example: Open & Read from an InnoDB index
14
+
15
+ Open the PRIMARY key index on the widgets.user InnoDB table, and query for id == 1.
16
+
17
+ EM.run {
18
+ c = EM::HandlerSocket.new
19
+ idx = {:id => 0, :db => 'widgets', :table => 'user', :index_name => 'PRIMARY', :columns => 'user_name'}
20
+
21
+ d = c.open_index(idx)
22
+ d.callback do
23
+
24
+ d = c.query(:id => 0, :op => '=', :key => '1')
25
+ d.errback { p 'uh oh!' }
26
+ d.callback do |data|
27
+ p ["received data", data]
28
+ end
29
+ end
30
+ }
31
+
32
+ ## Todo
33
+
34
+ - Add support for insert/update/delete
35
+
36
+ ## Resources
37
+
38
+ - [HandlerSocket Protocol](https://github.com/ahiguti/HandlerSocket-Plugin-for-MySQL/blob/master/docs-en/protocol.en.txt)
39
+ - [HandlerSocket @ DeNA](http://yoshinorimatsunobu.blogspot.com/2010/10/using-mysql-as-nosql-story-for.html)
40
+
41
+ # License
42
+
43
+ (The MIT License)
44
+ Copyright © 2011 Ilya Grigorik
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "em-handlersocket/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "em-handlersocket"
7
+ s.version = EventMachine::HandlerSocket::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Ilya Grigorik"]
10
+ s.email = ["ilya@igvita.com"]
11
+ s.homepage = "http://github.com/igrigorik/em-handlersocket"
12
+ s.summary = "Asynchronous (EventMachine) HandlerSocket client"
13
+ s.description = s.summary
14
+
15
+ s.rubyforge_project = "em-handlersocket"
16
+
17
+ s.add_dependency "eventmachine"
18
+ s.add_development_dependency "rspec"
19
+
20
+ s.files = `git ls-files`.split("\n")
21
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
23
+ s.require_paths = ["lib"]
24
+ end
@@ -0,0 +1,80 @@
1
+ module EventMachine
2
+ module HandlerSocket
3
+
4
+ class Deferrable < EM::DefaultDeferrable
5
+ attr_accessor :lines, :buffer
6
+
7
+ def initialize(lines)
8
+ @lines = lines
9
+ @buffer = []
10
+ end
11
+
12
+ def recieve(line)
13
+ status, cols, data = line.chomp.split("\t")
14
+ @buffer.push data
15
+
16
+ # non-zero response code indicates error
17
+ if status.to_i != 0
18
+ fail
19
+ return true
20
+ end
21
+
22
+ done?
23
+ end
24
+
25
+ # non zero response code, or we've processed all lines
26
+ def done?; (@buffer.size == @lines); end
27
+
28
+ def succeed
29
+ super(@buffer)
30
+ end
31
+ end
32
+
33
+ class Client < EventMachine::Connection
34
+ include EventMachine::Deferrable
35
+ include EventMachine::Protocols::LineProtocol
36
+
37
+ def initialize
38
+ @deferrables = []
39
+ end
40
+
41
+ def connection_completed
42
+ succeed
43
+ end
44
+
45
+ def open_index(opts)
46
+ execute([['P', opts[:id], opts[:db], opts[:table], opts[:index_name], opts[:columns]]])
47
+ end
48
+
49
+ def query(*queries)
50
+ execute(queries.map{|q| [q[:id], q[:op], q[:key].size, q[:key], q[:limit], q[:offset]].compact })
51
+ end
52
+
53
+ def execute(cmd, &blk)
54
+ callback { send(cmd) }
55
+ add_deferrable(cmd.size, &blk)
56
+ end
57
+
58
+ private
59
+
60
+ def send(data)
61
+ send_data data.map {|d| d.join("\t")}.join("\n") + "\n"
62
+ end
63
+
64
+ def receive_line(line)
65
+ if @deferrables.first.recieve(line)
66
+ @deferrables.shift.succeed
67
+ end
68
+ end
69
+
70
+ def add_deferrable(lines, &blk)
71
+ df = Deferrable.new(lines)
72
+ df.callback &blk
73
+
74
+ @deferrables.push(df)
75
+ df
76
+ end
77
+
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,5 @@
1
+ module EventMachine
2
+ module HandlerSocket
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,16 @@
1
+ require 'eventmachine'
2
+ require 'em/protocols/line_protocol'
3
+ require 'em-handlersocket/client'
4
+
5
+ module EventMachine
6
+ module HandlerSocket
7
+ def self.new(options = {})
8
+ opt = {
9
+ :host => '127.0.0.1',
10
+ :port => '9998'
11
+ }.merge(options)
12
+
13
+ EventMachine.connect(opt[:host], opt[:port], EventMachine::HandlerSocket::Client)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,122 @@
1
+ require 'helper'
2
+
3
+ #
4
+ # create database widgets;
5
+ # CREATE TABLE user (
6
+ # user_id INT UNSIGNED PRIMARY KEY,
7
+ # user_name VARCHAR(50),
8
+ # user_email VARCHAR(255),
9
+ # created DATETIME
10
+ # ) ENGINE=InnoDB;
11
+ #
12
+ # insert into user (user_id, user_name, user_email, created) values (1, 'Ilya', 'ilya@igvita.com', '2010-01-01');
13
+ # insert into user (user_id, user_name, user_email, created) values (2, 'John', 'john@example.com', '2010-01-02');
14
+ # insert into user (user_id, user_name, user_email, created) values (3, 'Bob', 'bob@example.com', '2010-01-03');
15
+ #
16
+
17
+ describe EventMachine::HandlerSocket do
18
+
19
+ it "should connect to HandlerSocket server" do
20
+ EM.run {
21
+ c = EM::HandlerSocket.new(:host => '127.0.0.1', :port => '9998')
22
+ c.class.should == EventMachine::HandlerSocket::Client
23
+
24
+ EM.stop
25
+ }
26
+ end
27
+
28
+ it "should connect to localhost, read-only port by default" do
29
+ EM.run {
30
+ c = EM::HandlerSocket.new
31
+ c.class.should == EventMachine::HandlerSocket::Client
32
+
33
+ EM.stop
34
+ }
35
+ end
36
+
37
+ it "should open an index via raw interface" do
38
+ EM.run {
39
+ c = EM::HandlerSocket.new
40
+
41
+ df = c.execute([['P', '0', 'widgets', 'user', 'PRIMARY', 'user_name,user_email,created']])
42
+ df.callback { EM.stop }
43
+ df.errback { fail }
44
+ }
45
+ end
46
+
47
+ it "should invoke errback on bad query" do
48
+ EM.run {
49
+ c = EM::HandlerSocket.new
50
+
51
+ df = c.execute([['P', '0', 'badDB', 'user', 'PRIMARY', 'user_name,user_email,created']])
52
+ df.callback { fail }
53
+ df.errback { EM.stop }
54
+ }
55
+ end
56
+
57
+ it "should open an index" do
58
+ EM.run {
59
+ c = EM::HandlerSocket.new
60
+ idx = {:id => 0, :db => 'widgets', :table => 'user', :index_name => 'PRIMARY', :columns => 'user_name'}
61
+
62
+ d = c.open_index(idx)
63
+ d.callback do
64
+ EM.stop
65
+ end
66
+ }
67
+ end
68
+
69
+ it "should fetch a single record" do
70
+ EM.run {
71
+ c = EM::HandlerSocket.new
72
+ idx = {:id => 0, :db => 'widgets', :table => 'user', :index_name => 'PRIMARY', :columns => 'user_name'}
73
+
74
+ d = c.open_index(idx)
75
+ d.callback do
76
+
77
+ d = c.query(:id => 0, :op => '=', :key => '1')
78
+ d.callback do |data|
79
+ data.should == ['Ilya']
80
+ EM.stop
81
+ end
82
+ end
83
+ }
84
+ end
85
+
86
+ it "should fetch multiple records" do
87
+ EM.run {
88
+ c = EM::HandlerSocket.new
89
+ idx = {:id => 0, :db => 'widgets', :table => 'user', :index_name => 'PRIMARY', :columns => 'user_name'}
90
+
91
+ d = c.open_index(idx)
92
+ d.callback do |s|
93
+
94
+ d = c.query({:id => 0, :op => '=', :key => '1'}, {:id => 0, :op => '=', :key => '2'})
95
+ d.errback { fail }
96
+ d.callback do |data|
97
+ data.should == ['Ilya', 'John']
98
+ EM.stop
99
+ end
100
+ end
101
+ }
102
+ end
103
+
104
+ it "should execute a query against a composite index" do
105
+ EM.run {
106
+ c = EM::HandlerSocket.new
107
+ idx = {:id => 0, :db => 'widgets', :table => 'user', :index_name => 'id_created', :columns => 'user_name'}
108
+
109
+ d = c.open_index(idx)
110
+ d.callback do |s|
111
+
112
+ d = c.query(:id => 0, :op => '>=', :key => ['2', '2010-01-03'])
113
+ d.errback { fail }
114
+ d.callback do |data|
115
+ data.should == ['Bob']
116
+ EM.stop
117
+ end
118
+ end
119
+ }
120
+ end
121
+
122
+ end
data/spec/helper.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'bundler'
2
+ Bundler.setup
3
+
4
+ require 'lib/em-handlersocket'
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: em-handlersocket
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Ilya Grigorik
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-01-14 00:00:00 -05:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: eventmachine
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :runtime
32
+ version_requirements: *id001
33
+ - !ruby/object:Gem::Dependency
34
+ name: rspec
35
+ prerelease: false
36
+ requirement: &id002 !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 0
43
+ version: "0"
44
+ type: :development
45
+ version_requirements: *id002
46
+ description: Asynchronous (EventMachine) HandlerSocket client
47
+ email:
48
+ - ilya@igvita.com
49
+ executables: []
50
+
51
+ extensions: []
52
+
53
+ extra_rdoc_files: []
54
+
55
+ files:
56
+ - .gitignore
57
+ - .rspec
58
+ - Gemfile
59
+ - Gemfile.lock
60
+ - README.md
61
+ - Rakefile
62
+ - em-handlersocket.gemspec
63
+ - lib/em-handlersocket.rb
64
+ - lib/em-handlersocket/client.rb
65
+ - lib/em-handlersocket/version.rb
66
+ - spec/client_spec.rb
67
+ - spec/helper.rb
68
+ has_rdoc: true
69
+ homepage: http://github.com/igrigorik/em-handlersocket
70
+ licenses: []
71
+
72
+ post_install_message:
73
+ rdoc_options: []
74
+
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ segments:
83
+ - 0
84
+ version: "0"
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ segments:
91
+ - 0
92
+ version: "0"
93
+ requirements: []
94
+
95
+ rubyforge_project: em-handlersocket
96
+ rubygems_version: 1.3.7
97
+ signing_key:
98
+ specification_version: 3
99
+ summary: Asynchronous (EventMachine) HandlerSocket client
100
+ test_files:
101
+ - spec/client_spec.rb
102
+ - spec/helper.rb