mysql2 0.2.8 → 0.2.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 +4 -0
- data/ext/mysql2/client.c +92 -51
- data/ext/mysql2/client.h +3 -2
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -1
- data/lib/mysql2/version.rb +1 -1
- data/spec/mysql2/client_spec.rb +102 -67
- data/spec/mysql2/result_spec.rb +6 -0
- metadata +4 -4
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 0.2.9 (June 15th, 2011)
|
4
|
+
* fix a long standing bug where a signal would interrupt rb_thread_select and put the connection in a permanently broken state
|
5
|
+
* turn on casting in the ActiveRecord again, users can disable it if they need to for performance reasons
|
6
|
+
|
3
7
|
## 0.2.8 (June 14th, 2011)
|
4
8
|
* disable async support, and access to the underlying file descriptor under Windows. It's never worked reliably and ruby-core has a lot of work to do in order to make it possible.
|
5
9
|
* added support for turning eager-casting off. This is especially useful in ORMs that will lazily cast values upon access.
|
data/ext/mysql2/client.c
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
#include <mysql2_ext.h>
|
2
2
|
#include <client.h>
|
3
3
|
#include <errno.h>
|
4
|
+
#ifndef _WIN32
|
5
|
+
#include <sys/socket.h>
|
6
|
+
#endif
|
4
7
|
|
5
8
|
VALUE cMysql2Client;
|
6
9
|
extern VALUE mMysql2, cMysql2Error;
|
@@ -9,7 +12,7 @@ static VALUE sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as, sym_arr
|
|
9
12
|
static ID intern_merge, intern_error_number_eql, intern_sql_state_eql;
|
10
13
|
|
11
14
|
#define REQUIRE_OPEN_DB(wrapper) \
|
12
|
-
if(wrapper->closed) { \
|
15
|
+
if(!wrapper->reconnect_enabled && wrapper->closed) { \
|
13
16
|
rb_raise(cMysql2Error, "closed MySQL connection"); \
|
14
17
|
}
|
15
18
|
|
@@ -125,7 +128,7 @@ static VALUE nogvl_close(void *ptr) {
|
|
125
128
|
wrapper = ptr;
|
126
129
|
if (!wrapper->closed) {
|
127
130
|
wrapper->closed = 1;
|
128
|
-
|
131
|
+
wrapper->active = 0;
|
129
132
|
/*
|
130
133
|
* we'll send a QUIT message to the server, but that message is more of a
|
131
134
|
* formality than a hard requirement since the socket is getting shutdown
|
@@ -162,6 +165,7 @@ static VALUE allocate(VALUE klass) {
|
|
162
165
|
obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper);
|
163
166
|
wrapper->encoding = Qnil;
|
164
167
|
wrapper->active = 0;
|
168
|
+
wrapper->reconnect_enabled = 0;
|
165
169
|
wrapper->closed = 1;
|
166
170
|
wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL));
|
167
171
|
return obj;
|
@@ -271,6 +275,10 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
|
|
271
275
|
#endif
|
272
276
|
GET_CLIENT(self);
|
273
277
|
|
278
|
+
// if we're not waiting on a result, do nothing
|
279
|
+
if (!wrapper->active)
|
280
|
+
return Qnil;
|
281
|
+
|
274
282
|
REQUIRE_OPEN_DB(wrapper);
|
275
283
|
if (rb_thread_blocking_region(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
|
276
284
|
// an error occurred, mark this connection inactive
|
@@ -301,19 +309,89 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
|
|
301
309
|
return resultObj;
|
302
310
|
}
|
303
311
|
|
312
|
+
#ifndef _WIN32
|
313
|
+
struct async_query_args {
|
314
|
+
int fd;
|
315
|
+
VALUE self;
|
316
|
+
};
|
317
|
+
|
318
|
+
static VALUE disconnect_and_raise(VALUE self, VALUE error) {
|
319
|
+
GET_CLIENT(self);
|
320
|
+
|
321
|
+
wrapper->closed = 1;
|
322
|
+
wrapper->active = 0;
|
323
|
+
|
324
|
+
// manually close the socket for read/write
|
325
|
+
// this feels dirty, but is there another way?
|
326
|
+
shutdown(wrapper->client->net.fd, 2);
|
327
|
+
|
328
|
+
rb_exc_raise(error);
|
329
|
+
|
330
|
+
return Qnil;
|
331
|
+
}
|
332
|
+
#endif
|
333
|
+
|
334
|
+
static VALUE do_query(void *args) {
|
335
|
+
struct async_query_args *async_args;
|
336
|
+
struct timeval tv;
|
337
|
+
struct timeval* tvp;
|
338
|
+
long int sec;
|
339
|
+
fd_set fdset;
|
340
|
+
int retval;
|
341
|
+
int fd_set_fd;
|
342
|
+
VALUE read_timeout;
|
343
|
+
|
344
|
+
async_args = (struct async_query_args *)args;
|
345
|
+
read_timeout = rb_iv_get(async_args->self, "@read_timeout");
|
346
|
+
|
347
|
+
tvp = NULL;
|
348
|
+
if (!NIL_P(read_timeout)) {
|
349
|
+
Check_Type(read_timeout, T_FIXNUM);
|
350
|
+
tvp = &tv;
|
351
|
+
sec = FIX2INT(read_timeout);
|
352
|
+
// TODO: support partial seconds?
|
353
|
+
// also, this check is here for sanity, we also check up in Ruby
|
354
|
+
if (sec >= 0) {
|
355
|
+
tvp->tv_sec = sec;
|
356
|
+
} else {
|
357
|
+
rb_raise(cMysql2Error, "read_timeout must be a positive integer, you passed %ld", sec);
|
358
|
+
}
|
359
|
+
tvp->tv_usec = 0;
|
360
|
+
}
|
361
|
+
|
362
|
+
fd_set_fd = async_args->fd;
|
363
|
+
for(;;) {
|
364
|
+
// the below code is largely from do_mysql
|
365
|
+
// http://github.com/datamapper/do
|
366
|
+
FD_ZERO(&fdset);
|
367
|
+
FD_SET(fd_set_fd, &fdset);
|
368
|
+
|
369
|
+
retval = rb_thread_select(fd_set_fd + 1, &fdset, NULL, NULL, tvp);
|
370
|
+
|
371
|
+
if (retval == 0) {
|
372
|
+
rb_raise(cMysql2Error, "Timeout waiting for a response from the last query. (waited %d seconds)", FIX2INT(read_timeout));
|
373
|
+
}
|
374
|
+
|
375
|
+
if (retval < 0) {
|
376
|
+
rb_sys_fail(0);
|
377
|
+
}
|
378
|
+
|
379
|
+
if (retval > 0) {
|
380
|
+
break;
|
381
|
+
}
|
382
|
+
}
|
383
|
+
|
384
|
+
return Qnil;
|
385
|
+
}
|
386
|
+
|
304
387
|
static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
|
388
|
+
struct async_query_args async_args;
|
305
389
|
struct nogvl_send_query_args args;
|
306
|
-
fd_set fdset;
|
307
|
-
int fd, retval;
|
308
390
|
int async = 0;
|
309
|
-
VALUE opts, defaults
|
391
|
+
VALUE opts, defaults;
|
310
392
|
#ifdef HAVE_RUBY_ENCODING_H
|
311
393
|
rb_encoding *conn_enc;
|
312
394
|
#endif
|
313
|
-
struct timeval tv;
|
314
|
-
struct timeval* tvp;
|
315
|
-
long int sec;
|
316
|
-
VALUE result;
|
317
395
|
GET_CLIENT(self);
|
318
396
|
|
319
397
|
REQUIRE_OPEN_DB(wrapper);
|
@@ -353,51 +431,13 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
|
|
353
431
|
}
|
354
432
|
|
355
433
|
#ifndef _WIN32
|
356
|
-
read_timeout = rb_iv_get(self, "@read_timeout");
|
357
|
-
|
358
|
-
tvp = NULL;
|
359
|
-
if (!NIL_P(read_timeout)) {
|
360
|
-
Check_Type(read_timeout, T_FIXNUM);
|
361
|
-
tvp = &tv;
|
362
|
-
sec = FIX2INT(read_timeout);
|
363
|
-
// TODO: support partial seconds?
|
364
|
-
// also, this check is here for sanity, we also check up in Ruby
|
365
|
-
if (sec >= 0) {
|
366
|
-
tvp->tv_sec = sec;
|
367
|
-
} else {
|
368
|
-
rb_raise(cMysql2Error, "read_timeout must be a positive integer, you passed %ld", sec);
|
369
|
-
}
|
370
|
-
tvp->tv_usec = 0;
|
371
|
-
}
|
372
|
-
|
373
434
|
if (!async) {
|
374
|
-
|
375
|
-
|
376
|
-
fd = wrapper->client->net.fd;
|
377
|
-
for(;;) {
|
378
|
-
int fd_set_fd = fd;
|
379
|
-
|
380
|
-
FD_ZERO(&fdset);
|
381
|
-
FD_SET(fd_set_fd, &fdset);
|
382
|
-
|
383
|
-
retval = rb_thread_select(fd_set_fd + 1, &fdset, NULL, NULL, tvp);
|
384
|
-
|
385
|
-
if (retval == 0) {
|
386
|
-
rb_raise(cMysql2Error, "Timeout waiting for a response from the last query. (waited %d seconds)", FIX2INT(read_timeout));
|
387
|
-
}
|
388
|
-
|
389
|
-
if (retval < 0) {
|
390
|
-
rb_sys_fail(0);
|
391
|
-
}
|
392
|
-
|
393
|
-
if (retval > 0) {
|
394
|
-
break;
|
395
|
-
}
|
396
|
-
}
|
435
|
+
async_args.fd = wrapper->client->net.fd;
|
436
|
+
async_args.self = self;
|
397
437
|
|
398
|
-
|
438
|
+
rb_rescue2(do_query, (VALUE)&async_args, disconnect_and_raise, self, rb_eException, (VALUE)0);
|
399
439
|
|
400
|
-
return
|
440
|
+
return rb_mysql_client_async_result(self);
|
401
441
|
} else {
|
402
442
|
return Qnil;
|
403
443
|
}
|
@@ -568,6 +608,7 @@ static VALUE set_reconnect(VALUE self, VALUE value) {
|
|
568
608
|
if(!NIL_P(value)) {
|
569
609
|
reconnect = value == Qfalse ? 0 : 1;
|
570
610
|
|
611
|
+
wrapper->reconnect_enabled = reconnect;
|
571
612
|
/* set default reconnect behavior */
|
572
613
|
if (mysql_options(wrapper->client, MYSQL_OPT_RECONNECT, &reconnect)) {
|
573
614
|
/* TODO: warning - unable to set reconnect behavior */
|
data/ext/mysql2/client.h
CHANGED
@@ -562,7 +562,7 @@ module ActiveRecord
|
|
562
562
|
end
|
563
563
|
|
564
564
|
def configure_connection
|
565
|
-
@connection.query_options.merge!(:as => :array
|
565
|
+
@connection.query_options.merge!(:as => :array)
|
566
566
|
|
567
567
|
# By default, MySQL 'where id is null' selects the last inserted id.
|
568
568
|
# Turn this off. http://dev.rubyonrails.org/ticket/6778
|
data/lib/mysql2/version.rb
CHANGED
data/spec/mysql2/client_spec.rb
CHANGED
@@ -116,13 +116,6 @@ describe Mysql2::Client do
|
|
116
116
|
@client.query("SELECT 1", :symbolize_keys => true).first.keys[0].class.should eql(Symbol)
|
117
117
|
end
|
118
118
|
|
119
|
-
it "should not allow another query to be sent without fetching a result first" do
|
120
|
-
@client.query("SELECT 1", :async => true)
|
121
|
-
lambda {
|
122
|
-
@client.query("SELECT 1")
|
123
|
-
}.should raise_error(Mysql2::Error)
|
124
|
-
end
|
125
|
-
|
126
119
|
it "should require an open connection" do
|
127
120
|
@client.close
|
128
121
|
lambda {
|
@@ -130,16 +123,23 @@ describe Mysql2::Client do
|
|
130
123
|
}.should raise_error(Mysql2::Error)
|
131
124
|
end
|
132
125
|
|
133
|
-
it "should timeout if we wait longer than :read_timeout" do
|
134
|
-
client = Mysql2::Client.new(:read_timeout => 1)
|
135
|
-
lambda {
|
136
|
-
client.query("SELECT sleep(2)")
|
137
|
-
}.should raise_error(Mysql2::Error)
|
138
|
-
end
|
139
|
-
|
140
|
-
# XXX this test is not deterministic (because Unix signal handling is not)
|
141
|
-
# and may fail on a loaded system
|
142
126
|
if RUBY_PLATFORM !~ /mingw|mswin/
|
127
|
+
it "should not allow another query to be sent without fetching a result first" do
|
128
|
+
@client.query("SELECT 1", :async => true)
|
129
|
+
lambda {
|
130
|
+
@client.query("SELECT 1")
|
131
|
+
}.should raise_error(Mysql2::Error)
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should timeout if we wait longer than :read_timeout" do
|
135
|
+
client = Mysql2::Client.new(:read_timeout => 1)
|
136
|
+
lambda {
|
137
|
+
client.query("SELECT sleep(2)")
|
138
|
+
}.should raise_error(Mysql2::Error)
|
139
|
+
end
|
140
|
+
|
141
|
+
# XXX this test is not deterministic (because Unix signal handling is not)
|
142
|
+
# and may fail on a loaded system
|
143
143
|
it "should run signal handlers while waiting for a response" do
|
144
144
|
mark = {}
|
145
145
|
trap(:USR1) { mark[:USR1] = Time.now }
|
@@ -164,6 +164,92 @@ describe Mysql2::Client do
|
|
164
164
|
trap(:USR1, 'DEFAULT')
|
165
165
|
end
|
166
166
|
end
|
167
|
+
|
168
|
+
it "#socket should return a Fixnum (file descriptor from C)" do
|
169
|
+
@client.socket.class.should eql(Fixnum)
|
170
|
+
@client.socket.should_not eql(0)
|
171
|
+
end
|
172
|
+
|
173
|
+
it "#socket should require an open connection" do
|
174
|
+
@client.close
|
175
|
+
lambda {
|
176
|
+
@client.socket
|
177
|
+
}.should raise_error(Mysql2::Error)
|
178
|
+
end
|
179
|
+
|
180
|
+
it "should close the connection when an exception is raised" do
|
181
|
+
begin
|
182
|
+
Timeout.timeout(1) do
|
183
|
+
@client.query("SELECT sleep(2)")
|
184
|
+
end
|
185
|
+
rescue Timeout::Error
|
186
|
+
end
|
187
|
+
|
188
|
+
lambda {
|
189
|
+
@client.query("SELECT 1")
|
190
|
+
}.should raise_error(Mysql2::Error, 'closed MySQL connection')
|
191
|
+
end
|
192
|
+
|
193
|
+
it "should handle Timeouts without leaving the connection hanging if reconnect is true" do
|
194
|
+
client = Mysql2::Client.new(:reconnect => true)
|
195
|
+
begin
|
196
|
+
Timeout.timeout(1) do
|
197
|
+
client.query("SELECT sleep(2)")
|
198
|
+
end
|
199
|
+
rescue Timeout::Error
|
200
|
+
end
|
201
|
+
|
202
|
+
lambda {
|
203
|
+
client.query("SELECT 1")
|
204
|
+
}.should_not raise_error(Mysql2::Error)
|
205
|
+
end
|
206
|
+
|
207
|
+
it "threaded queries should be supported" do
|
208
|
+
threads, results = [], {}
|
209
|
+
connect = lambda{ Mysql2::Client.new(:host => "localhost", :username => "root") }
|
210
|
+
Timeout.timeout(0.7) do
|
211
|
+
5.times {
|
212
|
+
threads << Thread.new do
|
213
|
+
results[Thread.current.object_id] = connect.call.query("SELECT sleep(0.5) as result")
|
214
|
+
end
|
215
|
+
}
|
216
|
+
end
|
217
|
+
threads.each{|t| t.join }
|
218
|
+
results.keys.sort.should eql(threads.map{|t| t.object_id }.sort)
|
219
|
+
end
|
220
|
+
|
221
|
+
it "evented async queries should be supported" do
|
222
|
+
# should immediately return nil
|
223
|
+
@client.query("SELECT sleep(0.1)", :async => true).should eql(nil)
|
224
|
+
|
225
|
+
io_wrapper = IO.for_fd(@client.socket)
|
226
|
+
loops = 0
|
227
|
+
loop do
|
228
|
+
if IO.select([io_wrapper], nil, nil, 0.05)
|
229
|
+
break
|
230
|
+
else
|
231
|
+
loops += 1
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
# make sure we waited some period of time
|
236
|
+
(loops >= 1).should be_true
|
237
|
+
|
238
|
+
result = @client.async_result
|
239
|
+
result.class.should eql(Mysql2::Result)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
it "should respond to #socket" do
|
245
|
+
@client.should respond_to(:socket)
|
246
|
+
end
|
247
|
+
|
248
|
+
if RUBY_PLATFORM =~ /mingw|mswin/
|
249
|
+
it "#socket should raise as it's not supported" do
|
250
|
+
lambda {
|
251
|
+
@client.socket
|
252
|
+
}.should raise_error(Mysql2::Error)
|
167
253
|
end
|
168
254
|
end
|
169
255
|
|
@@ -311,22 +397,6 @@ describe Mysql2::Client do
|
|
311
397
|
end
|
312
398
|
end
|
313
399
|
|
314
|
-
it "should respond to #socket" do
|
315
|
-
@client.should respond_to(:socket)
|
316
|
-
end
|
317
|
-
|
318
|
-
it "#socket should return a Fixnum (file descriptor from C)" do
|
319
|
-
@client.socket.class.should eql(Fixnum)
|
320
|
-
@client.socket.should_not eql(0)
|
321
|
-
end
|
322
|
-
|
323
|
-
it "#socket should require an open connection" do
|
324
|
-
@client.close
|
325
|
-
lambda {
|
326
|
-
@client.socket
|
327
|
-
}.should raise_error(Mysql2::Error)
|
328
|
-
end
|
329
|
-
|
330
400
|
it "should raise a Mysql2::Error exception upon connection failure" do
|
331
401
|
lambda {
|
332
402
|
bad_client = Mysql2::Client.new :host => "localhost", :username => 'asdfasdf8d2h', :password => 'asdfasdfw42'
|
@@ -337,41 +407,6 @@ describe Mysql2::Client do
|
|
337
407
|
}.should_not raise_error(Mysql2::Error)
|
338
408
|
end
|
339
409
|
|
340
|
-
it "threaded queries should be supported" do
|
341
|
-
threads, results = [], {}
|
342
|
-
connect = lambda{ Mysql2::Client.new(:host => "localhost", :username => "root") }
|
343
|
-
Timeout.timeout(0.7) do
|
344
|
-
5.times {
|
345
|
-
threads << Thread.new do
|
346
|
-
results[Thread.current.object_id] = connect.call.query("SELECT sleep(0.5) as result")
|
347
|
-
end
|
348
|
-
}
|
349
|
-
end
|
350
|
-
threads.each{|t| t.join }
|
351
|
-
results.keys.sort.should eql(threads.map{|t| t.object_id }.sort)
|
352
|
-
end
|
353
|
-
|
354
|
-
it "evented async queries should be supported" do
|
355
|
-
# should immediately return nil
|
356
|
-
@client.query("SELECT sleep(0.1)", :async => true).should eql(nil)
|
357
|
-
|
358
|
-
io_wrapper = IO.for_fd(@client.socket)
|
359
|
-
loops = 0
|
360
|
-
loop do
|
361
|
-
if IO.select([io_wrapper], nil, nil, 0.05)
|
362
|
-
break
|
363
|
-
else
|
364
|
-
loops += 1
|
365
|
-
end
|
366
|
-
end
|
367
|
-
|
368
|
-
# make sure we waited some period of time
|
369
|
-
(loops >= 1).should be_true
|
370
|
-
|
371
|
-
result = @client.async_result
|
372
|
-
result.class.should eql(Mysql2::Result)
|
373
|
-
end
|
374
|
-
|
375
410
|
context 'write operations api' do
|
376
411
|
before(:each) do
|
377
412
|
@client.query "USE test"
|
data/spec/mysql2/result_spec.rb
CHANGED
@@ -28,6 +28,12 @@ describe Mysql2::Result do
|
|
28
28
|
}.should_not raise_error(Mysql2::Error)
|
29
29
|
end
|
30
30
|
|
31
|
+
context "metadata queries" do
|
32
|
+
it "should show tables" do
|
33
|
+
@result = @client.query "SHOW TABLES"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
31
37
|
context "#each" do
|
32
38
|
it "should yield rows as hash's" do
|
33
39
|
@result.each do |row|
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mysql2
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 5
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 2
|
9
|
-
-
|
10
|
-
version: 0.2.
|
9
|
+
- 9
|
10
|
+
version: 0.2.9
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Brian Lopez
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-06-
|
18
|
+
date: 2011-06-16 00:00:00 -07:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|