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.
@@ -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