mysql2 0.2.6 → 0.2.7
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/.rvmrc +1 -0
- data/CHANGELOG.md +25 -0
- data/Gemfile +3 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +9 -1
- data/benchmark/active_record.rb +2 -4
- data/benchmark/escape.rb +3 -6
- data/benchmark/query_with_mysql_casting.rb +3 -6
- data/benchmark/query_without_mysql_casting.rb +3 -6
- data/benchmark/sequel.rb +4 -6
- data/benchmark/setup_db.rb +17 -13
- data/ext/mysql2/client.c +179 -41
- data/ext/mysql2/client.h +2 -2
- data/ext/mysql2/extconf.rb +8 -1
- data/ext/mysql2/mysql2_ext.h +10 -0
- data/ext/mysql2/result.c +25 -12
- data/ext/mysql2/result.h +2 -2
- data/lib/active_record/connection_adapters/em_mysql2_adapter.rb +10 -9
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +7 -7
- data/lib/active_record/fiber_patches.rb +10 -10
- data/lib/mysql2.rb +1 -1
- data/lib/mysql2/client.rb +5 -0
- data/lib/mysql2/em.rb +10 -6
- data/lib/mysql2/em_fiber.rb +31 -0
- data/lib/mysql2/version.rb +3 -0
- data/mysql2.gemspec +16 -73
- data/spec/em/em_fiber_spec.rb +22 -0
- data/spec/mysql2/client_spec.rb +44 -1
- data/spec/mysql2/error_spec.rb +47 -3
- data/spec/mysql2/result_spec.rb +18 -3
- data/spec/spec_helper.rb +1 -2
- data/tasks/benchmarks.rake +15 -3
- data/tasks/compile.rake +0 -1
- metadata +121 -44
- data/VERSION +0 -1
- data/tasks/jeweler.rake +0 -17
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use 1.9.2@mysql2 --create
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,30 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 0.2.7 (March 28th, 2011)
|
4
|
+
* various fixes for em_mysql2 and fiber usage
|
5
|
+
* use our own Mysql2IndexDefinition class for better compatibility across ActiveRecord versions
|
6
|
+
* ensure the query is a string earlier in the Mysql2::Client#query codepath for 1.9
|
7
|
+
* only set binary ruby encoding on fields that have a binary flag *and* encoding set
|
8
|
+
* a few various optimizations
|
9
|
+
* add support for :read_timeout to be set on a connection
|
10
|
+
* Fix to install with MariDB on Windows
|
11
|
+
* add fibered em connection without activerecord
|
12
|
+
* fix some 1.9.3 compilation warnings
|
13
|
+
* add LD_RUN_PATH when using hard coded mysql paths - this should help users with MySQL installed in non-standard locations
|
14
|
+
* for windows support, duplicate the socket from libmysql and create a temporary CRT fd
|
15
|
+
* fix for handling years before 1970 on Windows
|
16
|
+
* fixes to the Fiber adapter
|
17
|
+
* set wait_timeout maximum on Windows to 2147483
|
18
|
+
* update supported range for Time objects
|
19
|
+
* upon being required, make sure the libmysql we're using is the one we were built against
|
20
|
+
* add Mysql2::Client#thread_id
|
21
|
+
* add Mysql2::Client#ping
|
22
|
+
* switch connection check in AR adapter to use Mysql2::Client#ping for efficiency
|
23
|
+
* prefer linking against thread-safe version of libmysqlclient
|
24
|
+
* define RSTRING_NOT_MODIFIED for an awesome rbx speed boost
|
25
|
+
* expose Mysql2::Client#encoding in 1.9, make sure we set the error message and sqlstate encodings accordingly
|
26
|
+
* do not segfault when raising for invalid charset (found in 1.9.3dev)
|
27
|
+
|
3
28
|
## 0.2.6 (October 19th, 2010)
|
4
29
|
* version bump since the 0.2.5 win32 binary gems were broken
|
5
30
|
|
data/Gemfile
ADDED
data/MIT-LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -146,7 +146,7 @@ This would be helpful if you wanted to iterate over the results in a streaming m
|
|
146
146
|
|
147
147
|
== ActiveRecord
|
148
148
|
|
149
|
-
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".
|
149
|
+
To use the ActiveRecord driver (with our without rails), all you should need to do is have this gem installed and set the adapter in your database.yml to "mysql2".
|
150
150
|
That was easy right? :)
|
151
151
|
|
152
152
|
== Asynchronous ActiveRecord
|
@@ -232,6 +232,14 @@ then iterating over every row using an #each like method yielding a block:
|
|
232
232
|
Mysql
|
233
233
|
7.500000 0.210000 7.710000 ( 8.065871)
|
234
234
|
|
235
|
+
== Development
|
236
|
+
|
237
|
+
To run the tests, you can use RVM and Bundler to create a pristine environment for mysql2 development/hacking.
|
238
|
+
Use 'bundle install' to install the necessary development and testing gems:
|
239
|
+
|
240
|
+
bundle install
|
241
|
+
rake
|
242
|
+
|
235
243
|
== Special Thanks
|
236
244
|
|
237
245
|
* Eric Wong - for the contribution (and the informative explanations) of some thread-safety, non-blocking I/O and cleanup patches. You rock dude
|
data/benchmark/active_record.rb
CHANGED
@@ -27,9 +27,8 @@ class MysqlModel < ActiveRecord::Base
|
|
27
27
|
end
|
28
28
|
|
29
29
|
Benchmark.bmbm do |x|
|
30
|
-
x.report do
|
30
|
+
x.report "Mysql2" do
|
31
31
|
Mysql2Model.establish_connection(mysql2_opts)
|
32
|
-
puts "Mysql2"
|
33
32
|
number_of.times do
|
34
33
|
Mysql2Model.all(:limit => 1000).each{ |r|
|
35
34
|
r.attributes.keys.each{ |k|
|
@@ -39,9 +38,8 @@ Benchmark.bmbm do |x|
|
|
39
38
|
end
|
40
39
|
end
|
41
40
|
|
42
|
-
x.report do
|
41
|
+
x.report "Mysql" do
|
43
42
|
MysqlModel.establish_connection(mysql_opts)
|
44
|
-
puts "Mysql"
|
45
43
|
number_of.times do
|
46
44
|
MysqlModel.all(:limit => 1000).each{ |r|
|
47
45
|
r.attributes.keys.each{ |k|
|
data/benchmark/escape.rb
CHANGED
@@ -10,24 +10,21 @@ require 'do_mysql'
|
|
10
10
|
def run_escape_benchmarks(str, number_of = 1000)
|
11
11
|
Benchmark.bmbm do |x|
|
12
12
|
mysql = Mysql.new("localhost", "root")
|
13
|
-
x.report do
|
14
|
-
puts "Mysql #{str.inspect}"
|
13
|
+
x.report "Mysql #{str.inspect}" do
|
15
14
|
number_of.times do
|
16
15
|
mysql.quote str
|
17
16
|
end
|
18
17
|
end
|
19
18
|
|
20
19
|
mysql2 = Mysql2::Client.new(:host => "localhost", :username => "root")
|
21
|
-
x.report do
|
22
|
-
puts "Mysql2 #{str.inspect}"
|
20
|
+
x.report "Mysql2 #{str.inspect}" do
|
23
21
|
number_of.times do
|
24
22
|
mysql2.escape str
|
25
23
|
end
|
26
24
|
end
|
27
25
|
|
28
26
|
do_mysql = DataObjects::Connection.new("mysql://localhost/test")
|
29
|
-
x.report do
|
30
|
-
puts "do_mysql #{str.inspect}"
|
27
|
+
x.report "do_mysql #{str.inspect}" do
|
31
28
|
number_of.times do
|
32
29
|
do_mysql.quote_string str
|
33
30
|
end
|
@@ -42,8 +42,7 @@ end
|
|
42
42
|
Benchmark.bmbm do |x|
|
43
43
|
mysql2 = Mysql2::Client.new(:host => "localhost", :username => "root")
|
44
44
|
mysql2.query "USE #{database}"
|
45
|
-
x.report do
|
46
|
-
puts "Mysql2"
|
45
|
+
x.report "Mysql2" do
|
47
46
|
number_of.times do
|
48
47
|
mysql2_result = mysql2.query sql, :symbolize_keys => true
|
49
48
|
mysql2_result.each do |res|
|
@@ -54,8 +53,7 @@ Benchmark.bmbm do |x|
|
|
54
53
|
|
55
54
|
mysql = Mysql.new("localhost", "root")
|
56
55
|
mysql.query "USE #{database}"
|
57
|
-
x.report do
|
58
|
-
puts "Mysql"
|
56
|
+
x.report "Mysql" do
|
59
57
|
number_of.times do
|
60
58
|
mysql_result = mysql.query sql
|
61
59
|
fields = mysql_result.fetch_fields
|
@@ -71,8 +69,7 @@ Benchmark.bmbm do |x|
|
|
71
69
|
|
72
70
|
do_mysql = DataObjects::Connection.new("mysql://localhost/#{database}")
|
73
71
|
command = do_mysql.create_command sql
|
74
|
-
x.report do
|
75
|
-
puts "do_mysql"
|
72
|
+
x.report "do_mysql" do
|
76
73
|
number_of.times do
|
77
74
|
do_result = command.execute_reader
|
78
75
|
do_result.each do |res|
|
@@ -14,8 +14,7 @@ sql = "SELECT * FROM mysql2_test LIMIT 100"
|
|
14
14
|
Benchmark.bmbm do |x|
|
15
15
|
mysql2 = Mysql2::Client.new(:host => "localhost", :username => "root")
|
16
16
|
mysql2.query "USE #{database}"
|
17
|
-
x.report do
|
18
|
-
puts "Mysql2"
|
17
|
+
x.report "Mysql2" do
|
19
18
|
number_of.times do
|
20
19
|
mysql2_result = mysql2.query sql, :symbolize_keys => true
|
21
20
|
mysql2_result.each do |res|
|
@@ -26,8 +25,7 @@ Benchmark.bmbm do |x|
|
|
26
25
|
|
27
26
|
mysql = Mysql.new("localhost", "root")
|
28
27
|
mysql.query "USE #{database}"
|
29
|
-
x.report do
|
30
|
-
puts "Mysql"
|
28
|
+
x.report "Mysql" do
|
31
29
|
number_of.times do
|
32
30
|
mysql_result = mysql.query sql
|
33
31
|
mysql_result.each_hash do |res|
|
@@ -38,8 +36,7 @@ Benchmark.bmbm do |x|
|
|
38
36
|
|
39
37
|
do_mysql = DataObjects::Connection.new("mysql://localhost/#{database}")
|
40
38
|
command = DataObjects::Mysql::Command.new do_mysql, sql
|
41
|
-
x.report do
|
42
|
-
puts "do_mysql"
|
39
|
+
x.report "do_mysql" do
|
43
40
|
number_of.times do
|
44
41
|
do_result = command.execute_reader
|
45
42
|
do_result.each do |res|
|
data/benchmark/sequel.rb
CHANGED
@@ -3,6 +3,7 @@ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
|
3
3
|
|
4
4
|
require 'rubygems'
|
5
5
|
require 'benchmark'
|
6
|
+
require 'mysql2'
|
6
7
|
require 'sequel'
|
7
8
|
require 'sequel/adapters/do'
|
8
9
|
|
@@ -16,22 +17,19 @@ class MysqlModel < Sequel::Model(Sequel.connect(mysql_opts)[:mysql2_test]); end
|
|
16
17
|
class DOMysqlModel < Sequel::Model(Sequel.connect(do_mysql_opts)[:mysql2_test]); end
|
17
18
|
|
18
19
|
Benchmark.bmbm do |x|
|
19
|
-
x.report do
|
20
|
-
puts "Mysql2"
|
20
|
+
x.report "Mysql2" do
|
21
21
|
number_of.times do
|
22
22
|
Mysql2Model.limit(1000).all
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
-
x.report do
|
27
|
-
puts "do:mysql"
|
26
|
+
x.report "do:mysql" do
|
28
27
|
number_of.times do
|
29
28
|
DOMysqlModel.limit(1000).all
|
30
29
|
end
|
31
30
|
end
|
32
31
|
|
33
|
-
x.report do
|
34
|
-
puts "Mysql"
|
32
|
+
x.report "Mysql" do
|
35
33
|
number_of.times do
|
36
34
|
MysqlModel.limit(1000).all
|
37
35
|
end
|
data/benchmark/setup_db.rb
CHANGED
@@ -76,6 +76,8 @@ end
|
|
76
76
|
|
77
77
|
puts "Creating #{num} records"
|
78
78
|
num.times do |n|
|
79
|
+
five_words = Faker::Lorem.words(rand(5))
|
80
|
+
twenty5_paragraphs = Faker::Lorem.paragraphs(rand(25))
|
79
81
|
insert_record(
|
80
82
|
:bit_test => 1,
|
81
83
|
:tiny_int_test => rand(128),
|
@@ -93,23 +95,25 @@ num.times do |n|
|
|
93
95
|
:timestamp_test => '2010-4-4 11:44:00',
|
94
96
|
:time_test => '11:44:00',
|
95
97
|
:year_test => Time.now.year,
|
96
|
-
:char_test =>
|
97
|
-
:varchar_test =>
|
98
|
-
:binary_test =>
|
99
|
-
:varbinary_test =>
|
100
|
-
:tiny_blob_test =>
|
98
|
+
:char_test => five_words,
|
99
|
+
:varchar_test => five_words,
|
100
|
+
:binary_test => five_words,
|
101
|
+
:varbinary_test => five_words,
|
102
|
+
:tiny_blob_test => five_words,
|
101
103
|
:tiny_text_test => Faker::Lorem.paragraph(rand(5)),
|
102
|
-
:blob_test =>
|
103
|
-
:text_test =>
|
104
|
-
:medium_blob_test =>
|
105
|
-
:medium_text_test =>
|
106
|
-
:long_blob_test =>
|
107
|
-
:long_text_test =>
|
104
|
+
:blob_test => twenty5_paragraphs,
|
105
|
+
:text_test => twenty5_paragraphs,
|
106
|
+
:medium_blob_test => twenty5_paragraphs,
|
107
|
+
:medium_text_test => twenty5_paragraphs,
|
108
|
+
:long_blob_test => twenty5_paragraphs,
|
109
|
+
:long_text_test => twenty5_paragraphs,
|
108
110
|
:enum_test => ['val1', 'val2'].rand,
|
109
111
|
:set_test => ['val1', 'val2', 'val1,val2'].rand
|
110
112
|
)
|
111
|
-
|
112
|
-
|
113
|
+
if n % 100 == 0
|
114
|
+
$stdout.putc '.'
|
115
|
+
$stdout.flush
|
116
|
+
end
|
113
117
|
end
|
114
118
|
puts
|
115
119
|
puts "Done"
|
data/ext/mysql2/client.c
CHANGED
@@ -5,13 +5,12 @@
|
|
5
5
|
VALUE cMysql2Client;
|
6
6
|
extern VALUE mMysql2, cMysql2Error;
|
7
7
|
static VALUE intern_encoding_from_charset;
|
8
|
-
static
|
8
|
+
static VALUE sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as, sym_array;
|
9
9
|
static ID intern_merge, intern_error_number_eql, intern_sql_state_eql;
|
10
10
|
|
11
11
|
#define REQUIRE_OPEN_DB(wrapper) \
|
12
12
|
if(wrapper->closed) { \
|
13
13
|
rb_raise(cMysql2Error, "closed MySQL connection"); \
|
14
|
-
return Qnil; \
|
15
14
|
}
|
16
15
|
|
17
16
|
#define MARK_CONN_INACTIVE(conn) \
|
@@ -74,10 +73,24 @@ static void rb_mysql_client_mark(void * wrapper) {
|
|
74
73
|
}
|
75
74
|
}
|
76
75
|
|
77
|
-
static VALUE rb_raise_mysql2_error(
|
78
|
-
VALUE
|
79
|
-
|
80
|
-
|
76
|
+
static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) {
|
77
|
+
VALUE rb_error_msg = rb_str_new2(mysql_error(wrapper->client));
|
78
|
+
VALUE rb_sql_state = rb_tainted_str_new2(mysql_sqlstate(wrapper->client));
|
79
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
80
|
+
rb_encoding *default_internal_enc = rb_default_internal_encoding();
|
81
|
+
rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
|
82
|
+
|
83
|
+
rb_enc_associate(rb_error_msg, conn_enc);
|
84
|
+
rb_enc_associate(rb_sql_state, conn_enc);
|
85
|
+
if (default_internal_enc) {
|
86
|
+
rb_error_msg = rb_str_export_to_enc(rb_error_msg, default_internal_enc);
|
87
|
+
rb_sql_state = rb_str_export_to_enc(rb_sql_state, default_internal_enc);
|
88
|
+
}
|
89
|
+
#endif
|
90
|
+
|
91
|
+
VALUE e = rb_exc_new3(cMysql2Error, rb_error_msg);
|
92
|
+
rb_funcall(e, intern_error_number_eql, 1, UINT2NUM(mysql_errno(wrapper->client)));
|
93
|
+
rb_funcall(e, intern_sql_state_eql, 1, rb_sql_state);
|
81
94
|
rb_exc_raise(e);
|
82
95
|
return Qnil;
|
83
96
|
}
|
@@ -105,7 +118,11 @@ static VALUE nogvl_connect(void *ptr) {
|
|
105
118
|
}
|
106
119
|
|
107
120
|
static VALUE nogvl_close(void *ptr) {
|
108
|
-
mysql_client_wrapper *wrapper
|
121
|
+
mysql_client_wrapper *wrapper;
|
122
|
+
#ifndef _WIN32
|
123
|
+
int flags;
|
124
|
+
#endif
|
125
|
+
wrapper = ptr;
|
109
126
|
if (!wrapper->closed) {
|
110
127
|
wrapper->closed = 1;
|
111
128
|
|
@@ -119,16 +136,17 @@ static VALUE nogvl_close(void *ptr) {
|
|
119
136
|
* so ignore any potential fcntl errors since they don't matter
|
120
137
|
*/
|
121
138
|
#ifndef _WIN32
|
122
|
-
|
139
|
+
flags = fcntl(wrapper->client->net.fd, F_GETFL);
|
123
140
|
if (flags > 0 && !(flags & O_NONBLOCK))
|
124
141
|
fcntl(wrapper->client->net.fd, F_SETFL, flags | O_NONBLOCK);
|
125
142
|
#else
|
126
|
-
u_long iMode
|
143
|
+
u_long iMode;
|
144
|
+
iMode = 1;
|
127
145
|
ioctlsocket(wrapper->client->net.fd, FIONBIO, &iMode);
|
128
146
|
#endif
|
129
147
|
|
130
148
|
mysql_close(wrapper->client);
|
131
|
-
|
149
|
+
xfree(wrapper->client);
|
132
150
|
}
|
133
151
|
|
134
152
|
return Qnil;
|
@@ -149,7 +167,7 @@ static VALUE allocate(VALUE klass) {
|
|
149
167
|
wrapper->encoding = Qnil;
|
150
168
|
wrapper->active = 0;
|
151
169
|
wrapper->closed = 1;
|
152
|
-
wrapper->client = (MYSQL*)
|
170
|
+
wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL));
|
153
171
|
return obj;
|
154
172
|
}
|
155
173
|
|
@@ -168,7 +186,7 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
|
|
168
186
|
|
169
187
|
if (rb_thread_blocking_region(nogvl_connect, &args, RUBY_UBF_IO, 0) == Qfalse) {
|
170
188
|
// unable to connect
|
171
|
-
return rb_raise_mysql2_error(wrapper
|
189
|
+
return rb_raise_mysql2_error(wrapper);
|
172
190
|
}
|
173
191
|
|
174
192
|
return self;
|
@@ -226,13 +244,17 @@ static VALUE nogvl_store_result(void *ptr) {
|
|
226
244
|
|
227
245
|
static VALUE rb_mysql_client_async_result(VALUE self) {
|
228
246
|
MYSQL_RES * result;
|
247
|
+
VALUE resultObj;
|
248
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
249
|
+
mysql2_result_wrapper * result_wrapper;
|
250
|
+
#endif
|
229
251
|
GET_CLIENT(self);
|
230
252
|
|
231
253
|
REQUIRE_OPEN_DB(wrapper);
|
232
254
|
if (rb_thread_blocking_region(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
|
233
255
|
// an error occurred, mark this connection inactive
|
234
256
|
MARK_CONN_INACTIVE(self);
|
235
|
-
return rb_raise_mysql2_error(wrapper
|
257
|
+
return rb_raise_mysql2_error(wrapper);
|
236
258
|
}
|
237
259
|
|
238
260
|
result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper->client, RUBY_UBF_IO, 0);
|
@@ -242,17 +264,16 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
|
|
242
264
|
|
243
265
|
if (result == NULL) {
|
244
266
|
if (mysql_field_count(wrapper->client) != 0) {
|
245
|
-
rb_raise_mysql2_error(wrapper
|
267
|
+
rb_raise_mysql2_error(wrapper);
|
246
268
|
}
|
247
269
|
return Qnil;
|
248
270
|
}
|
249
271
|
|
250
|
-
|
272
|
+
resultObj = rb_mysql_result_to_obj(result);
|
251
273
|
// pass-through query options for result construction later
|
252
274
|
rb_iv_set(resultObj, "@query_options", rb_funcall(rb_iv_get(self, "@query_options"), rb_intern("dup"), 0));
|
253
275
|
|
254
276
|
#ifdef HAVE_RUBY_ENCODING_H
|
255
|
-
mysql2_result_wrapper * result_wrapper;
|
256
277
|
GetMysql2Result(resultObj, result_wrapper);
|
257
278
|
result_wrapper->encoding = wrapper->encoding;
|
258
279
|
#endif
|
@@ -264,7 +285,14 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
|
|
264
285
|
fd_set fdset;
|
265
286
|
int fd, retval;
|
266
287
|
int async = 0;
|
267
|
-
VALUE opts, defaults;
|
288
|
+
VALUE opts, defaults, read_timeout;
|
289
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
290
|
+
rb_encoding *conn_enc;
|
291
|
+
#endif
|
292
|
+
struct timeval tv;
|
293
|
+
struct timeval* tvp;
|
294
|
+
long int sec;
|
295
|
+
VALUE result;
|
268
296
|
GET_CLIENT(self);
|
269
297
|
|
270
298
|
REQUIRE_OPEN_DB(wrapper);
|
@@ -290,8 +318,9 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
|
|
290
318
|
opts = defaults;
|
291
319
|
}
|
292
320
|
|
321
|
+
Check_Type(args.sql, T_STRING);
|
293
322
|
#ifdef HAVE_RUBY_ENCODING_H
|
294
|
-
|
323
|
+
conn_enc = rb_to_encoding(wrapper->encoding);
|
295
324
|
// ensure the string is in the encoding the connection is expecting
|
296
325
|
args.sql = rb_str_export_to_enc(args.sql, conn_enc);
|
297
326
|
#endif
|
@@ -299,7 +328,24 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
|
|
299
328
|
if (rb_thread_blocking_region(nogvl_send_query, &args, RUBY_UBF_IO, 0) == Qfalse) {
|
300
329
|
// an error occurred, we're not active anymore
|
301
330
|
MARK_CONN_INACTIVE(self);
|
302
|
-
return rb_raise_mysql2_error(wrapper
|
331
|
+
return rb_raise_mysql2_error(wrapper);
|
332
|
+
}
|
333
|
+
|
334
|
+
read_timeout = rb_iv_get(self, "@read_timeout");
|
335
|
+
|
336
|
+
tvp = NULL;
|
337
|
+
if (!NIL_P(read_timeout)) {
|
338
|
+
Check_Type(read_timeout, T_FIXNUM);
|
339
|
+
tvp = &tv;
|
340
|
+
sec = FIX2INT(read_timeout);
|
341
|
+
// TODO: support partial seconds?
|
342
|
+
// also, this check is here for sanity, we also check up in Ruby
|
343
|
+
if (sec >= 0) {
|
344
|
+
tvp->tv_sec = sec;
|
345
|
+
} else {
|
346
|
+
rb_raise(cMysql2Error, "read_timeout must be a positive integer, you passed %ld", sec);
|
347
|
+
}
|
348
|
+
tvp->tv_usec = 0;
|
303
349
|
}
|
304
350
|
|
305
351
|
if (!async) {
|
@@ -307,10 +353,32 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
|
|
307
353
|
// http://github.com/datamapper/do
|
308
354
|
fd = wrapper->client->net.fd;
|
309
355
|
for(;;) {
|
356
|
+
int fd_set_fd = fd;
|
357
|
+
|
358
|
+
#ifdef _WIN32
|
359
|
+
WSAPROTOCOL_INFO wsa_pi;
|
360
|
+
// dupicate the SOCKET from libmysql
|
361
|
+
int r = WSADuplicateSocket(fd, GetCurrentProcessId(), &wsa_pi);
|
362
|
+
SOCKET s = WSASocket(wsa_pi.iAddressFamily, wsa_pi.iSocketType, wsa_pi.iProtocol, &wsa_pi, 0, 0);
|
363
|
+
// create the CRT fd so ruby can get back to the SOCKET
|
364
|
+
fd_set_fd = _open_osfhandle(s, O_RDWR|O_BINARY);
|
365
|
+
#endif
|
366
|
+
|
310
367
|
FD_ZERO(&fdset);
|
311
|
-
FD_SET(
|
368
|
+
FD_SET(fd_set_fd, &fdset);
|
312
369
|
|
313
|
-
retval = rb_thread_select(
|
370
|
+
retval = rb_thread_select(fd_set_fd + 1, &fdset, NULL, NULL, tvp);
|
371
|
+
|
372
|
+
#ifdef _WIN32
|
373
|
+
// cleanup the CRT fd
|
374
|
+
_close(fd_set_fd);
|
375
|
+
// cleanup the duplicated SOCKET
|
376
|
+
closesocket(s);
|
377
|
+
#endif
|
378
|
+
|
379
|
+
if (retval == 0) {
|
380
|
+
rb_raise(cMysql2Error, "Timeout waiting for a response from the last query. (waited %d seconds)", FIX2INT(read_timeout));
|
381
|
+
}
|
314
382
|
|
315
383
|
if (retval < 0) {
|
316
384
|
rb_sys_fail(0);
|
@@ -321,7 +389,7 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
|
|
321
389
|
}
|
322
390
|
}
|
323
391
|
|
324
|
-
|
392
|
+
result = rb_mysql_client_async_result(self);
|
325
393
|
|
326
394
|
return result;
|
327
395
|
} else {
|
@@ -330,45 +398,57 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
|
|
330
398
|
}
|
331
399
|
|
332
400
|
static VALUE rb_mysql_client_escape(VALUE self, VALUE str) {
|
333
|
-
|
401
|
+
unsigned char *newStr;
|
402
|
+
VALUE rb_str;
|
334
403
|
unsigned long newLen, oldLen;
|
404
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
405
|
+
rb_encoding *default_internal_enc;
|
406
|
+
rb_encoding *conn_enc;
|
407
|
+
#endif
|
335
408
|
GET_CLIENT(self);
|
336
409
|
|
337
410
|
REQUIRE_OPEN_DB(wrapper);
|
338
411
|
Check_Type(str, T_STRING);
|
339
412
|
#ifdef HAVE_RUBY_ENCODING_H
|
340
|
-
|
341
|
-
|
413
|
+
default_internal_enc = rb_default_internal_encoding();
|
414
|
+
conn_enc = rb_to_encoding(wrapper->encoding);
|
342
415
|
// ensure the string is in the encoding the connection is expecting
|
343
416
|
str = rb_str_export_to_enc(str, conn_enc);
|
344
417
|
#endif
|
345
418
|
|
346
419
|
oldLen = RSTRING_LEN(str);
|
347
|
-
newStr =
|
420
|
+
newStr = xmalloc(oldLen*2+1);
|
348
421
|
|
349
|
-
newLen = mysql_real_escape_string(wrapper->client,
|
422
|
+
newLen = mysql_real_escape_string(wrapper->client, (char *)newStr, StringValuePtr(str), oldLen);
|
350
423
|
if (newLen == oldLen) {
|
351
424
|
// no need to return a new ruby string if nothing changed
|
425
|
+
xfree(newStr);
|
352
426
|
return str;
|
353
427
|
} else {
|
354
|
-
|
428
|
+
rb_str = rb_str_new((const char*)newStr, newLen);
|
355
429
|
#ifdef HAVE_RUBY_ENCODING_H
|
356
|
-
rb_enc_associate(
|
430
|
+
rb_enc_associate(rb_str, conn_enc);
|
357
431
|
if (default_internal_enc) {
|
358
|
-
|
432
|
+
rb_str = rb_str_export_to_enc(rb_str, default_internal_enc);
|
359
433
|
}
|
360
434
|
#endif
|
361
|
-
|
435
|
+
xfree(newStr);
|
436
|
+
return rb_str;
|
362
437
|
}
|
363
438
|
}
|
364
439
|
|
365
440
|
static VALUE rb_mysql_client_info(VALUE self) {
|
366
|
-
VALUE version
|
441
|
+
VALUE version, client_info;
|
442
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
443
|
+
rb_encoding *default_internal_enc;
|
444
|
+
rb_encoding *conn_enc;
|
445
|
+
#endif
|
367
446
|
GET_CLIENT(self);
|
447
|
+
version = rb_hash_new();
|
368
448
|
|
369
449
|
#ifdef HAVE_RUBY_ENCODING_H
|
370
|
-
|
371
|
-
|
450
|
+
default_internal_enc = rb_default_internal_encoding();
|
451
|
+
conn_enc = rb_to_encoding(wrapper->encoding);
|
372
452
|
#endif
|
373
453
|
|
374
454
|
rb_hash_aset(version, sym_id, LONG2NUM(mysql_get_client_version()));
|
@@ -385,12 +465,16 @@ static VALUE rb_mysql_client_info(VALUE self) {
|
|
385
465
|
|
386
466
|
static VALUE rb_mysql_client_server_info(VALUE self) {
|
387
467
|
VALUE version, server_info;
|
468
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
469
|
+
rb_encoding *default_internal_enc;
|
470
|
+
rb_encoding *conn_enc;
|
471
|
+
#endif
|
388
472
|
GET_CLIENT(self);
|
389
473
|
|
390
474
|
REQUIRE_OPEN_DB(wrapper);
|
391
475
|
#ifdef HAVE_RUBY_ENCODING_H
|
392
|
-
|
393
|
-
|
476
|
+
default_internal_enc = rb_default_internal_encoding();
|
477
|
+
conn_enc = rb_to_encoding(wrapper->encoding);
|
394
478
|
#endif
|
395
479
|
|
396
480
|
version = rb_hash_new();
|
@@ -419,17 +503,46 @@ static VALUE rb_mysql_client_last_id(VALUE self) {
|
|
419
503
|
}
|
420
504
|
|
421
505
|
static VALUE rb_mysql_client_affected_rows(VALUE self) {
|
422
|
-
GET_CLIENT(self);
|
423
506
|
my_ulonglong retVal;
|
507
|
+
GET_CLIENT(self);
|
424
508
|
|
425
509
|
REQUIRE_OPEN_DB(wrapper);
|
426
510
|
retVal = mysql_affected_rows(wrapper->client);
|
427
511
|
if (retVal == (my_ulonglong)-1) {
|
428
|
-
rb_raise_mysql2_error(wrapper
|
512
|
+
rb_raise_mysql2_error(wrapper);
|
429
513
|
}
|
430
514
|
return ULL2NUM(retVal);
|
431
515
|
}
|
432
516
|
|
517
|
+
static VALUE rb_mysql_client_thread_id(VALUE self) {
|
518
|
+
unsigned long retVal;
|
519
|
+
GET_CLIENT(self);
|
520
|
+
|
521
|
+
REQUIRE_OPEN_DB(wrapper);
|
522
|
+
retVal = mysql_thread_id(wrapper->client);
|
523
|
+
return ULL2NUM(retVal);
|
524
|
+
}
|
525
|
+
|
526
|
+
static VALUE nogvl_ping(void *ptr)
|
527
|
+
{
|
528
|
+
MYSQL *client = ptr;
|
529
|
+
|
530
|
+
return mysql_ping(client) == 0 ? Qtrue : Qfalse;
|
531
|
+
}
|
532
|
+
|
533
|
+
static VALUE rb_mysql_client_ping(VALUE self) {
|
534
|
+
GET_CLIENT(self);
|
535
|
+
|
536
|
+
return rb_thread_blocking_region(nogvl_ping, wrapper->client, RUBY_UBF_IO, 0);
|
537
|
+
}
|
538
|
+
|
539
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
540
|
+
static VALUE rb_mysql_client_encoding(VALUE self) {
|
541
|
+
GET_CLIENT(self);
|
542
|
+
return wrapper->encoding;
|
543
|
+
}
|
544
|
+
#endif
|
545
|
+
|
433
546
|
static VALUE set_reconnect(VALUE self, VALUE value) {
|
434
547
|
my_bool reconnect;
|
435
548
|
GET_CLIENT(self);
|
@@ -465,13 +578,16 @@ static VALUE set_connect_timeout(VALUE self, VALUE value) {
|
|
465
578
|
|
466
579
|
static VALUE set_charset_name(VALUE self, VALUE value) {
|
467
580
|
char * charset_name;
|
581
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
582
|
+
VALUE new_encoding;
|
583
|
+
#endif
|
468
584
|
GET_CLIENT(self);
|
469
585
|
|
470
586
|
#ifdef HAVE_RUBY_ENCODING_H
|
471
|
-
VALUE new_encoding;
|
472
587
|
new_encoding = rb_funcall(cMysql2Client, intern_encoding_from_charset, 1, value);
|
473
588
|
if (new_encoding == Qnil) {
|
474
|
-
|
589
|
+
VALUE inspect = rb_inspect(value);
|
590
|
+
rb_raise(cMysql2Error, "Unsupported charset: '%s'", RSTRING_PTR(inspect));
|
475
591
|
} else {
|
476
592
|
if (wrapper->encoding == Qnil) {
|
477
593
|
wrapper->encoding = new_encoding;
|
@@ -509,7 +625,7 @@ static VALUE init_connection(VALUE self) {
|
|
509
625
|
|
510
626
|
if (rb_thread_blocking_region(nogvl_init, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
|
511
627
|
/* TODO: warning - not enough memory? */
|
512
|
-
return rb_raise_mysql2_error(wrapper
|
628
|
+
return rb_raise_mysql2_error(wrapper);
|
513
629
|
}
|
514
630
|
|
515
631
|
wrapper->closed = 0;
|
@@ -517,6 +633,23 @@ static VALUE init_connection(VALUE self) {
|
|
517
633
|
}
|
518
634
|
|
519
635
|
void init_mysql2_client() {
|
636
|
+
// verify the libmysql we're about to use was the version we were built against
|
637
|
+
// https://github.com/luislavena/mysql-gem/commit/a600a9c459597da0712f70f43736e24b484f8a99
|
638
|
+
int i;
|
639
|
+
int dots = 0;
|
640
|
+
const char *lib = mysql_get_client_info();
|
641
|
+
for (i = 0; lib[i] != 0 && MYSQL_SERVER_VERSION[i] != 0; i++) {
|
642
|
+
if (lib[i] == '.') {
|
643
|
+
dots++;
|
644
|
+
// we only compare MAJOR and MINOR
|
645
|
+
if (dots == 2) break;
|
646
|
+
}
|
647
|
+
if (lib[i] != MYSQL_SERVER_VERSION[i]) {
|
648
|
+
rb_raise(rb_eRuntimeError, "Incorrect MySQL client library version! This gem was compiled for %s but the client library is %s.", MYSQL_SERVER_VERSION, lib);
|
649
|
+
return;
|
650
|
+
}
|
651
|
+
}
|
652
|
+
|
520
653
|
cMysql2Client = rb_define_class_under(mMysql2, "Client", rb_cObject);
|
521
654
|
|
522
655
|
rb_define_alloc_func(cMysql2Client, allocate);
|
@@ -530,6 +663,11 @@ void init_mysql2_client() {
|
|
530
663
|
rb_define_method(cMysql2Client, "async_result", rb_mysql_client_async_result, 0);
|
531
664
|
rb_define_method(cMysql2Client, "last_id", rb_mysql_client_last_id, 0);
|
532
665
|
rb_define_method(cMysql2Client, "affected_rows", rb_mysql_client_affected_rows, 0);
|
666
|
+
rb_define_method(cMysql2Client, "thread_id", rb_mysql_client_thread_id, 0);
|
667
|
+
rb_define_method(cMysql2Client, "ping", rb_mysql_client_ping, 0);
|
668
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
669
|
+
rb_define_method(cMysql2Client, "encoding", rb_mysql_client_encoding, 0);
|
670
|
+
#endif
|
533
671
|
|
534
672
|
rb_define_private_method(cMysql2Client, "reconnect=", set_reconnect, 1);
|
535
673
|
rb_define_private_method(cMysql2Client, "connect_timeout=", set_connect_timeout, 1);
|