mysql2 0.1.8 → 0.1.9
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/CHANGELOG.md +16 -0
- data/README.rdoc +12 -4
- data/Rakefile +10 -3
- data/VERSION +1 -1
- data/benchmark/active_record.rb +0 -1
- data/benchmark/escape.rb +2 -2
- data/benchmark/query_with_mysql_casting.rb +2 -2
- data/benchmark/query_without_mysql_casting.rb +2 -2
- data/benchmark/sequel.rb +0 -1
- data/benchmark/setup_db.rb +2 -2
- data/ext/{extconf.rb → mysql2/extconf.rb} +11 -31
- data/ext/mysql2/mysql2_ext.c +496 -0
- data/ext/{mysql2_ext.h → mysql2/mysql2_ext.h} +11 -39
- data/ext/mysql2/result.c +340 -0
- data/ext/mysql2/result.h +7 -0
- data/lib/active_record/connection_adapters/em_mysql2_adapter.rb +59 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +7 -7
- data/lib/active_record/fiber_patches.rb +104 -0
- data/lib/mysql2.rb +9 -3
- data/lib/mysql2/client.rb +211 -0
- data/lib/mysql2/error.rb +11 -0
- data/lib/mysql2/result.rb +5 -0
- data/lib/sequel/adapters/mysql2.rb +2 -3
- data/mysql2.gemspec +15 -8
- data/spec/active_record/active_record_spec.rb +6 -4
- data/spec/em/em_spec.rb +2 -2
- data/spec/mysql2/client_spec.rb +48 -2
- data/spec/mysql2/error_spec.rb +2 -2
- data/spec/mysql2/result_spec.rb +127 -3
- metadata +20 -8
- data/ext/mysql2_ext.c +0 -748
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,21 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 0.1.9 (HEAD)
|
4
|
+
* Support async ActiveRecord access with fibers and EventMachine (mperham)
|
5
|
+
* string encoding support for 1.9, respecting Encoding.default_internal
|
6
|
+
* added support for rake-compiler (tenderlove)
|
7
|
+
* bugfixes for ActiveRecord driver
|
8
|
+
** one minor bugfix for TimeZone support
|
9
|
+
** fix the select_rows method to return what it should according to the docs (r-stu31)
|
10
|
+
* Mysql2::Client#fields method added - returns the array of field names from a resultset, as strings
|
11
|
+
* Sequel adapter
|
12
|
+
** bugfix regarding sybolized field names (Eric Wong)
|
13
|
+
** fix query logging in Sequel adapter
|
14
|
+
* Lots of nice code cleanup (tenderlove)
|
15
|
+
** Mysql2::Error definition moved to pure-Ruby
|
16
|
+
** Mysql2::client#initialize definition moved to pure-Ruby
|
17
|
+
** Mysql2::Result partially moved to pure-Ruby
|
18
|
+
|
3
19
|
## 0.1.8 (June 2nd, 2010)
|
4
20
|
* fixes for AR adapter for timezone juggling
|
5
21
|
* fixes to be able to run benchmarks and specs under 1.9.2
|
data/README.rdoc
CHANGED
@@ -4,7 +4,7 @@ The Mysql2 gem is meant to serve the extremely common use-case of connecting, qu
|
|
4
4
|
Some database libraries out there serve as direct 1:1 mappings of the already complex C API's available.
|
5
5
|
This one is not.
|
6
6
|
|
7
|
-
It also forces the use of UTF-8 [or binary] for the connection [and all strings in 1.9] and uses encoding-aware MySQL API calls where it can.
|
7
|
+
It also forces the use of UTF-8 [or binary] for the connection [and all strings in 1.9, unless Encoding.default_internal is set then it'll convert from UTF-8 to that encoding] and uses encoding-aware MySQL API calls where it can.
|
8
8
|
|
9
9
|
The API consists of two clases:
|
10
10
|
|
@@ -80,6 +80,11 @@ If you need multiple query concurrency take a look at using a connection pool.
|
|
80
80
|
To use the ActiveRecord driver, all you should need to do is have this gem installed and set the adapter in your database.yml to "mysql2".
|
81
81
|
That was easy right? :)
|
82
82
|
|
83
|
+
== Asynchronous ActiveRecord
|
84
|
+
|
85
|
+
You can also use Mysql2 with asynchronous Rails (first introduced at http://www.mikeperham.com/2010/04/03/introducing-phat-an-asynchronous-rails-app/) by
|
86
|
+
setting the adapter in your database.yml to "em_mysql2". You must be running Ruby 1.9, thin and the rack-fiber_pool middleware for it to work.
|
87
|
+
|
83
88
|
== EventMachine
|
84
89
|
|
85
90
|
The mysql2 EventMachine deferrable api allows you to make async queries using EventMachine,
|
@@ -136,7 +141,7 @@ them into proper Ruby types in Ruby-land - which is slow as balls.
|
|
136
141
|
|
137
142
|
Someone: OK fine, but do_mysql can already give me back values with Ruby objects mapped to MySQL types.
|
138
143
|
|
139
|
-
Me: Yep, but it's API is considerably more complex *and*
|
144
|
+
Me: Yep, but it's API is considerably more complex *and* can be ~2x slower.
|
140
145
|
|
141
146
|
== Benchmarks
|
142
147
|
|
@@ -147,11 +152,14 @@ then iterating over every row using an #each like method yielding a block:
|
|
147
152
|
user system total real
|
148
153
|
Mysql2
|
149
154
|
0.890000 0.190000 1.080000 ( 2.028887)
|
150
|
-
Mysql
|
151
|
-
7.330000 0.350000 7.680000 ( 8.013160)
|
152
155
|
do_mysql
|
153
156
|
1.740000 0.220000 1.960000 ( 2.909290)
|
157
|
+
Mysql
|
158
|
+
7.330000 0.350000 7.680000 ( 8.013160)
|
154
159
|
|
155
160
|
== Special Thanks
|
156
161
|
|
157
162
|
* Eric Wong - for the contribution (and informative explanations of) of some thread-safety, non-blocking I/O and cleanup patches. You rock dude
|
163
|
+
* Yury Korolev (http://github.com/yury) - for TONS of help testing the ActiveRecord adapter
|
164
|
+
* Aaron Patterson (http://github.com/tenderlove) - tons of contributions, suggestions and general badassness
|
165
|
+
* Mike Perham (http://github.com/mperham) - Async ActiveRecord adapter (uses Fibers and EventMachine)
|
data/Rakefile
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
begin
|
3
3
|
require 'jeweler'
|
4
|
-
Jeweler::Tasks.new do |gem|
|
4
|
+
JEWELER = Jeweler::Tasks.new do |gem|
|
5
5
|
gem.name = "mysql2"
|
6
6
|
gem.summary = "A simple, fast Mysql library for Ruby, binding to libmysql"
|
7
7
|
gem.email = "seniorlopez@gmail.com"
|
@@ -10,7 +10,7 @@ begin
|
|
10
10
|
gem.require_paths = ["lib", "ext"]
|
11
11
|
gem.extra_rdoc_files = `git ls-files *.rdoc`.split("\n")
|
12
12
|
gem.files = `git ls-files`.split("\n")
|
13
|
-
gem.extensions = ["ext/extconf.rb"]
|
13
|
+
gem.extensions = ["ext/mysql2/extconf.rb"]
|
14
14
|
gem.files.include %w(lib/jeweler/templates/.document lib/jeweler/templates/.gitignore)
|
15
15
|
# gem.rubyforge_project = "mysql2"
|
16
16
|
end
|
@@ -20,6 +20,8 @@ end
|
|
20
20
|
|
21
21
|
require 'rake'
|
22
22
|
require 'spec/rake/spectask'
|
23
|
+
gem 'rake-compiler', '>= 0.4.1'
|
24
|
+
require "rake/extensiontask"
|
23
25
|
|
24
26
|
desc "Run all examples with RCov"
|
25
27
|
Spec::Rake::SpecTask.new('spec:rcov') do |t|
|
@@ -32,4 +34,9 @@ end
|
|
32
34
|
Spec::Rake::SpecTask.new('spec') do |t|
|
33
35
|
t.spec_files = FileList['spec/']
|
34
36
|
t.spec_opts << '--options' << 'spec/spec.opts'
|
35
|
-
end
|
37
|
+
end
|
38
|
+
|
39
|
+
Rake::ExtensionTask.new("mysql2", JEWELER.gemspec) do |ext|
|
40
|
+
ext.lib_dir = File.join 'lib', 'mysql2'
|
41
|
+
end
|
42
|
+
Rake::Task[:spec].prerequisites << :compile
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.9
|
data/benchmark/active_record.rb
CHANGED
data/benchmark/escape.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
-
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '
|
2
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
3
3
|
|
4
4
|
require 'rubygems'
|
5
5
|
require 'benchmark'
|
6
6
|
require 'mysql'
|
7
|
-
require '
|
7
|
+
require 'mysql2'
|
8
8
|
require 'do_mysql'
|
9
9
|
|
10
10
|
number_of = 1000
|
@@ -1,10 +1,10 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
-
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '
|
2
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
3
3
|
|
4
4
|
require 'rubygems'
|
5
5
|
require 'benchmark'
|
6
6
|
require 'mysql'
|
7
|
-
require '
|
7
|
+
require 'mysql2'
|
8
8
|
require 'do_mysql'
|
9
9
|
|
10
10
|
number_of = 100
|
@@ -1,10 +1,10 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
-
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '
|
2
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
3
3
|
|
4
4
|
require 'rubygems'
|
5
5
|
require 'benchmark'
|
6
6
|
require 'mysql'
|
7
|
-
require '
|
7
|
+
require 'mysql2'
|
8
8
|
require 'do_mysql'
|
9
9
|
|
10
10
|
number_of = 100
|
data/benchmark/sequel.rb
CHANGED
data/benchmark/setup_db.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
-
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '
|
2
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
3
3
|
|
4
4
|
# This script is for generating psudo-random data into a single table consisting of nearly every
|
5
5
|
# data type MySQL 5.1 supports.
|
6
6
|
#
|
7
7
|
# It's meant to be used with the query.rb benchmark script (or others in the future)
|
8
8
|
|
9
|
-
require '
|
9
|
+
require 'mysql2'
|
10
10
|
require 'rubygems'
|
11
11
|
require 'faker'
|
12
12
|
|
@@ -1,6 +1,10 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
require 'mkmf'
|
3
3
|
|
4
|
+
def asplode lib
|
5
|
+
abort "-----\n#{lib} is missing. please check your installation of mysql and try again.\n-----"
|
6
|
+
end
|
7
|
+
|
4
8
|
# 1.9-only
|
5
9
|
have_func('rb_thread_blocking_region')
|
6
10
|
|
@@ -41,43 +45,19 @@ else
|
|
41
45
|
end
|
42
46
|
|
43
47
|
if have_header('mysql.h') then
|
44
|
-
|
48
|
+
prefix = nil
|
45
49
|
elsif have_header('mysql/mysql.h') then
|
46
|
-
|
50
|
+
prefix = 'mysql'
|
47
51
|
else
|
48
|
-
|
52
|
+
asplode 'mysql.h'
|
49
53
|
end
|
50
54
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
end
|
55
|
-
if defined? cpp_command then
|
56
|
-
cpp = Config.expand(cpp_command(''))
|
57
|
-
else
|
58
|
-
cpp = Config.expand sprintf(CPP, $CPPFLAGS, $CFLAGS, '')
|
59
|
-
end
|
60
|
-
if /mswin32/ =~ RUBY_PLATFORM && !/-E/.match(cpp)
|
61
|
-
cpp << " -E"
|
62
|
-
end
|
63
|
-
unless system "#{cpp} > confout" then
|
64
|
-
exit 1
|
65
|
-
end
|
66
|
-
File.unlink "conftest.c"
|
67
|
-
|
68
|
-
error_syms = []
|
69
|
-
IO.foreach('confout') do |l|
|
70
|
-
next unless l =~ /errmsg\.h|mysqld_error\.h/
|
71
|
-
fn = l.split(/\"/)[1]
|
72
|
-
IO.foreach(fn) do |m|
|
73
|
-
if m =~ /^#define\s+([CE]R_[0-9A-Z_]+)/ then
|
74
|
-
error_syms << $1
|
75
|
-
end
|
76
|
-
end
|
55
|
+
%w{ errmsg.h mysqld_error.h }.each do |h|
|
56
|
+
header = [prefix, h].compact.join '/'
|
57
|
+
asplode h unless have_header h
|
77
58
|
end
|
78
|
-
File.unlink 'confout'
|
79
59
|
|
80
60
|
$CFLAGS << ' -Wall -Wextra -funroll-loops'
|
81
61
|
# $CFLAGS << ' -O0 -ggdb3'
|
82
62
|
|
83
|
-
create_makefile('
|
63
|
+
create_makefile('mysql2/mysql2')
|
@@ -0,0 +1,496 @@
|
|
1
|
+
#include <mysql2_ext.h>
|
2
|
+
|
3
|
+
VALUE mMysql2, cMysql2Client;
|
4
|
+
VALUE cMysql2Error, intern_encoding_from_charset;
|
5
|
+
ID sym_id, sym_version, sym_async;
|
6
|
+
|
7
|
+
#define REQUIRE_OPEN_DB(_ctxt) \
|
8
|
+
if(!_ctxt->net.vio) { \
|
9
|
+
rb_raise(cMysql2Error, "closed MySQL connection"); \
|
10
|
+
return Qnil; \
|
11
|
+
}
|
12
|
+
|
13
|
+
/*
|
14
|
+
* non-blocking mysql_*() functions that we won't be wrapping since
|
15
|
+
* they do not appear to hit the network nor issue any interruptible
|
16
|
+
* or blocking system calls.
|
17
|
+
*
|
18
|
+
* - mysql_affected_rows()
|
19
|
+
* - mysql_error()
|
20
|
+
* - mysql_fetch_fields()
|
21
|
+
* - mysql_fetch_lengths() - calls cli_fetch_lengths or emb_fetch_lengths
|
22
|
+
* - mysql_field_count()
|
23
|
+
* - mysql_get_client_info()
|
24
|
+
* - mysql_get_client_version()
|
25
|
+
* - mysql_get_server_info()
|
26
|
+
* - mysql_get_server_version()
|
27
|
+
* - mysql_insert_id()
|
28
|
+
* - mysql_num_fields()
|
29
|
+
* - mysql_num_rows()
|
30
|
+
* - mysql_options()
|
31
|
+
* - mysql_real_escape_string()
|
32
|
+
* - mysql_ssl_set()
|
33
|
+
*/
|
34
|
+
|
35
|
+
static VALUE rb_raise_mysql2_error(MYSQL *client) {
|
36
|
+
VALUE e = rb_exc_new2(cMysql2Error, mysql_error(client));
|
37
|
+
rb_funcall(e, rb_intern("error_number="), 1, INT2NUM(mysql_errno(client)));
|
38
|
+
rb_funcall(e, rb_intern("sql_state="), 1, rb_tainted_str_new2(mysql_sqlstate(client)));
|
39
|
+
rb_exc_raise(e);
|
40
|
+
return Qnil;
|
41
|
+
}
|
42
|
+
|
43
|
+
static VALUE nogvl_init(void *ptr) {
|
44
|
+
MYSQL * client = (MYSQL *)ptr;
|
45
|
+
|
46
|
+
/* may initialize embedded server and read /etc/services off disk */
|
47
|
+
mysql_init(client);
|
48
|
+
|
49
|
+
return client ? Qtrue : Qfalse;
|
50
|
+
}
|
51
|
+
|
52
|
+
static VALUE nogvl_connect(void *ptr) {
|
53
|
+
struct nogvl_connect_args *args = ptr;
|
54
|
+
MYSQL *client;
|
55
|
+
|
56
|
+
client = mysql_real_connect(args->mysql, args->host,
|
57
|
+
args->user, args->passwd,
|
58
|
+
args->db, args->port, args->unix_socket,
|
59
|
+
args->client_flag);
|
60
|
+
|
61
|
+
return client ? Qtrue : Qfalse;
|
62
|
+
}
|
63
|
+
|
64
|
+
static void rb_mysql_client_free(void * ptr) {
|
65
|
+
MYSQL * client = (MYSQL *)ptr;
|
66
|
+
|
67
|
+
/*
|
68
|
+
* we'll send a QUIT message to the server, but that message is more of a
|
69
|
+
* formality than a hard requirement since the socket is getting shutdown
|
70
|
+
* anyways, so ensure the socket write does not block our interpreter
|
71
|
+
*/
|
72
|
+
int fd = client->net.fd;
|
73
|
+
int flags;
|
74
|
+
|
75
|
+
if (fd >= 0) {
|
76
|
+
/*
|
77
|
+
* if the socket is dead we have no chance of blocking,
|
78
|
+
* so ignore any potential fcntl errors since they don't matter
|
79
|
+
*/
|
80
|
+
flags = fcntl(fd, F_GETFL);
|
81
|
+
if (flags > 0 && !(flags & O_NONBLOCK))
|
82
|
+
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
83
|
+
}
|
84
|
+
|
85
|
+
/* It's safe to call mysql_close() on an already closed connection. */
|
86
|
+
mysql_close(client);
|
87
|
+
xfree(ptr);
|
88
|
+
}
|
89
|
+
|
90
|
+
static VALUE nogvl_close(void * ptr) {
|
91
|
+
mysql_close((MYSQL *)ptr);
|
92
|
+
return Qnil;
|
93
|
+
}
|
94
|
+
|
95
|
+
static VALUE allocate(VALUE klass)
|
96
|
+
{
|
97
|
+
MYSQL * client;
|
98
|
+
|
99
|
+
return Data_Make_Struct(
|
100
|
+
klass,
|
101
|
+
MYSQL,
|
102
|
+
NULL,
|
103
|
+
rb_mysql_client_free,
|
104
|
+
client
|
105
|
+
);
|
106
|
+
}
|
107
|
+
|
108
|
+
static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket)
|
109
|
+
{
|
110
|
+
MYSQL * client;
|
111
|
+
struct nogvl_connect_args args;
|
112
|
+
|
113
|
+
Data_Get_Struct(self, MYSQL, client);
|
114
|
+
|
115
|
+
args.host = NIL_P(host) ? "localhost" : StringValuePtr(host);
|
116
|
+
args.unix_socket = NIL_P(socket) ? NULL : StringValuePtr(socket);
|
117
|
+
args.port = NIL_P(port) ? 3306 : NUM2INT(port);
|
118
|
+
args.user = NIL_P(user) ? NULL : StringValuePtr(user);
|
119
|
+
args.passwd = NIL_P(pass) ? NULL : StringValuePtr(pass);
|
120
|
+
args.db = NIL_P(database) ? NULL : StringValuePtr(database);
|
121
|
+
args.mysql = client;
|
122
|
+
args.client_flag = 0;
|
123
|
+
|
124
|
+
if (rb_thread_blocking_region(nogvl_connect, &args, RUBY_UBF_IO, 0) == Qfalse)
|
125
|
+
{
|
126
|
+
// unable to connect
|
127
|
+
return rb_raise_mysql2_error(client);
|
128
|
+
}
|
129
|
+
|
130
|
+
return self;
|
131
|
+
}
|
132
|
+
|
133
|
+
/*
|
134
|
+
* Immediately disconnect from the server, normally the garbage collector
|
135
|
+
* will disconnect automatically when a connection is no longer needed.
|
136
|
+
* Explicitly closing this will free up server resources sooner than waiting
|
137
|
+
* for the garbage collector.
|
138
|
+
*/
|
139
|
+
static VALUE rb_mysql_client_close(VALUE self) {
|
140
|
+
MYSQL *client;
|
141
|
+
|
142
|
+
Data_Get_Struct(self, MYSQL, client);
|
143
|
+
|
144
|
+
REQUIRE_OPEN_DB(client);
|
145
|
+
rb_thread_blocking_region(nogvl_close, client, RUBY_UBF_IO, 0);
|
146
|
+
|
147
|
+
return Qnil;
|
148
|
+
}
|
149
|
+
|
150
|
+
/*
|
151
|
+
* mysql_send_query is unlikely to block since most queries are small
|
152
|
+
* enough to fit in a socket buffer, but sometimes large UPDATE and
|
153
|
+
* INSERTs will cause the process to block
|
154
|
+
*/
|
155
|
+
static VALUE nogvl_send_query(void *ptr) {
|
156
|
+
struct nogvl_send_query_args *args = ptr;
|
157
|
+
int rv;
|
158
|
+
const char *sql = StringValuePtr(args->sql);
|
159
|
+
long sql_len = RSTRING_LEN(args->sql);
|
160
|
+
|
161
|
+
rv = mysql_send_query(args->mysql, sql, sql_len);
|
162
|
+
|
163
|
+
return rv == 0 ? Qtrue : Qfalse;
|
164
|
+
}
|
165
|
+
|
166
|
+
/*
|
167
|
+
* even though we did rb_thread_select before calling this, a large
|
168
|
+
* response can overflow the socket buffers and cause us to eventually
|
169
|
+
* block while calling mysql_read_query_result
|
170
|
+
*/
|
171
|
+
static VALUE nogvl_read_query_result(void *ptr) {
|
172
|
+
MYSQL * client = ptr;
|
173
|
+
my_bool res = mysql_read_query_result(client);
|
174
|
+
|
175
|
+
return res == 0 ? Qtrue : Qfalse;
|
176
|
+
}
|
177
|
+
|
178
|
+
/* mysql_store_result may (unlikely) read rows off the socket */
|
179
|
+
static VALUE nogvl_store_result(void *ptr) {
|
180
|
+
MYSQL * client = ptr;
|
181
|
+
return (VALUE)mysql_store_result(client);
|
182
|
+
}
|
183
|
+
|
184
|
+
static VALUE rb_mysql_client_async_result(VALUE self) {
|
185
|
+
MYSQL * client;
|
186
|
+
MYSQL_RES * result;
|
187
|
+
|
188
|
+
Data_Get_Struct(self, MYSQL, client);
|
189
|
+
|
190
|
+
REQUIRE_OPEN_DB(client);
|
191
|
+
if (rb_thread_blocking_region(nogvl_read_query_result, client, RUBY_UBF_IO, 0) == Qfalse) {
|
192
|
+
return rb_raise_mysql2_error(client);
|
193
|
+
}
|
194
|
+
|
195
|
+
result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, client, RUBY_UBF_IO, 0);
|
196
|
+
if (result == NULL) {
|
197
|
+
if (mysql_field_count(client) != 0) {
|
198
|
+
rb_raise_mysql2_error(client);
|
199
|
+
}
|
200
|
+
return Qnil;
|
201
|
+
}
|
202
|
+
|
203
|
+
VALUE resultObj = rb_mysql_result_to_obj(result);
|
204
|
+
rb_iv_set(resultObj, "@encoding", rb_iv_get(self, "@encoding"));
|
205
|
+
return resultObj;
|
206
|
+
}
|
207
|
+
|
208
|
+
static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
|
209
|
+
struct nogvl_send_query_args args;
|
210
|
+
fd_set fdset;
|
211
|
+
int fd, retval;
|
212
|
+
int async = 0;
|
213
|
+
VALUE opts;
|
214
|
+
VALUE rb_async;
|
215
|
+
|
216
|
+
MYSQL * client;
|
217
|
+
|
218
|
+
if (rb_scan_args(argc, argv, "11", &args.sql, &opts) == 2) {
|
219
|
+
if ((rb_async = rb_hash_aref(opts, sym_async)) != Qnil) {
|
220
|
+
async = rb_async == Qtrue ? 1 : 0;
|
221
|
+
}
|
222
|
+
}
|
223
|
+
|
224
|
+
Check_Type(args.sql, T_STRING);
|
225
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
226
|
+
rb_encoding *conn_enc = rb_to_encoding(rb_iv_get(self, "@encoding"));
|
227
|
+
// ensure the string is in the encoding the connection is expecting
|
228
|
+
args.sql = rb_str_export_to_enc(args.sql, conn_enc);
|
229
|
+
#endif
|
230
|
+
|
231
|
+
Data_Get_Struct(self, MYSQL, client);
|
232
|
+
|
233
|
+
REQUIRE_OPEN_DB(client);
|
234
|
+
|
235
|
+
args.mysql = client;
|
236
|
+
if (rb_thread_blocking_region(nogvl_send_query, &args, RUBY_UBF_IO, 0) == Qfalse) {
|
237
|
+
return rb_raise_mysql2_error(client);
|
238
|
+
}
|
239
|
+
|
240
|
+
if (!async) {
|
241
|
+
// the below code is largely from do_mysql
|
242
|
+
// http://github.com/datamapper/do
|
243
|
+
fd = client->net.fd;
|
244
|
+
for(;;) {
|
245
|
+
FD_ZERO(&fdset);
|
246
|
+
FD_SET(fd, &fdset);
|
247
|
+
|
248
|
+
retval = rb_thread_select(fd + 1, &fdset, NULL, NULL, NULL);
|
249
|
+
|
250
|
+
if (retval < 0) {
|
251
|
+
rb_sys_fail(0);
|
252
|
+
}
|
253
|
+
|
254
|
+
if (retval > 0) {
|
255
|
+
break;
|
256
|
+
}
|
257
|
+
}
|
258
|
+
|
259
|
+
return rb_mysql_client_async_result(self);
|
260
|
+
} else {
|
261
|
+
return Qnil;
|
262
|
+
}
|
263
|
+
}
|
264
|
+
|
265
|
+
static VALUE rb_mysql_client_escape(VALUE self, VALUE str) {
|
266
|
+
MYSQL * client;
|
267
|
+
VALUE newStr;
|
268
|
+
unsigned long newLen, oldLen;
|
269
|
+
|
270
|
+
Check_Type(str, T_STRING);
|
271
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
272
|
+
rb_encoding *default_internal_enc = rb_default_internal_encoding();
|
273
|
+
rb_encoding *conn_enc = rb_to_encoding(rb_iv_get(self, "@encoding"));
|
274
|
+
// ensure the string is in the encoding the connection is expecting
|
275
|
+
str = rb_str_export_to_enc(str, conn_enc);
|
276
|
+
#endif
|
277
|
+
|
278
|
+
oldLen = RSTRING_LEN(str);
|
279
|
+
char escaped[(oldLen*2)+1];
|
280
|
+
|
281
|
+
Data_Get_Struct(self, MYSQL, client);
|
282
|
+
|
283
|
+
REQUIRE_OPEN_DB(client);
|
284
|
+
newLen = mysql_real_escape_string(client, escaped, StringValuePtr(str), RSTRING_LEN(str));
|
285
|
+
if (newLen == oldLen) {
|
286
|
+
// no need to return a new ruby string if nothing changed
|
287
|
+
return str;
|
288
|
+
} else {
|
289
|
+
newStr = rb_str_new(escaped, newLen);
|
290
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
291
|
+
rb_enc_associate(newStr, conn_enc);
|
292
|
+
if (default_internal_enc) {
|
293
|
+
newStr = rb_str_export_to_enc(newStr, default_internal_enc);
|
294
|
+
}
|
295
|
+
#endif
|
296
|
+
return newStr;
|
297
|
+
}
|
298
|
+
}
|
299
|
+
|
300
|
+
static VALUE rb_mysql_client_info(RB_MYSQL_UNUSED VALUE self) {
|
301
|
+
VALUE version = rb_hash_new(), client_info;
|
302
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
303
|
+
rb_encoding *default_internal_enc = rb_default_internal_encoding();
|
304
|
+
rb_encoding *conn_enc = rb_to_encoding(rb_iv_get(self, "@encoding"));
|
305
|
+
#endif
|
306
|
+
|
307
|
+
rb_hash_aset(version, sym_id, LONG2NUM(mysql_get_client_version()));
|
308
|
+
client_info = rb_str_new2(mysql_get_client_info());
|
309
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
310
|
+
rb_enc_associate(client_info, conn_enc);
|
311
|
+
if (default_internal_enc) {
|
312
|
+
client_info = rb_str_export_to_enc(client_info, default_internal_enc);
|
313
|
+
}
|
314
|
+
#endif
|
315
|
+
rb_hash_aset(version, sym_version, client_info);
|
316
|
+
return version;
|
317
|
+
}
|
318
|
+
|
319
|
+
static VALUE rb_mysql_client_server_info(VALUE self) {
|
320
|
+
MYSQL * client;
|
321
|
+
VALUE version, server_info;
|
322
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
323
|
+
rb_encoding *default_internal_enc = rb_default_internal_encoding();
|
324
|
+
rb_encoding *conn_enc = rb_to_encoding(rb_iv_get(self, "@encoding"));
|
325
|
+
#endif
|
326
|
+
|
327
|
+
Data_Get_Struct(self, MYSQL, client);
|
328
|
+
REQUIRE_OPEN_DB(client);
|
329
|
+
|
330
|
+
version = rb_hash_new();
|
331
|
+
rb_hash_aset(version, sym_id, LONG2FIX(mysql_get_server_version(client)));
|
332
|
+
server_info = rb_str_new2(mysql_get_server_info(client));
|
333
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
334
|
+
rb_enc_associate(server_info, conn_enc);
|
335
|
+
if (default_internal_enc) {
|
336
|
+
server_info = rb_str_export_to_enc(server_info, default_internal_enc);
|
337
|
+
}
|
338
|
+
#endif
|
339
|
+
rb_hash_aset(version, sym_version, server_info);
|
340
|
+
return version;
|
341
|
+
}
|
342
|
+
|
343
|
+
static VALUE rb_mysql_client_socket(VALUE self) {
|
344
|
+
MYSQL * client;
|
345
|
+
Data_Get_Struct(self, MYSQL, client);
|
346
|
+
REQUIRE_OPEN_DB(client);
|
347
|
+
return INT2NUM(client->net.fd);
|
348
|
+
}
|
349
|
+
|
350
|
+
static VALUE rb_mysql_client_last_id(VALUE self) {
|
351
|
+
MYSQL * client;
|
352
|
+
Data_Get_Struct(self, MYSQL, client);
|
353
|
+
REQUIRE_OPEN_DB(client);
|
354
|
+
return ULL2NUM(mysql_insert_id(client));
|
355
|
+
}
|
356
|
+
|
357
|
+
static VALUE rb_mysql_client_affected_rows(VALUE self) {
|
358
|
+
MYSQL * client;
|
359
|
+
Data_Get_Struct(self, MYSQL, client);
|
360
|
+
REQUIRE_OPEN_DB(client);
|
361
|
+
return ULL2NUM(mysql_affected_rows(client));
|
362
|
+
}
|
363
|
+
|
364
|
+
static VALUE set_reconnect(VALUE self, VALUE value)
|
365
|
+
{
|
366
|
+
my_bool reconnect;
|
367
|
+
MYSQL * client;
|
368
|
+
|
369
|
+
Data_Get_Struct(self, MYSQL, client);
|
370
|
+
|
371
|
+
if(!NIL_P(value)) {
|
372
|
+
reconnect = value == Qfalse ? 0 : 1;
|
373
|
+
|
374
|
+
/* set default reconnect behavior */
|
375
|
+
if (mysql_options(client, MYSQL_OPT_RECONNECT, &reconnect)) {
|
376
|
+
/* TODO: warning - unable to set reconnect behavior */
|
377
|
+
rb_warn("%s\n", mysql_error(client));
|
378
|
+
}
|
379
|
+
}
|
380
|
+
return value;
|
381
|
+
}
|
382
|
+
|
383
|
+
static VALUE set_connect_timeout(VALUE self, VALUE value)
|
384
|
+
{
|
385
|
+
unsigned int connect_timeout = 0;
|
386
|
+
MYSQL * client;
|
387
|
+
|
388
|
+
Data_Get_Struct(self, MYSQL, client);
|
389
|
+
|
390
|
+
if(!NIL_P(value)) {
|
391
|
+
connect_timeout = NUM2INT(value);
|
392
|
+
if(0 == connect_timeout) return value;
|
393
|
+
|
394
|
+
/* set default connection timeout behavior */
|
395
|
+
if (mysql_options(client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout)) {
|
396
|
+
/* TODO: warning - unable to set connection timeout */
|
397
|
+
rb_warn("%s\n", mysql_error(client));
|
398
|
+
}
|
399
|
+
}
|
400
|
+
return value;
|
401
|
+
}
|
402
|
+
|
403
|
+
static VALUE set_charset_name(VALUE self, VALUE value)
|
404
|
+
{
|
405
|
+
char * charset_name;
|
406
|
+
MYSQL * client;
|
407
|
+
|
408
|
+
Data_Get_Struct(self, MYSQL, client);
|
409
|
+
|
410
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
411
|
+
VALUE new_encoding, old_encoding;
|
412
|
+
new_encoding = rb_funcall(cMysql2Client, intern_encoding_from_charset, 1, value);
|
413
|
+
if (new_encoding == Qnil) {
|
414
|
+
rb_raise(cMysql2Error, "Unsupported charset: '%s'", RSTRING_PTR(value));
|
415
|
+
} else {
|
416
|
+
old_encoding = rb_iv_get(self, "@encoding");
|
417
|
+
if (old_encoding == Qnil) {
|
418
|
+
rb_iv_set(self, "@encoding", new_encoding);
|
419
|
+
}
|
420
|
+
}
|
421
|
+
#endif
|
422
|
+
|
423
|
+
charset_name = StringValuePtr(value);
|
424
|
+
|
425
|
+
if (mysql_options(client, MYSQL_SET_CHARSET_NAME, charset_name)) {
|
426
|
+
/* TODO: warning - unable to set charset */
|
427
|
+
rb_warn("%s\n", mysql_error(client));
|
428
|
+
}
|
429
|
+
|
430
|
+
return value;
|
431
|
+
}
|
432
|
+
|
433
|
+
static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE capath, VALUE cipher)
|
434
|
+
{
|
435
|
+
MYSQL * client;
|
436
|
+
Data_Get_Struct(self, MYSQL, client);
|
437
|
+
|
438
|
+
if(!NIL_P(ca) || !NIL_P(key)) {
|
439
|
+
mysql_ssl_set(client,
|
440
|
+
NIL_P(key) ? NULL : StringValuePtr(key),
|
441
|
+
NIL_P(cert) ? NULL : StringValuePtr(cert),
|
442
|
+
NIL_P(ca) ? NULL : StringValuePtr(ca),
|
443
|
+
NIL_P(capath) ? NULL : StringValuePtr(capath),
|
444
|
+
NIL_P(cipher) ? NULL : StringValuePtr(cipher));
|
445
|
+
}
|
446
|
+
|
447
|
+
return self;
|
448
|
+
}
|
449
|
+
|
450
|
+
static VALUE init_connection(VALUE self)
|
451
|
+
{
|
452
|
+
MYSQL * client;
|
453
|
+
Data_Get_Struct(self, MYSQL, client);
|
454
|
+
|
455
|
+
if (rb_thread_blocking_region(nogvl_init, client, RUBY_UBF_IO, 0) == Qfalse) {
|
456
|
+
/* TODO: warning - not enough memory? */
|
457
|
+
return rb_raise_mysql2_error(client);
|
458
|
+
}
|
459
|
+
|
460
|
+
return self;
|
461
|
+
}
|
462
|
+
|
463
|
+
/* Ruby Extension initializer */
|
464
|
+
void Init_mysql2() {
|
465
|
+
mMysql2 = rb_define_module("Mysql2");
|
466
|
+
cMysql2Client = rb_define_class_under(mMysql2, "Client", rb_cObject);
|
467
|
+
|
468
|
+
rb_define_alloc_func(cMysql2Client, allocate);
|
469
|
+
|
470
|
+
rb_define_method(cMysql2Client, "close", rb_mysql_client_close, 0);
|
471
|
+
rb_define_method(cMysql2Client, "query", rb_mysql_client_query, -1);
|
472
|
+
rb_define_method(cMysql2Client, "escape", rb_mysql_client_escape, 1);
|
473
|
+
rb_define_method(cMysql2Client, "info", rb_mysql_client_info, 0);
|
474
|
+
rb_define_method(cMysql2Client, "server_info", rb_mysql_client_server_info, 0);
|
475
|
+
rb_define_method(cMysql2Client, "socket", rb_mysql_client_socket, 0);
|
476
|
+
rb_define_method(cMysql2Client, "async_result", rb_mysql_client_async_result, 0);
|
477
|
+
rb_define_method(cMysql2Client, "last_id", rb_mysql_client_last_id, 0);
|
478
|
+
rb_define_method(cMysql2Client, "affected_rows", rb_mysql_client_affected_rows, 0);
|
479
|
+
|
480
|
+
rb_define_private_method(cMysql2Client, "reconnect=", set_reconnect, 1);
|
481
|
+
rb_define_private_method(cMysql2Client, "connect_timeout=", set_connect_timeout, 1);
|
482
|
+
rb_define_private_method(cMysql2Client, "charset_name=", set_charset_name, 1);
|
483
|
+
rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5);
|
484
|
+
rb_define_private_method(cMysql2Client, "init_connection", init_connection, 0);
|
485
|
+
rb_define_private_method(cMysql2Client, "connect", rb_connect, 6);
|
486
|
+
|
487
|
+
cMysql2Error = rb_const_get(mMysql2, rb_intern("Error"));
|
488
|
+
|
489
|
+
init_mysql2_result();
|
490
|
+
|
491
|
+
sym_id = ID2SYM(rb_intern("id"));
|
492
|
+
sym_version = ID2SYM(rb_intern("version"));
|
493
|
+
sym_async = ID2SYM(rb_intern("async"));
|
494
|
+
|
495
|
+
intern_encoding_from_charset = rb_intern("encoding_from_charset");
|
496
|
+
}
|