handlersocket-rb 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: acad8d994a34c1431708bfe35b8a1dc0fbf94597
4
+ data.tar.gz: cf8d34c2ad01ee30af626d70fc1bbfcd35a242da
5
+ SHA512:
6
+ metadata.gz: 41b05acfa225c3adcb1b440eb12da25901ba0ff820ab3fe9f0225b0c5bb4d28d39a5aac74d1ff3004036e1ee8b8e464d03a7e86e98391b142fe86d0072e9761b
7
+ data.tar.gz: 5a8e4d2a8be4d0dae6b92bebf4aa1e56948e306c4e840bb5e020934695f288d6b0a77660152d31f799b535fe961fcd2a8a37b4e4ee797976759b69486d8517ce
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ .ruby-version
11
+ .ruby-gemset
12
+ .rvmrc
13
+ lib/handlersocket_ext.so
14
+ .rspec
15
+ mysql.yml
16
+ handlersocket.yml
@@ -0,0 +1,14 @@
1
+ language: ruby
2
+ before_install: gem install bundler -v 1.10.6
3
+ before_script:
4
+ - cp local_handlersocket.yml handlersocket.yml
5
+ - cp mysql.yml.sample mysql.yml
6
+ - bin/mock_hs_server > server_log &
7
+ after_script:
8
+ - cat server_log
9
+ rvm:
10
+ - 2.2.3
11
+ - 2.0.0
12
+ env:
13
+ - HS_VERSION=pure
14
+ - HS_VERSION=ext
@@ -0,0 +1,13 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4
+
5
+ We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
6
+
7
+ Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8
+
9
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10
+
11
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
12
+
13
+ This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in handlersocket.gemspec
4
+ gemspec
5
+
6
+ gem 'mysql2'
7
+ gem 'benchmark-ips'
8
+ gem 'pry'
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Ilya Bylich
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,134 @@
1
+ [![Build Status](https://travis-ci.org/iliabylich/handlersocket-ruby.svg?branch=master)](https://travis-ci.org/iliabylich/handlersocket-ruby)
2
+ [![Code Climate](https://codeclimate.com/github/iliabylich/handlersocket-ruby/badges/gpa.svg)](https://codeclimate.com/github/iliabylich/handlersocket-ruby)
3
+
4
+ # HandlerSocket
5
+
6
+ Ruby/C -based zero dependency implementation of MySQL's HandlerSocket protocol.
7
+
8
+ # What is HandlerSocket
9
+
10
+ HandlerSocket is a NoSQL plugin for MySQL. It works as a daemon inside the mysqld process, accepting TCP connections, and executing requests from clients. HandlerSocket does not support SQL queries. Instead, it supports simple CRUD operations on tables.
11
+
12
+ HandlerSocket can be much faster than mysqld/libmysql in some cases because it has lower CPU, disk, and network overhead:
13
+
14
+ To lower CPU usage it does not parse SQL.
15
+ Next, it batch-processes requests where possible, which further reduces CPU usage and lowers disk usage.
16
+ Lastly, the client/server protocol is very compact compared to mysql/libmysql, which reduces network usage
17
+
18
+ ## Installation
19
+
20
+ Add this line to your application's Gemfile:
21
+
22
+ ```ruby
23
+ # If you want to use Ruby-based implementation
24
+ gem 'handlersocket-rb', require: 'handlersocket/pure'
25
+
26
+ # If you want to use C-based implementation
27
+ gem 'handlersocket-rb', require: 'handlersocket/ext'
28
+ ```
29
+
30
+ And then execute:
31
+
32
+ $ bundle
33
+
34
+ Or install it yourself as:
35
+
36
+ $ gem install handlersocket-rb
37
+
38
+ ## Usage
39
+
40
+ First of all, you need to install [handlersocket](http://yoshinorimatsunobu.blogspot.com.by/2010/10/using-mysql-as-nosql-story-for.html) plugin.
41
+
42
+ If you are using Percona Server or MariaDB, you already have all necessary libraries.
43
+
44
+ ``` sql
45
+ INSTALL PLUGIN handlersocket SONAME 'handlersocket.so';
46
+ ```
47
+
48
+ And add the following code to mysql config file to `mysqld` section:
49
+ ```
50
+ handlersocket_address="127.0.0.1"
51
+ handlersocket_port="9998"
52
+ handlersocket_port_wr="9999"
53
+ ```
54
+
55
+ Reload MySQL server and run `show processlist;`. You should see a lot of connections from HandlerSocket.
56
+
57
+ To create a connection to HandlerSocket run:
58
+ ``` ruby
59
+ hs = Handlersocket.new('host', port)
60
+ ```
61
+
62
+ To open an index run:
63
+ ``` ruby
64
+ hs.open_index('0', 'hs_test', 'users', 'PRIMARY', ['id', 'email'])
65
+ # where:
66
+ # '0' - index name, you should pass it later to query method
67
+ # 'hs_test' - database name
68
+ # 'users' - table name
69
+ # 'PRIMARY' - index name
70
+ # ['id', 'name'] - columns that you want to read/write
71
+ ```
72
+
73
+ To read the data run:
74
+ ``` ruby
75
+ hs.find('0', '=', ['12'], ['100]'])
76
+ # This query opens index '0' (primary index from the previous example)
77
+ # and finds 100 rows with 'indexed value' = '12'
78
+ # similar to
79
+ # SELECT id, name FROM hs_test.users WHERE id = 12 LIMIT 100
80
+ ```
81
+
82
+ ## Benchmarks
83
+
84
+ See `rake:benchmark`.
85
+
86
+ ```
87
+ Calculating -------------------------------------
88
+ pure HS 2.000 i/100ms
89
+ ext HS 3.149k i/100ms
90
+ mysql2 669.000 i/100ms
91
+ -------------------------------------------------
92
+ pure HS 49.979 (± 2.0%) i/s - 250.000
93
+ ext HS 110.369M (±15.7%) i/s - 467.793M
94
+ mysql2 5.218M (±16.3%) i/s - 23.869M
95
+
96
+ Comparison:
97
+ ext HS: 110369437.2 i/s
98
+ mysql2: 5218120.6 i/s - 21.15x slower
99
+ pure HS: 50.0 i/s - 2208314.91x slower
100
+ ```
101
+
102
+ Where:
103
+ + `ext HS` is a C-based implementation of HandlerSocket protocol
104
+ + `mysql2` is a default mysql2 adapter
105
+ + `pure HS` is a Ruby-based implementation of HandlerSocket protocal
106
+
107
+ ## Development
108
+
109
+ After checking out the repo, run `bin/setup` to install dependencies.
110
+ Fill `handlersocket.yml` and `mysql.yml` in a root directory of the project.
111
+ Run `rake test:db:create` to create a default database that is used in tests.
112
+ Then, run `rake` to (re)compile the code and run the tests.
113
+ You can also run `bin/console` for an interactive prompt that will allow you to experiment.
114
+
115
+ To install this gem onto your local machine, run `bundle exec rake install`.
116
+ To release a new version, update the version number in `version.rb`,
117
+ and then run `bundle exec rake release`,
118
+ which will create a git tag for the version,
119
+ push git commits and tags,
120
+ and push the `.gem` file to [rubygems.org](https://rubygems.org).
121
+
122
+ Run `rake benchmark` to compare the performance of Ruby HS/C HS/Mysql2.
123
+
124
+ ## Contributing
125
+
126
+ Bug reports and pull requests are welcome on GitHub at https://github.com/iliabylich/handlersocket-ruby.
127
+ This project is intended to be a safe, welcoming space for collaboration,
128
+ and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
129
+
130
+
131
+ ## License
132
+
133
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
134
+
@@ -0,0 +1,90 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/extensiontask'
4
+ Rake::ExtensionTask.new('handlersocket_ext')
5
+
6
+ require 'bundler/setup'
7
+ require 'mysql2'
8
+ mysql_config = YAML.load_file(File.expand_path('../mysql.yml', __FILE__))
9
+ client = Mysql2::Client.new(mysql_config)
10
+
11
+ namespace :test do
12
+ namespace :db do
13
+ desc 'Drops tests database'
14
+ task :drop do
15
+ client.query("DROP DATABASE IF EXISTS hs_test")
16
+ end
17
+
18
+ desc 'Prepares test database'
19
+ task :create do
20
+ client.query("CREATE DATABASE hs_test")
21
+ client.query("use hs_test")
22
+ client.query <<-SQL
23
+ CREATE TABLE users (
24
+ id int(11) NOT NULL AUTO_INCREMENT,
25
+ email varchar(255) NOT NULL DEFAULT '',
26
+ PRIMARY KEY (id)
27
+ )
28
+ SQL
29
+ end
30
+
31
+ desc 'Seeds test database'
32
+ task :seed do
33
+ count = ENV.fetch('SEED_COUNT', 100)
34
+ 1.upto(count) do |i|
35
+ client.query("INSERT INTO users(email) VALUES ('email#{i}@email')")
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ desc 'Runs benchmark hs_pure/hs_ext/mysql2'
42
+ task :benchmark do
43
+ mysql_config = YAML.load_file(File.expand_path('../mysql.yml', __FILE__))
44
+ hs_config = YAML.load_file(File.expand_path('../handlersocket.yml', __FILE__))
45
+
46
+ require 'bundler/setup'
47
+ require 'benchmark/ips'
48
+
49
+ require 'handlersocket/pure'
50
+ class Handlersocket
51
+ alias_method :pure_query, :query
52
+ undef_method :query
53
+ end
54
+ pure_socket = Handlersocket.new(hs_config['host'], hs_config['port'])
55
+ pure_socket.pure_query(['P', '0', 'hs_test', 'users', 'PRIMARY', 'id', 'col'])
56
+
57
+ require 'handlersocket/ext'
58
+ class Handlersocket
59
+ alias_method :ext_query, :query
60
+ undef_method :query
61
+ end
62
+ ext_socket = Handlersocket.new(hs_config['host'], hs_config['port'])
63
+ ext_socket.ext_query(['P', '0', 'hs_test', 'users', 'PRIMARY', 'id', 'col'])
64
+
65
+ require 'mysql2'
66
+ mysql_client = Mysql2::Client.new(mysql_config)
67
+
68
+ Benchmark.ips do |x|
69
+ x.config time: 5, warmup: 2
70
+
71
+ x.report 'pure HS' do |times|
72
+ pure_socket.pure_query(['0', '=', '1', times.to_s, '100'])
73
+ end
74
+
75
+ x.report 'ext HS' do |times|
76
+ ext_socket.ext_query(['0', '=', '1', times.to_s, '100'])
77
+ end
78
+
79
+ x.report 'mysql2' do |times|
80
+ mysql_client.query("SELECT * FROM users where id = #{times} LIMIT 100").to_a
81
+ end
82
+
83
+ x.compare!
84
+ end
85
+ end
86
+
87
+ require 'rspec/core/rake_task'
88
+ RSpec::Core::RakeTask.new(:spec)
89
+
90
+ task default: [:clobber, :compile, :spec]
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "handlersocket"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'socket'
4
+
5
+ port = (ENV['HS_PORT'] || '9997').to_i
6
+ server = TCPServer.new(port)
7
+ puts "Server is running on 127.0.0.1:#{port}"
8
+
9
+ REQ_RES = {
10
+ %w(P 0 hs_test users PRIMARY id,email) => %w(0 1),
11
+ %w(P 1 hs_test users PRIMARY id,email) => %w(0 1),
12
+ %w(P 2 missing-db missing-table missing-idx ) => %w(1 1 open_table),
13
+ %w(0 = 1 12 100) => %w(0 2 12 email12@email)
14
+ }
15
+
16
+ loop do
17
+ Thread.new(server.accept) do |client|
18
+ loop do
19
+ req = client.gets
20
+ if req
21
+ puts "[client=#{client.object_id} REQ] #{req.inspect}"
22
+ res = REQ_RES[req.chomp.split("\t")] || ["Unsupported"]
23
+ res = res.join("\t") + "\n"
24
+ puts "[client=#{client.object_id} RES] #{res.inspect}"
25
+ client.puts(res)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,2 @@
1
+ require 'mkmf'
2
+ create_makefile('handlersocket_ext')
@@ -0,0 +1,128 @@
1
+ #include <stdio.h>
2
+ #include <ruby.h>
3
+
4
+ VALUE rb_Handlersocket;
5
+
6
+ #ifndef RARRAY_AREF
7
+ #define RARRAY_AREF(ary,n) (RARRAY_PTR(ary)[n])
8
+ #endif
9
+
10
+ struct SocketData {
11
+ int socket_desc;
12
+ char* host;
13
+ int port;
14
+ };
15
+ typedef struct SocketData SOCKET_DATA;
16
+
17
+ #include "socket/send.h"
18
+
19
+ // A default destructor for a Handlersocket obejct that closes connection
20
+ //
21
+ // @param [Data] data
22
+ // @see SocketData
23
+ //
24
+ static void hs_free(SOCKET_DATA *data) {
25
+ close(data->socket_desc);
26
+ }
27
+
28
+ // An allocator for a Handlersocket object
29
+ //
30
+ // @param [Handlersocket] hs
31
+ //
32
+ // @return [Data] SocketData
33
+ //
34
+ static VALUE hs_alloc(VALUE hs) {
35
+ SOCKET_DATA *data;
36
+ data = malloc(sizeof(SOCKET_DATA));
37
+ return Data_Wrap_Struct(hs, NULL, hs_free, data);
38
+ }
39
+
40
+ // Disconnects passed Handlersocket
41
+ //
42
+ // @param [Handlersocket] hs
43
+ //
44
+ static VALUE rb_hs_disconnect(VALUE hs) {
45
+ SOCKET_DATA *data;
46
+ Data_Get_Struct(hs, SOCKET_DATA, data);
47
+ hs_free(data);
48
+
49
+ return Qnil;
50
+ }
51
+
52
+ // Establishes a connection to HS server
53
+ //
54
+ // @param [Handlersocket] hs
55
+ //
56
+ // @return [nil]
57
+ //
58
+ static VALUE rb_hs_connect(VALUE hs) {
59
+ SOCKET_DATA *data;
60
+ struct sockaddr_in server;
61
+
62
+ Data_Get_Struct(hs, SOCKET_DATA, data);
63
+
64
+ if (data->socket_desc == -1) {
65
+ hs_free(data);
66
+ }
67
+
68
+ data->socket_desc = socket(AF_INET , SOCK_STREAM , 0);
69
+ if (data->socket_desc == -1) {
70
+ rb_warn("Could not create socket");
71
+ }
72
+
73
+ server.sin_addr.s_addr = inet_addr(data->host);
74
+ server.sin_family = AF_INET;
75
+ server.sin_port = htons(data->port);
76
+
77
+ if (connect(data->socket_desc , (struct sockaddr *)&server , sizeof(server)) < 0) {
78
+ rb_raise(rb_eStandardError, "HS: connect error");
79
+ }
80
+
81
+ return Qnil;
82
+ }
83
+
84
+ // Re-establishes a connection to HS server
85
+ //
86
+ // @param [Handlersocket] hs
87
+ //
88
+ // @return [nil]
89
+ //
90
+ static VALUE rb_hs_reconnect(VALUE hs) {
91
+ rb_hs_disconnect(hs);
92
+ rb_hs_connect(hs);
93
+
94
+ return Qnil;
95
+ }
96
+
97
+ // Constructor for a Handlersocket object
98
+ //
99
+ // @param [Handlersocket] hs
100
+ // @param [String] host
101
+ // @param [Fixnum] port
102
+ //
103
+ // @return [nil]
104
+ //
105
+ static VALUE rb_hs_initialize(VALUE hs, VALUE host, VALUE port) {
106
+ SOCKET_DATA *data;
107
+ Data_Get_Struct(hs, SOCKET_DATA, data);
108
+
109
+ data->host = StringValuePtr(host);
110
+ data->port = NUM2INT(port);
111
+ data->socket_desc = -1;
112
+
113
+ rb_hs_connect(hs);
114
+
115
+ return Qnil;
116
+ }
117
+
118
+
119
+ void Init_handlersocket_ext(void) {
120
+ rb_Handlersocket = rb_const_get(rb_cObject, rb_intern("Handlersocket"));
121
+
122
+ rb_define_alloc_func(rb_Handlersocket, hs_alloc);
123
+ rb_define_method(rb_Handlersocket, "initialize", rb_hs_initialize, 2);
124
+ rb_define_method(rb_Handlersocket, "connect", rb_hs_connect, 0);
125
+ rb_define_method(rb_Handlersocket, "disconnect", rb_hs_disconnect, 0);
126
+ rb_define_method(rb_Handlersocket, "reconnect", rb_hs_reconnect, 0);
127
+ rb_define_method(rb_Handlersocket, "query", rb_hs_query, 1);
128
+ }
@@ -0,0 +1,135 @@
1
+ #include <arpa/inet.h>
2
+ #include <sys/ioctl.h>
3
+
4
+ // Writes passed message to a socket
5
+ //
6
+ // @param [int] socket_desc
7
+ // @param [char*] message
8
+ //
9
+ #define SOCKET_WRITE(socket_desc, message) send(socket_desc, message, strlen(message) , 0)
10
+
11
+
12
+ // Concatenates a string +s1+ and a string +s2+ with the length +l2+
13
+ // Result will be placed into +s1+
14
+ //
15
+ // @param [char**] s1
16
+ // @param [char*] s2
17
+ // @param [int] l2
18
+ //
19
+ void concat(char **s1, char *s2, int l2) {
20
+ int l1;
21
+ char *res;
22
+
23
+ l1 = *s1 ? strlen(*s1) : 0;
24
+ res = realloc(*s1, l1 + l2 + 1);
25
+
26
+ if (res) {
27
+ memcpy(res + l1, s2, l2);
28
+ res[l1 + l2] = 0;
29
+ *s1 = res;
30
+ }
31
+ }
32
+
33
+ // Writes an array of Ruby strings to passed socket
34
+ //
35
+ // @param [int] socket_desc
36
+ // @param [Array<String>] ary
37
+ //
38
+ static void hs_write(int socket_desc, VALUE ary) {
39
+ char *message, *buffer;
40
+ int i, l1, l2;
41
+ VALUE item;
42
+ message = NULL;
43
+
44
+ for (i = 0; i < RARRAY_LEN(ary); ++i) {
45
+ item = RARRAY_AREF(ary, i);
46
+ buffer = StringValuePtr(item);
47
+ l1 = message ? strlen(message) : 0;
48
+ l2 = buffer ? strlen(buffer) : 0;
49
+ if (l1 == 0) {
50
+ message = realloc(message, l2);
51
+ strcpy(message, buffer);
52
+ } else {
53
+ message = realloc(message, l1 + l2 + 2);
54
+ message[l1] = '\t';
55
+ memcpy(message + l1 + 1, buffer, l2);
56
+ message[l1 + l2 + 1] = 0;
57
+ }
58
+ }
59
+ l1 = strlen(message);
60
+ message = realloc(message, l1 + 2);
61
+ message[l1] = '\n';
62
+ message[l1 + 1] = 0;
63
+
64
+ SOCKET_WRITE(socket_desc, message);
65
+ }
66
+
67
+ // Reads all available data from the passed socket until the new line
68
+ //
69
+ // @param [int] socket_desc
70
+ //
71
+ // @return [char*] reply from the server
72
+ //
73
+ // @raise [StandardError] when socket is closed
74
+ //
75
+ static char* hs_read_until_newline(int socket_desc) {
76
+ int buffer_length = 100, bytes_read = 0;
77
+ char buf[buffer_length];
78
+ char *result = NULL;
79
+ int end_of_line_matched = 0;
80
+
81
+ while (!end_of_line_matched) {
82
+ bytes_read = read(socket_desc, buf, buffer_length);
83
+ if (!bytes_read) {
84
+ rb_raise(rb_eStandardError, "HS: closed socket");
85
+ }
86
+ concat(&result, buf, bytes_read);
87
+ if (buf[bytes_read - 1] == '\n') {
88
+ end_of_line_matched = 1;
89
+ }
90
+ }
91
+
92
+ return result;
93
+ }
94
+
95
+ // Parses "\t"-terminated response from the HS server
96
+ //
97
+ // @param [char*] raw_response
98
+ //
99
+ // @return [Array<String>] parsed reply
100
+ //
101
+ static VALUE hs_parse_response(char* raw_response) {
102
+ VALUE ary;
103
+ char *token;
104
+
105
+ ary = rb_ary_new();
106
+
107
+ while ((token = strsep(&raw_response, "\t\n")) != NULL) {
108
+ if (strlen(token) > 0) {
109
+ rb_ary_push(ary, rb_str_new2(token));
110
+ }
111
+ }
112
+
113
+ free(raw_response);
114
+ free(token);
115
+
116
+ return ary;
117
+ }
118
+
119
+ // Sends a query to the HS server and parses response
120
+ //
121
+ // @param [Handlersocket] hs
122
+ // @param [Array<String>] req request data
123
+ //
124
+ // @return [Array<String>] reply
125
+ //
126
+ static VALUE rb_hs_query(VALUE hs, VALUE req) {
127
+ SOCKET_DATA *data;
128
+ char *raw_result;
129
+
130
+ Data_Get_Struct(hs, SOCKET_DATA, data);
131
+
132
+ hs_write(data->socket_desc, req);
133
+ raw_result = hs_read_until_newline(data->socket_desc);
134
+ return hs_parse_response(raw_result);
135
+ }
@@ -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 'handlersocket/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "handlersocket-rb"
8
+ spec.version = Handlersocket::VERSION
9
+ spec.authors = ["Ilya Bylich"]
10
+ spec.email = ["ibylich@gmail.com"]
11
+
12
+ spec.summary = %q{Zero-dependency implementation of HandlerSocket protocol}
13
+ spec.homepage = "https://github.com/iliabylich/handlersocket-ruby"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.10"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "rake-compiler"
24
+ spec.add_development_dependency "rspec"
25
+ end
@@ -0,0 +1,2 @@
1
+ host: 127.0.0.1
2
+ port: 9999
@@ -0,0 +1,58 @@
1
+ require "handlersocket/version"
2
+
3
+ # Class for establising connection to MySQL's HandlerSocket
4
+ #
5
+ # @example
6
+ # hs = Handlersocket.new('0.0.0.0', 9999)
7
+ #
8
+ class Handlersocket
9
+ # @param [String] host
10
+ # @param [Fixnum] port
11
+ #
12
+ def initialize(host, port)
13
+ @host = host
14
+ @port = port
15
+ end
16
+
17
+ # Sends a query to Handlersocket server and returns a reply
18
+ #
19
+ # @param [Array<String>] args
20
+ #
21
+ # @return [Array<String>]
22
+ #
23
+ def query(*args)
24
+ # A stub method
25
+ end
26
+
27
+ # Opens an an index for requests
28
+ #
29
+ # @param [String] idx_id id of index
30
+ # @param [String] db_name name of the database
31
+ # @param [String] tbl_name name of the table
32
+ # @param [String] idx_name name of the index
33
+ # @param [Array<String>] columns you want to get
34
+ #
35
+ # @return [Array<String>]
36
+ #
37
+ def open_index(idx_id, db_name, tbl_name, idx_name, columns)
38
+ req = ["P", idx_id, db_name, tbl_name, idx_name, columns.join(",")]
39
+ query(req)
40
+ end
41
+
42
+ # Runs a find statement on HandlerSocket server and returns matched rows
43
+ #
44
+ # @param [String] idx_id id of index
45
+ # @param [String] op operation ('=', '>', '<')
46
+ # @param [Array<String>] args arguments for comparison
47
+ # @param [Array<String>] extra arguments like limit and offset
48
+ #
49
+ # @return [Array<String>] matched rows
50
+ #
51
+ def find(idx_id, op, args, extra = [])
52
+ req = [idx_id, op, args.length.to_s, *args, *extra]
53
+ query(req)
54
+ end
55
+ end
56
+
57
+ # require 'handlersocket/pure'
58
+ # require 'handlersocket/ext'
@@ -0,0 +1,2 @@
1
+ require 'handlersocket'
2
+ require 'handlersocket_ext'
@@ -0,0 +1,27 @@
1
+ require 'socket'
2
+ require 'handlersocket'
3
+
4
+ # Pure Ruby implementation of HS protocol
5
+ #
6
+ # Sockets implementation is very slow in MRI, use HandlerSocket::Ext for better performance
7
+ #
8
+ class Handlersocket
9
+ # Returns a memoized instance of TCP socket
10
+ #
11
+ # @return [TCPSocket]
12
+ #
13
+ def socket
14
+ @socket ||= TCPSocket.new(@host, @port)
15
+ end
16
+
17
+ # Sends request to a HS server and returns a reply
18
+ #
19
+ # @param [Array<String>] req
20
+ #
21
+ # @return [Array<String>]
22
+ #
23
+ def query(req)
24
+ socket.puts(req.join("\t"))
25
+ socket.gets.chomp.split("\t")
26
+ end
27
+ end
@@ -0,0 +1,3 @@
1
+ class Handlersocket
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,2 @@
1
+ host: 127.0.0.1
2
+ port: 9997
@@ -0,0 +1,3 @@
1
+ host: localhost
2
+ username: root
3
+ password:
metadata ADDED
@@ -0,0 +1,121 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: handlersocket-rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ilya Bylich
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-11-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.10'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake-compiler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description:
70
+ email:
71
+ - ibylich@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - ".travis.yml"
78
+ - CODE_OF_CONDUCT.md
79
+ - Gemfile
80
+ - LICENSE.txt
81
+ - README.md
82
+ - Rakefile
83
+ - bin/console
84
+ - bin/mock_hs_server
85
+ - bin/setup
86
+ - ext/handlersocket_ext/extconf.rb
87
+ - ext/handlersocket_ext/handlersocket_ext.c
88
+ - ext/handlersocket_ext/socket/send.h
89
+ - handlersocket.gemspec
90
+ - handlersocket.yml.sample
91
+ - lib/handlersocket.rb
92
+ - lib/handlersocket/ext.rb
93
+ - lib/handlersocket/pure.rb
94
+ - lib/handlersocket/version.rb
95
+ - local_handlersocket.yml
96
+ - mysql.yml.sample
97
+ homepage: https://github.com/iliabylich/handlersocket-ruby
98
+ licenses:
99
+ - MIT
100
+ metadata: {}
101
+ post_install_message:
102
+ rdoc_options: []
103
+ require_paths:
104
+ - lib
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ requirements: []
116
+ rubyforge_project:
117
+ rubygems_version: 2.4.8
118
+ signing_key:
119
+ specification_version: 4
120
+ summary: Zero-dependency implementation of HandlerSocket protocol
121
+ test_files: []