mysql2 0.1.4 → 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +19 -0
- data/README.rdoc +4 -4
- data/VERSION +1 -1
- data/benchmark/active_record.rb +17 -5
- data/benchmark/query_with_mysql_casting.rb +82 -0
- data/benchmark/{query.rb → query_without_mysql_casting.rb} +0 -0
- data/benchmark/sequel.rb +39 -0
- data/ext/extconf.rb +3 -0
- data/ext/mysql2_ext.c +234 -66
- data/ext/mysql2_ext.h +69 -9
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +25 -8
- data/lib/mysql2.rb +1 -1
- data/lib/sequel/adapters/mysql2.rb +238 -0
- data/mysql2.gemspec +6 -3
- data/spec/active_record/active_record_spec.rb +124 -1
- data/spec/mysql2/client_spec.rb +15 -0
- data/spec/mysql2/result_spec.rb +89 -42
- metadata +7 -4
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,24 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 0.1.5 (May 12th, 2010)
|
4
|
+
* quite a few patches from Eric Wong related to thread-safety, non-blocking I/O and general cleanup
|
5
|
+
** wrap mysql_real_connect with rb_thread_blocking_region
|
6
|
+
** release GVL for possibly blocking mysql_* library calls
|
7
|
+
** [cleanup] quiet down warnings
|
8
|
+
** [cleanup] make all C symbols static
|
9
|
+
** add Mysql2::Client#close method
|
10
|
+
** correctly free the wrapped result in case of EOF
|
11
|
+
** Fix memory leak from the result wrapper struct itself
|
12
|
+
** make Mysql2::Client destructor safely non-blocking
|
13
|
+
* bug fixes for ActiveRecord adapter
|
14
|
+
** added casting for default values since they all come back from Mysql as strings (!?!)
|
15
|
+
** missing constant was added
|
16
|
+
** fixed a typo in the show_variable method
|
17
|
+
* switched over sscanf for date/time parsing in C
|
18
|
+
* made some specs a little finer-grained
|
19
|
+
* initial Sequel adapter added
|
20
|
+
* updated query benchmarks to reflect the difference between casting in C and in Ruby
|
21
|
+
|
3
22
|
## 0.1.4 (April 23rd, 2010)
|
4
23
|
* optimization: implemented a local cache for rows that are lazily created in ruby during iteration. The MySQL C result is freed as soon as all the results have been cached
|
5
24
|
* optimization: implemented a local cache for field names so every row reuses the same objects as field names/keys
|
data/README.rdoc
CHANGED
@@ -130,11 +130,11 @@ Me: Yep, but it's API is considerably more complex *and* is 2-3x slower.
|
|
130
130
|
Performing a basic "SELECT * FROM" query on a table with 30k rows and fields of nearly every Ruby-representable data type,
|
131
131
|
then iterating over every row using an #each like method yielding a block:
|
132
132
|
|
133
|
-
|
133
|
+
# These results are from the query_with_mysql_casting.rb script in the benchmarks folder
|
134
134
|
user system total real
|
135
135
|
Mysql2
|
136
|
-
0.
|
136
|
+
0.890000 0.190000 1.080000 ( 2.028887)
|
137
137
|
Mysql
|
138
|
-
|
138
|
+
7.330000 0.350000 7.680000 ( 8.013160)
|
139
139
|
do_mysql
|
140
|
-
1.
|
140
|
+
1.740000 0.220000 1.960000 ( 2.909290)
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.5
|
data/benchmark/active_record.rb
CHANGED
@@ -15,24 +15,36 @@ mysql_opts = {
|
|
15
15
|
:database => 'test'
|
16
16
|
}
|
17
17
|
|
18
|
-
class
|
18
|
+
class Mysql2Model < ActiveRecord::Base
|
19
|
+
set_table_name :mysql2_test
|
20
|
+
end
|
21
|
+
|
22
|
+
class MysqlModel < ActiveRecord::Base
|
19
23
|
set_table_name :mysql2_test
|
20
24
|
end
|
21
25
|
|
22
26
|
Benchmark.bmbm do |x|
|
23
27
|
x.report do
|
24
|
-
|
28
|
+
Mysql2Model.establish_connection(mysql2_opts)
|
25
29
|
puts "Mysql2"
|
26
30
|
number_of.times do
|
27
|
-
|
31
|
+
Mysql2Model.all(:limit => 1000).each{ |r|
|
32
|
+
r.attributes.keys.each{ |k|
|
33
|
+
r.send(k.to_sym)
|
34
|
+
}
|
35
|
+
}
|
28
36
|
end
|
29
37
|
end
|
30
38
|
|
31
39
|
x.report do
|
32
|
-
|
40
|
+
MysqlModel.establish_connection(mysql_opts)
|
33
41
|
puts "Mysql"
|
34
42
|
number_of.times do
|
35
|
-
|
43
|
+
MysqlModel.all(:limit => 1000).each{ |r|
|
44
|
+
r.attributes.keys.each{ |k|
|
45
|
+
r.send(k.to_sym)
|
46
|
+
}
|
47
|
+
}
|
36
48
|
end
|
37
49
|
end
|
38
50
|
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'benchmark'
|
5
|
+
require 'mysql'
|
6
|
+
require 'mysql2_ext'
|
7
|
+
require 'do_mysql'
|
8
|
+
|
9
|
+
number_of = 100
|
10
|
+
database = 'test'
|
11
|
+
sql = "SELECT * FROM mysql2_test LIMIT 100"
|
12
|
+
|
13
|
+
class Mysql
|
14
|
+
include Enumerable
|
15
|
+
end
|
16
|
+
|
17
|
+
def mysql_cast(type, value)
|
18
|
+
case type
|
19
|
+
when Mysql::Field::TYPE_NULL
|
20
|
+
nil
|
21
|
+
when Mysql::Field::TYPE_TINY, Mysql::Field::TYPE_SHORT, Mysql::Field::TYPE_LONG,
|
22
|
+
Mysql::Field::TYPE_INT24, Mysql::Field::TYPE_LONGLONG, Mysql::Field::TYPE_YEAR
|
23
|
+
value.to_i
|
24
|
+
when Mysql::Field::TYPE_DECIMAL, Mysql::Field::TYPE_NEWDECIMAL
|
25
|
+
BigDecimal.new(value)
|
26
|
+
when Mysql::Field::TYPE_DOUBLE, Mysql::Field::TYPE_FLOAT
|
27
|
+
value.to_f
|
28
|
+
when Mysql::Field::TYPE_DATE
|
29
|
+
Date.parse(value)
|
30
|
+
when Mysql::Field::TYPE_TIME, Mysql::Field::TYPE_DATETIME, Mysql::Field::TYPE_TIMESTAMP
|
31
|
+
Time.parse(value)
|
32
|
+
when Mysql::Field::TYPE_BLOB, Mysql::Field::TYPE_BIT, Mysql::Field::TYPE_STRING,
|
33
|
+
Mysql::Field::TYPE_VAR_STRING, Mysql::Field::TYPE_CHAR, Mysql::Field::TYPE_SET
|
34
|
+
Mysql::Field::TYPE_ENUM
|
35
|
+
value
|
36
|
+
else
|
37
|
+
value
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
Benchmark.bmbm do |x|
|
42
|
+
mysql2 = Mysql2::Client.new(:host => "localhost", :username => "root")
|
43
|
+
mysql2.query "USE #{database}"
|
44
|
+
x.report do
|
45
|
+
puts "Mysql2"
|
46
|
+
number_of.times do
|
47
|
+
mysql2_result = mysql2.query sql
|
48
|
+
mysql2_result.each(:symbolize_keys => true) do |res|
|
49
|
+
# puts res.inspect
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
mysql = Mysql.new("localhost", "root")
|
55
|
+
mysql.query "USE #{database}"
|
56
|
+
x.report do
|
57
|
+
puts "Mysql"
|
58
|
+
number_of.times do
|
59
|
+
mysql_result = mysql.query sql
|
60
|
+
fields = mysql_result.fetch_fields
|
61
|
+
mysql_result.each do |row|
|
62
|
+
row_hash = {}
|
63
|
+
row.each_with_index do |f, j|
|
64
|
+
row_hash[fields[j].name.to_sym] = mysql_cast(fields[j].type, row[j])
|
65
|
+
end
|
66
|
+
# puts row_hash.inspect
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
do_mysql = DataObjects::Connection.new("mysql://localhost/#{database}")
|
72
|
+
command = DataObjects::Mysql::Command.new do_mysql, sql
|
73
|
+
x.report do
|
74
|
+
puts "do_mysql"
|
75
|
+
number_of.times do
|
76
|
+
do_result = command.execute_reader
|
77
|
+
do_result.each do |res|
|
78
|
+
# puts res.inspect
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
File without changes
|
data/benchmark/sequel.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'benchmark'
|
6
|
+
require 'sequel'
|
7
|
+
require 'sequel/adapters/do'
|
8
|
+
|
9
|
+
number_of = 10
|
10
|
+
mysql2_opts = "mysql2://localhost/test"
|
11
|
+
mysql_opts = "mysql://localhost/test"
|
12
|
+
do_mysql_opts = "do:mysql://localhost/test"
|
13
|
+
|
14
|
+
class Mysql2Model < Sequel::Model(Sequel.connect(mysql2_opts)[:mysql2_test]); end
|
15
|
+
class MysqlModel < Sequel::Model(Sequel.connect(mysql_opts)[:mysql2_test]); end
|
16
|
+
class DOMysqlModel < Sequel::Model(Sequel.connect(do_mysql_opts)[:mysql2_test]); end
|
17
|
+
|
18
|
+
Benchmark.bmbm do |x|
|
19
|
+
x.report do
|
20
|
+
puts "Mysql2"
|
21
|
+
number_of.times do
|
22
|
+
Mysql2Model.limit(1000).all
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
x.report do
|
27
|
+
puts "do:mysql"
|
28
|
+
number_of.times do
|
29
|
+
DOMysqlModel.limit(1000).all
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
x.report do
|
34
|
+
puts "Mysql"
|
35
|
+
number_of.times do
|
36
|
+
MysqlModel.limit(1000).all
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/ext/extconf.rb
CHANGED
data/ext/mysql2_ext.c
CHANGED
@@ -1,54 +1,105 @@
|
|
1
1
|
#include "mysql2_ext.h"
|
2
2
|
|
3
|
+
/*
|
4
|
+
* non-blocking mysql_*() functions that we won't be wrapping since
|
5
|
+
* they do not appear to hit the network nor issue any interruptible
|
6
|
+
* or blocking system calls.
|
7
|
+
*
|
8
|
+
* - mysql_affected_rows()
|
9
|
+
* - mysql_error()
|
10
|
+
* - mysql_fetch_fields()
|
11
|
+
* - mysql_fetch_lengths() - calls cli_fetch_lengths or emb_fetch_lengths
|
12
|
+
* - mysql_field_count()
|
13
|
+
* - mysql_get_client_info()
|
14
|
+
* - mysql_get_client_version()
|
15
|
+
* - mysql_get_server_info()
|
16
|
+
* - mysql_get_server_version()
|
17
|
+
* - mysql_insert_id()
|
18
|
+
* - mysql_num_fields()
|
19
|
+
* - mysql_num_rows()
|
20
|
+
* - mysql_options()
|
21
|
+
* - mysql_real_escape_string()
|
22
|
+
* - mysql_ssl_set()
|
23
|
+
*/
|
24
|
+
|
25
|
+
static VALUE nogvl_init(void *ptr) {
|
26
|
+
struct nogvl_connect_args *args = ptr;
|
27
|
+
|
28
|
+
/* may initialize embedded server and read /etc/services off disk */
|
29
|
+
args->mysql = mysql_init(NULL);
|
30
|
+
|
31
|
+
return args->mysql == NULL ? Qfalse : Qtrue;
|
32
|
+
}
|
33
|
+
|
34
|
+
static VALUE nogvl_connect(void *ptr)
|
35
|
+
{
|
36
|
+
struct nogvl_connect_args *args = ptr;
|
37
|
+
MYSQL *client;
|
38
|
+
|
39
|
+
client = mysql_real_connect(args->mysql, args->host,
|
40
|
+
args->user, args->passwd,
|
41
|
+
args->db, args->port, args->unix_socket,
|
42
|
+
args->client_flag);
|
43
|
+
|
44
|
+
return client ? Qtrue : Qfalse;
|
45
|
+
}
|
46
|
+
|
3
47
|
/* Mysql2::Client */
|
4
48
|
static VALUE rb_mysql_client_new(int argc, VALUE * argv, VALUE klass) {
|
5
|
-
|
49
|
+
mysql2_client_wrapper * client;
|
50
|
+
struct nogvl_connect_args args = {
|
51
|
+
.host = "localhost",
|
52
|
+
.user = NULL,
|
53
|
+
.passwd = NULL,
|
54
|
+
.db = NULL,
|
55
|
+
.port = 3306,
|
56
|
+
.unix_socket = NULL,
|
57
|
+
.client_flag = 0
|
58
|
+
};
|
6
59
|
VALUE obj, opts;
|
7
60
|
VALUE rb_host, rb_socket, rb_port, rb_database,
|
8
61
|
rb_username, rb_password, rb_reconnect,
|
9
62
|
rb_connect_timeout;
|
10
63
|
VALUE rb_ssl_client_key, rb_ssl_client_cert, rb_ssl_ca_cert,
|
11
64
|
rb_ssl_ca_path, rb_ssl_cipher;
|
12
|
-
char *host = "localhost", *socket = NULL, *username = NULL,
|
13
|
-
*password = NULL, *database = NULL;
|
14
65
|
char *ssl_client_key = NULL, *ssl_client_cert = NULL, *ssl_ca_cert = NULL,
|
15
66
|
*ssl_ca_path = NULL, *ssl_cipher = NULL;
|
16
|
-
unsigned int
|
67
|
+
unsigned int connect_timeout = 0;
|
17
68
|
my_bool reconnect = 1;
|
18
69
|
|
19
|
-
obj = Data_Make_Struct(klass,
|
70
|
+
obj = Data_Make_Struct(klass, mysql2_client_wrapper, NULL, rb_mysql_client_free, client);
|
20
71
|
|
21
72
|
if (rb_scan_args(argc, argv, "01", &opts) == 1) {
|
22
73
|
Check_Type(opts, T_HASH);
|
23
74
|
|
24
75
|
if ((rb_host = rb_hash_aref(opts, sym_host)) != Qnil) {
|
25
76
|
Check_Type(rb_host, T_STRING);
|
26
|
-
host = RSTRING_PTR(rb_host);
|
77
|
+
args.host = RSTRING_PTR(rb_host);
|
27
78
|
}
|
28
79
|
|
29
80
|
if ((rb_socket = rb_hash_aref(opts, sym_socket)) != Qnil) {
|
30
81
|
Check_Type(rb_socket, T_STRING);
|
31
|
-
|
82
|
+
args.unix_socket = RSTRING_PTR(rb_socket);
|
32
83
|
}
|
33
84
|
|
34
85
|
if ((rb_port = rb_hash_aref(opts, sym_port)) != Qnil) {
|
35
86
|
Check_Type(rb_port, T_FIXNUM);
|
36
|
-
port = FIX2INT(rb_port);
|
87
|
+
args.port = FIX2INT(rb_port);
|
37
88
|
}
|
38
89
|
|
39
90
|
if ((rb_username = rb_hash_aref(opts, sym_username)) != Qnil) {
|
40
91
|
Check_Type(rb_username, T_STRING);
|
41
|
-
|
92
|
+
args.user = RSTRING_PTR(rb_username);
|
42
93
|
}
|
43
94
|
|
44
95
|
if ((rb_password = rb_hash_aref(opts, sym_password)) != Qnil) {
|
45
96
|
Check_Type(rb_password, T_STRING);
|
46
|
-
|
97
|
+
args.passwd = RSTRING_PTR(rb_password);
|
47
98
|
}
|
48
99
|
|
49
100
|
if ((rb_database = rb_hash_aref(opts, sym_database)) != Qnil) {
|
50
101
|
Check_Type(rb_database, T_STRING);
|
51
|
-
|
102
|
+
args.db = RSTRING_PTR(rb_database);
|
52
103
|
}
|
53
104
|
|
54
105
|
if ((rb_reconnect = rb_hash_aref(opts, sym_reconnect)) != Qnil) {
|
@@ -87,82 +138,145 @@ static VALUE rb_mysql_client_new(int argc, VALUE * argv, VALUE klass) {
|
|
87
138
|
}
|
88
139
|
}
|
89
140
|
|
90
|
-
if (
|
141
|
+
if (rb_thread_blocking_region(nogvl_init, &args, RUBY_UBF_IO, 0) == Qfalse) {
|
91
142
|
// TODO: warning - not enough memory?
|
92
|
-
rb_raise(cMysql2Error, "%s", mysql_error(
|
143
|
+
rb_raise(cMysql2Error, "%s", mysql_error(args.mysql));
|
93
144
|
return Qnil;
|
94
145
|
}
|
95
146
|
|
96
147
|
// set default reconnect behavior
|
97
|
-
if (mysql_options(
|
148
|
+
if (mysql_options(args.mysql, MYSQL_OPT_RECONNECT, &reconnect) != 0) {
|
98
149
|
// TODO: warning - unable to set reconnect behavior
|
99
|
-
rb_warn("%s\n", mysql_error(
|
150
|
+
rb_warn("%s\n", mysql_error(args.mysql));
|
100
151
|
}
|
101
152
|
|
102
153
|
// set default connection timeout behavior
|
103
|
-
if (connect_timeout != 0 && mysql_options(
|
154
|
+
if (connect_timeout != 0 && mysql_options(args.mysql, MYSQL_OPT_CONNECT_TIMEOUT, (const char *)&connect_timeout) != 0) {
|
104
155
|
// TODO: warning - unable to set connection timeout
|
105
|
-
rb_warn("%s\n", mysql_error(
|
156
|
+
rb_warn("%s\n", mysql_error(args.mysql));
|
106
157
|
}
|
107
158
|
|
108
159
|
// force the encoding to utf8
|
109
|
-
if (mysql_options(
|
160
|
+
if (mysql_options(args.mysql, MYSQL_SET_CHARSET_NAME, "utf8") != 0) {
|
110
161
|
// TODO: warning - unable to set charset
|
111
|
-
rb_warn("%s\n", mysql_error(
|
162
|
+
rb_warn("%s\n", mysql_error(args.mysql));
|
112
163
|
}
|
113
164
|
|
114
165
|
if (ssl_ca_cert != NULL || ssl_client_key != NULL) {
|
115
|
-
mysql_ssl_set(
|
166
|
+
mysql_ssl_set(args.mysql, ssl_client_key, ssl_client_cert, ssl_ca_cert, ssl_ca_path, ssl_cipher);
|
116
167
|
}
|
117
168
|
|
118
|
-
if (
|
169
|
+
if (rb_thread_blocking_region(nogvl_connect, &args, RUBY_UBF_IO, 0) == Qfalse) {
|
119
170
|
// unable to connect
|
120
|
-
rb_raise(cMysql2Error, "%s", mysql_error(
|
171
|
+
rb_raise(cMysql2Error, "%s", mysql_error(args.mysql));
|
121
172
|
return Qnil;
|
122
173
|
}
|
123
174
|
|
175
|
+
client->client = args.mysql;
|
176
|
+
|
124
177
|
rb_obj_call_init(obj, argc, argv);
|
125
178
|
return obj;
|
126
179
|
}
|
127
180
|
|
128
|
-
static VALUE rb_mysql_client_init(int argc, VALUE * argv, VALUE self) {
|
181
|
+
static VALUE rb_mysql_client_init(RB_MYSQL_UNUSED int argc, RB_MYSQL_UNUSED VALUE * argv, VALUE self) {
|
129
182
|
return self;
|
130
183
|
}
|
131
184
|
|
132
|
-
void rb_mysql_client_free(void *
|
133
|
-
|
134
|
-
|
135
|
-
|
185
|
+
static void rb_mysql_client_free(void * ptr) {
|
186
|
+
mysql2_client_wrapper * client = ptr;
|
187
|
+
|
188
|
+
if (client->client) {
|
189
|
+
/*
|
190
|
+
* we'll send a QUIT message to the server, but that message is more of a
|
191
|
+
* formality than a hard requirement since the socket is getting shutdown
|
192
|
+
* anyways, so ensure the socket write does not block our interpreter
|
193
|
+
*/
|
194
|
+
int fd = client->client->net.fd;
|
195
|
+
int flags;
|
196
|
+
|
197
|
+
if (fd >= 0) {
|
198
|
+
/*
|
199
|
+
* if the socket is dead we have no chance of blocking,
|
200
|
+
* so ignore any potential fcntl errors since they don't matter
|
201
|
+
*/
|
202
|
+
flags = fcntl(fd, F_GETFL);
|
203
|
+
if (flags > 0 && !(flags & O_NONBLOCK))
|
204
|
+
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
205
|
+
}
|
206
|
+
|
207
|
+
mysql_close(client->client);
|
136
208
|
}
|
209
|
+
xfree(ptr);
|
210
|
+
}
|
211
|
+
|
212
|
+
static VALUE nogvl_close(void * ptr) {
|
213
|
+
mysql_close((MYSQL *)ptr);
|
214
|
+
return Qnil;
|
215
|
+
}
|
216
|
+
|
217
|
+
/*
|
218
|
+
* Immediately disconnect from the server, normally the garbage collector
|
219
|
+
* will disconnect automatically when a connection is no longer needed.
|
220
|
+
* Explicitly closing this will free up server resources sooner than waiting
|
221
|
+
* for the garbage collector.
|
222
|
+
*/
|
223
|
+
static VALUE rb_mysql_client_close(VALUE self) {
|
224
|
+
mysql2_client_wrapper *client;
|
225
|
+
|
226
|
+
Data_Get_Struct(self, mysql2_client_wrapper, client);
|
227
|
+
|
228
|
+
if (client->client) {
|
229
|
+
rb_thread_blocking_region(nogvl_close, client->client, RUBY_UBF_IO, 0);
|
230
|
+
client->client = NULL;
|
231
|
+
} else {
|
232
|
+
rb_raise(cMysql2Error, "already closed MySQL connection");
|
233
|
+
}
|
234
|
+
return Qnil;
|
235
|
+
}
|
236
|
+
|
237
|
+
/*
|
238
|
+
* mysql_send_query is unlikely to block since most queries are small
|
239
|
+
* enough to fit in a socket buffer, but sometimes large UPDATE and
|
240
|
+
* INSERTs will cause the process to block
|
241
|
+
*/
|
242
|
+
static VALUE nogvl_send_query(void *ptr)
|
243
|
+
{
|
244
|
+
struct nogvl_send_query_args *args = ptr;
|
245
|
+
int rv;
|
246
|
+
const char *sql = RSTRING_PTR(args->sql);
|
247
|
+
long sql_len = RSTRING_LEN(args->sql);
|
248
|
+
|
249
|
+
rv = mysql_send_query(args->mysql, sql, sql_len);
|
250
|
+
|
251
|
+
return rv == 0 ? Qtrue : Qfalse;
|
137
252
|
}
|
138
253
|
|
139
254
|
static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
|
140
|
-
|
141
|
-
MYSQL_RES * result;
|
255
|
+
struct nogvl_send_query_args args;
|
142
256
|
fd_set fdset;
|
143
257
|
int fd, retval;
|
144
258
|
int async = 0;
|
145
|
-
VALUE
|
259
|
+
VALUE opts;
|
146
260
|
VALUE rb_async;
|
147
261
|
|
148
|
-
if (rb_scan_args(argc, argv, "11", &sql, &opts) == 2) {
|
262
|
+
if (rb_scan_args(argc, argv, "11", &args.sql, &opts) == 2) {
|
149
263
|
if ((rb_async = rb_hash_aref(opts, sym_async)) != Qnil) {
|
150
264
|
async = rb_async == Qtrue ? 1 : 0;
|
151
265
|
}
|
152
266
|
}
|
153
267
|
|
154
|
-
Check_Type(sql, T_STRING);
|
268
|
+
Check_Type(args.sql, T_STRING);
|
155
269
|
|
156
|
-
GetMysql2Client(self,
|
157
|
-
if (
|
158
|
-
rb_raise(cMysql2Error, "%s", mysql_error(
|
270
|
+
GetMysql2Client(self, args.mysql);
|
271
|
+
if (rb_thread_blocking_region(nogvl_send_query, &args, RUBY_UBF_IO, 0) == Qfalse) {
|
272
|
+
rb_raise(cMysql2Error, "%s", mysql_error(args.mysql));
|
159
273
|
return Qnil;
|
160
274
|
}
|
161
275
|
|
162
276
|
if (!async) {
|
163
277
|
// the below code is largely from do_mysql
|
164
278
|
// http://github.com/datamapper/do
|
165
|
-
fd =
|
279
|
+
fd = args.mysql->net.fd;
|
166
280
|
for(;;) {
|
167
281
|
FD_ZERO(&fdset);
|
168
282
|
FD_SET(fd, &fdset);
|
@@ -208,7 +322,7 @@ static VALUE rb_mysql_client_escape(VALUE self, VALUE str) {
|
|
208
322
|
}
|
209
323
|
}
|
210
324
|
|
211
|
-
static VALUE rb_mysql_client_info(VALUE self) {
|
325
|
+
static VALUE rb_mysql_client_info(RB_MYSQL_UNUSED VALUE self) {
|
212
326
|
VALUE version = rb_hash_new();
|
213
327
|
rb_hash_aset(version, sym_id, LONG2FIX(mysql_get_client_version()));
|
214
328
|
rb_hash_aset(version, sym_version, rb_str_new2(mysql_get_client_info()));
|
@@ -231,17 +345,37 @@ static VALUE rb_mysql_client_socket(VALUE self) {
|
|
231
345
|
return INT2NUM(client->net.fd);
|
232
346
|
}
|
233
347
|
|
348
|
+
/*
|
349
|
+
* even though we did rb_thread_select before calling this, a large
|
350
|
+
* response can overflow the socket buffers and cause us to eventually
|
351
|
+
* block while calling mysql_read_query_result
|
352
|
+
*/
|
353
|
+
static VALUE nogvl_read_query_result(void *ptr)
|
354
|
+
{
|
355
|
+
MYSQL * client = ptr;
|
356
|
+
my_bool res = mysql_read_query_result(client);
|
357
|
+
|
358
|
+
return res == 0 ? Qtrue : Qfalse;
|
359
|
+
}
|
360
|
+
|
361
|
+
/* mysql_store_result may (unlikely) read rows off the socket */
|
362
|
+
static VALUE nogvl_store_result(void *ptr)
|
363
|
+
{
|
364
|
+
MYSQL * client = ptr;
|
365
|
+
return (VALUE)mysql_store_result(client);
|
366
|
+
}
|
367
|
+
|
234
368
|
static VALUE rb_mysql_client_async_result(VALUE self) {
|
235
369
|
MYSQL * client;
|
236
370
|
MYSQL_RES * result;
|
237
371
|
GetMysql2Client(self, client);
|
238
372
|
|
239
|
-
if (
|
373
|
+
if (rb_thread_blocking_region(nogvl_read_query_result, client, RUBY_UBF_IO, 0) == Qfalse) {
|
240
374
|
rb_raise(cMysql2Error, "%s", mysql_error(client));
|
241
375
|
return Qnil;
|
242
376
|
}
|
243
377
|
|
244
|
-
result =
|
378
|
+
result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, client, RUBY_UBF_IO, 0);
|
245
379
|
if (result == NULL) {
|
246
380
|
if (mysql_field_count(client) != 0) {
|
247
381
|
rb_raise(cMysql2Error, "%s", mysql_error(client));
|
@@ -280,15 +414,23 @@ static VALUE rb_mysql_result_to_obj(MYSQL_RES * r) {
|
|
280
414
|
return obj;
|
281
415
|
}
|
282
416
|
|
283
|
-
|
284
|
-
|
285
|
-
if (
|
286
|
-
mysql_free_result(
|
287
|
-
|
417
|
+
/* this may be called manually or during GC */
|
418
|
+
static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) {
|
419
|
+
if (wrapper && wrapper->resultFreed != 1) {
|
420
|
+
mysql_free_result(wrapper->result);
|
421
|
+
wrapper->resultFreed = 1;
|
288
422
|
}
|
289
423
|
}
|
290
424
|
|
291
|
-
|
425
|
+
/* this is called during GC */
|
426
|
+
static void rb_mysql_result_free(void * wrapper) {
|
427
|
+
mysql2_result_wrapper * w = wrapper;
|
428
|
+
/* FIXME: this may call flush_use_result, which can hit the socket */
|
429
|
+
rb_mysql_result_free_result(w);
|
430
|
+
xfree(wrapper);
|
431
|
+
}
|
432
|
+
|
433
|
+
static void rb_mysql_result_mark(void * wrapper) {
|
292
434
|
mysql2_result_wrapper * w = wrapper;
|
293
435
|
if (w) {
|
294
436
|
rb_gc_mark(w->fields);
|
@@ -296,14 +438,26 @@ void rb_mysql_result_mark(void * wrapper) {
|
|
296
438
|
}
|
297
439
|
}
|
298
440
|
|
441
|
+
/*
|
442
|
+
* for small results, this won't hit the network, but there's no
|
443
|
+
* reliable way for us to tell this so we'll always release the GVL
|
444
|
+
* to be safe
|
445
|
+
*/
|
446
|
+
static VALUE nogvl_fetch_row(void *ptr)
|
447
|
+
{
|
448
|
+
MYSQL_RES *result = ptr;
|
449
|
+
|
450
|
+
return (VALUE)mysql_fetch_row(result);
|
451
|
+
}
|
452
|
+
|
299
453
|
static VALUE rb_mysql_result_fetch_row(int argc, VALUE * argv, VALUE self) {
|
300
454
|
VALUE rowHash, opts, block;
|
301
455
|
mysql2_result_wrapper * wrapper;
|
302
456
|
MYSQL_ROW row;
|
303
457
|
MYSQL_FIELD * fields = NULL;
|
304
|
-
struct tm parsedTime;
|
305
458
|
unsigned int i = 0, symbolizeKeys = 0;
|
306
459
|
unsigned long * fieldLengths;
|
460
|
+
void * ptr;
|
307
461
|
|
308
462
|
GetMysql2Result(self, wrapper);
|
309
463
|
|
@@ -314,7 +468,8 @@ static VALUE rb_mysql_result_fetch_row(int argc, VALUE * argv, VALUE self) {
|
|
314
468
|
}
|
315
469
|
}
|
316
470
|
|
317
|
-
|
471
|
+
ptr = wrapper->result;
|
472
|
+
row = (MYSQL_ROW)rb_thread_blocking_region(nogvl_fetch_row, ptr, RUBY_UBF_IO, 0);
|
318
473
|
if (row == NULL) {
|
319
474
|
return Qnil;
|
320
475
|
}
|
@@ -353,8 +508,10 @@ static VALUE rb_mysql_result_fetch_row(int argc, VALUE * argv, VALUE self) {
|
|
353
508
|
case MYSQL_TYPE_NULL: // NULL-type field
|
354
509
|
val = Qnil;
|
355
510
|
break;
|
356
|
-
case MYSQL_TYPE_TINY: // TINYINT field
|
357
511
|
case MYSQL_TYPE_BIT: // BIT field (MySQL 5.0.3 and up)
|
512
|
+
val = rb_str_new(row[i], fieldLengths[i]);
|
513
|
+
break;
|
514
|
+
case MYSQL_TYPE_TINY: // TINYINT field
|
358
515
|
case MYSQL_TYPE_SHORT: // SMALLINT field
|
359
516
|
case MYSQL_TYPE_LONG: // INTEGER field
|
360
517
|
case MYSQL_TYPE_INT24: // MEDIUMINT field
|
@@ -370,32 +527,42 @@ static VALUE rb_mysql_result_fetch_row(int argc, VALUE * argv, VALUE self) {
|
|
370
527
|
case MYSQL_TYPE_DOUBLE: // DOUBLE or REAL field
|
371
528
|
val = rb_float_new(strtod(row[i], NULL));
|
372
529
|
break;
|
373
|
-
case MYSQL_TYPE_TIME:
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
strptime(row[i], "%T", &parsedTime);
|
378
|
-
val = rb_funcall(rb_cTime, intern_local, 6, INT2NUM(1900+parsedTime.tm_year), INT2NUM(parsedTime.tm_mon+1), INT2NUM(parsedTime.tm_mday), INT2NUM(parsedTime.tm_hour), INT2NUM(parsedTime.tm_min), INT2NUM(parsedTime.tm_sec));
|
379
|
-
}
|
530
|
+
case MYSQL_TYPE_TIME: { // TIME field
|
531
|
+
int hour, min, sec, tokens;
|
532
|
+
tokens = sscanf(row[i], "%2d:%2d:%2d", &hour, &min, &sec);
|
533
|
+
val = rb_funcall(rb_cTime, intern_local, 6, INT2NUM(0), INT2NUM(1), INT2NUM(1), INT2NUM(hour), INT2NUM(min), INT2NUM(sec));
|
380
534
|
break;
|
535
|
+
}
|
381
536
|
case MYSQL_TYPE_TIMESTAMP: // TIMESTAMP field
|
382
|
-
case MYSQL_TYPE_DATETIME:
|
383
|
-
|
537
|
+
case MYSQL_TYPE_DATETIME: { // DATETIME field
|
538
|
+
int year, month, day, hour, min, sec, tokens;
|
539
|
+
tokens = sscanf(row[i], "%4d-%2d-%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
|
540
|
+
if (year+month+day+hour+min+sec == 0) {
|
384
541
|
val = Qnil;
|
385
542
|
} else {
|
386
|
-
|
387
|
-
|
543
|
+
if (month < 1 || day < 1) {
|
544
|
+
rb_raise(cMysql2Error, "Invalid date: %s", row[i]);
|
545
|
+
} else {
|
546
|
+
val = rb_funcall(rb_cTime, intern_local, 6, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec));
|
547
|
+
}
|
388
548
|
}
|
389
549
|
break;
|
550
|
+
}
|
390
551
|
case MYSQL_TYPE_DATE: // DATE field
|
391
|
-
case MYSQL_TYPE_NEWDATE:
|
392
|
-
|
552
|
+
case MYSQL_TYPE_NEWDATE: { // Newer const used > 5.0
|
553
|
+
int year, month, day, tokens;
|
554
|
+
tokens = sscanf(row[i], "%4d-%2d-%2d", &year, &month, &day);
|
555
|
+
if (year+month+day == 0) {
|
393
556
|
val = Qnil;
|
394
557
|
} else {
|
395
|
-
|
396
|
-
|
558
|
+
if (month < 1 || day < 1) {
|
559
|
+
rb_raise(cMysql2Error, "Invalid date: %s", row[i]);
|
560
|
+
} else {
|
561
|
+
val = rb_funcall(cDate, intern_new, 3, INT2NUM(year), INT2NUM(month), INT2NUM(day));
|
562
|
+
}
|
397
563
|
}
|
398
564
|
break;
|
565
|
+
}
|
399
566
|
case MYSQL_TYPE_TINY_BLOB:
|
400
567
|
case MYSQL_TYPE_MEDIUM_BLOB:
|
401
568
|
case MYSQL_TYPE_LONG_BLOB:
|
@@ -464,7 +631,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
|
|
464
631
|
|
465
632
|
if (row == Qnil) {
|
466
633
|
// we don't need the mysql C dataset around anymore, peace it
|
467
|
-
|
634
|
+
rb_mysql_result_free_result(wrapper);
|
468
635
|
return Qnil;
|
469
636
|
}
|
470
637
|
|
@@ -474,7 +641,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
|
|
474
641
|
}
|
475
642
|
if (wrapper->lastRowProcessed == wrapper->numberOfRows) {
|
476
643
|
// we don't need the mysql C dataset around anymore, peace it
|
477
|
-
|
644
|
+
rb_mysql_result_free_result(wrapper);
|
478
645
|
}
|
479
646
|
}
|
480
647
|
|
@@ -495,6 +662,7 @@ void Init_mysql2_ext() {
|
|
495
662
|
VALUE cMysql2Client = rb_define_class_under(mMysql2, "Client", rb_cObject);
|
496
663
|
rb_define_singleton_method(cMysql2Client, "new", rb_mysql_client_new, -1);
|
497
664
|
rb_define_method(cMysql2Client, "initialize", rb_mysql_client_init, -1);
|
665
|
+
rb_define_method(cMysql2Client, "close", rb_mysql_client_close, 0);
|
498
666
|
rb_define_method(cMysql2Client, "query", rb_mysql_client_query, -1);
|
499
667
|
rb_define_method(cMysql2Client, "escape", rb_mysql_client_escape, 1);
|
500
668
|
rb_define_method(cMysql2Client, "info", rb_mysql_client_info, 0);
|
@@ -537,4 +705,4 @@ void Init_mysql2_ext() {
|
|
537
705
|
utf8Encoding = rb_enc_find_index("UTF-8");
|
538
706
|
binaryEncoding = rb_enc_find_index("binary");
|
539
707
|
#endif
|
540
|
-
}
|
708
|
+
}
|