mysql2 0.2.8 → 0.2.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.
@@ -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, read_timeout;
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
- // the below code is largely from do_mysql
375
- // http://github.com/datamapper/do
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
- result = rb_mysql_client_async_result(self);
438
+ rb_rescue2(do_query, (VALUE)&async_args, disconnect_and_raise, self, rb_eException, (VALUE)0);
399
439
 
400
- return result;
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 */
@@ -33,8 +33,9 @@ void init_mysql2_client();
33
33
 
34
34
  typedef struct {
35
35
  VALUE encoding;
36
- char active;
37
- char closed;
36
+ int active;
37
+ int reconnect_enabled;
38
+ int closed;
38
39
  MYSQL *client;
39
40
  } mysql_client_wrapper;
40
41
 
@@ -562,7 +562,7 @@ module ActiveRecord
562
562
  end
563
563
 
564
564
  def configure_connection
565
- @connection.query_options.merge!(:as => :array, :cast => false)
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
@@ -1,3 +1,3 @@
1
1
  module Mysql2
2
- VERSION = "0.2.8"
2
+ VERSION = "0.2.9"
3
3
  end
@@ -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"
@@ -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: 7
4
+ hash: 5
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 2
9
- - 8
10
- version: 0.2.8
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-14 00:00:00 -07:00
18
+ date: 2011-06-16 00:00:00 -07:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency