mysql2 0.2.6-x86-mswin32-60 → 0.2.15-x86-mswin32-60

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,10 +14,18 @@ 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 (cast: true)" do
19
18
  number_of.times do
20
- mysql2_result = mysql2.query sql, :symbolize_keys => true
19
+ mysql2_result = mysql2.query sql, :symbolize_keys => true, :cast => true
20
+ mysql2_result.each do |res|
21
+ # puts res.inspect
22
+ end
23
+ end
24
+ end
25
+
26
+ x.report "Mysql2 (cast: false)" do
27
+ number_of.times do
28
+ mysql2_result = mysql2.query sql, :symbolize_keys => true, :cast => false
21
29
  mysql2_result.each do |res|
22
30
  # puts res.inspect
23
31
  end
@@ -26,8 +34,7 @@ Benchmark.bmbm do |x|
26
34
 
27
35
  mysql = Mysql.new("localhost", "root")
28
36
  mysql.query "USE #{database}"
29
- x.report do
30
- puts "Mysql"
37
+ x.report "Mysql" do
31
38
  number_of.times do
32
39
  mysql_result = mysql.query sql
33
40
  mysql_result.each_hash do |res|
@@ -38,8 +45,7 @@ Benchmark.bmbm do |x|
38
45
 
39
46
  do_mysql = DataObjects::Connection.new("mysql://localhost/#{database}")
40
47
  command = DataObjects::Mysql::Command.new do_mysql, sql
41
- x.report do
42
- puts "do_mysql"
48
+ x.report "do_mysql" do
43
49
  number_of.times do
44
50
  do_result = command.execute_reader
45
51
  do_result.each do |res|
@@ -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
@@ -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 => Faker::Lorem.words(rand(5)),
97
- :varchar_test => Faker::Lorem.words(rand(5)),
98
- :binary_test => Faker::Lorem.words(rand(5)),
99
- :varbinary_test => Faker::Lorem.words(rand(5)),
100
- :tiny_blob_test => Faker::Lorem.words(rand(5)),
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 => Faker::Lorem.paragraphs(rand(25)),
103
- :text_test => Faker::Lorem.paragraphs(rand(25)),
104
- :medium_blob_test => Faker::Lorem.paragraphs(rand(25)),
105
- :medium_text_test => Faker::Lorem.paragraphs(rand(25)),
106
- :long_blob_test => Faker::Lorem.paragraphs(rand(25)),
107
- :long_text_test => Faker::Lorem.paragraphs(rand(25)),
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
- $stdout.putc '.'
112
- $stdout.flush
113
+ if n % 100 == 0
114
+ $stdout.putc '.'
115
+ $stdout.flush
116
+ end
113
117
  end
114
118
  puts
115
119
  puts "Done"
@@ -0,0 +1,44 @@
1
+ # encoding: UTF-8
2
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
3
+
4
+ require 'rubygems'
5
+ require 'benchmark'
6
+ require 'active_record'
7
+
8
+ mysql2_opts = {
9
+ :adapter => 'mysql2',
10
+ :database => 'test',
11
+ :pool => 25
12
+ }
13
+ ActiveRecord::Base.establish_connection(mysql2_opts)
14
+ x = Benchmark.realtime do
15
+ threads = []
16
+ 25.times do
17
+ threads << Thread.new { ActiveRecord::Base.connection.execute("select sleep(1)") }
18
+ end
19
+ threads.each {|t| t.join }
20
+ end
21
+ puts x
22
+
23
+ mysql2_opts = {
24
+ :adapter => 'mysql',
25
+ :database => 'test',
26
+ :pool => 25
27
+ }
28
+ ActiveRecord::Base.establish_connection(mysql2_opts)
29
+ x = Benchmark.realtime do
30
+ threads = []
31
+ 25.times do
32
+ threads << Thread.new { ActiveRecord::Base.connection.execute("select sleep(1)") }
33
+ end
34
+ threads.each {|t| t.join }
35
+ end
36
+ puts x
37
+
38
+ # these results are similar on 1.8.7, 1.9.2 and rbx-head
39
+ #
40
+ # $ bundle exec ruby benchmarks/threaded.rb
41
+ # 1.0774750709533691
42
+ #
43
+ # and using the mysql gem
44
+ # 25.099437952041626
@@ -1,17 +1,20 @@
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
7
+ #include "wait_for_single_fd.h"
4
8
 
5
9
  VALUE cMysql2Client;
6
10
  extern VALUE mMysql2, cMysql2Error;
7
11
  static VALUE intern_encoding_from_charset;
8
- static ID sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as, sym_array;
12
+ static VALUE sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as, sym_array;
9
13
  static ID intern_merge, intern_error_number_eql, intern_sql_state_eql;
10
14
 
11
15
  #define REQUIRE_OPEN_DB(wrapper) \
12
- if(wrapper->closed) { \
16
+ if(!wrapper->reconnect_enabled && wrapper->closed) { \
13
17
  rb_raise(cMysql2Error, "closed MySQL connection"); \
14
- return Qnil; \
15
18
  }
16
19
 
17
20
  #define MARK_CONN_INACTIVE(conn) \
@@ -43,6 +46,7 @@ struct nogvl_connect_args {
43
46
  struct nogvl_send_query_args {
44
47
  MYSQL *mysql;
45
48
  VALUE sql;
49
+ mysql_client_wrapper *wrapper;
46
50
  };
47
51
 
48
52
  /*
@@ -74,10 +78,24 @@ static void rb_mysql_client_mark(void * wrapper) {
74
78
  }
75
79
  }
76
80
 
77
- static VALUE rb_raise_mysql2_error(MYSQL *client) {
78
- VALUE e = rb_exc_new2(cMysql2Error, mysql_error(client));
79
- rb_funcall(e, intern_error_number_eql, 1, INT2NUM(mysql_errno(client)));
80
- rb_funcall(e, intern_sql_state_eql, 1, rb_tainted_str_new2(mysql_sqlstate(client)));
81
+ static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) {
82
+ VALUE rb_error_msg = rb_str_new2(mysql_error(wrapper->client));
83
+ VALUE rb_sql_state = rb_tainted_str_new2(mysql_sqlstate(wrapper->client));
84
+ #ifdef HAVE_RUBY_ENCODING_H
85
+ rb_encoding *default_internal_enc = rb_default_internal_encoding();
86
+ rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
87
+
88
+ rb_enc_associate(rb_error_msg, conn_enc);
89
+ rb_enc_associate(rb_sql_state, conn_enc);
90
+ if (default_internal_enc) {
91
+ rb_error_msg = rb_str_export_to_enc(rb_error_msg, default_internal_enc);
92
+ rb_sql_state = rb_str_export_to_enc(rb_sql_state, default_internal_enc);
93
+ }
94
+ #endif
95
+
96
+ VALUE e = rb_exc_new3(cMysql2Error, rb_error_msg);
97
+ rb_funcall(e, intern_error_number_eql, 1, UINT2NUM(mysql_errno(wrapper->client)));
98
+ rb_funcall(e, intern_sql_state_eql, 1, rb_sql_state);
81
99
  rb_exc_raise(e);
82
100
  return Qnil;
83
101
  }
@@ -105,10 +123,14 @@ static VALUE nogvl_connect(void *ptr) {
105
123
  }
106
124
 
107
125
  static VALUE nogvl_close(void *ptr) {
108
- mysql_client_wrapper *wrapper = ptr;
126
+ mysql_client_wrapper *wrapper;
127
+ #ifndef _WIN32
128
+ int flags;
129
+ #endif
130
+ wrapper = ptr;
109
131
  if (!wrapper->closed) {
110
132
  wrapper->closed = 1;
111
-
133
+ wrapper->active = 0;
112
134
  /*
113
135
  * we'll send a QUIT message to the server, but that message is more of a
114
136
  * formality than a hard requirement since the socket is getting shutdown
@@ -119,12 +141,9 @@ static VALUE nogvl_close(void *ptr) {
119
141
  * so ignore any potential fcntl errors since they don't matter
120
142
  */
121
143
  #ifndef _WIN32
122
- int flags = fcntl(wrapper->client->net.fd, F_GETFL);
144
+ flags = fcntl(wrapper->client->net.fd, F_GETFL);
123
145
  if (flags > 0 && !(flags & O_NONBLOCK))
124
146
  fcntl(wrapper->client->net.fd, F_SETFL, flags | O_NONBLOCK);
125
- #else
126
- u_long iMode = 1;
127
- ioctlsocket(wrapper->client->net.fd, FIONBIO, &iMode);
128
147
  #endif
129
148
 
130
149
  mysql_close(wrapper->client);
@@ -139,7 +158,7 @@ static void rb_mysql_client_free(void * ptr) {
139
158
 
140
159
  nogvl_close(wrapper);
141
160
 
142
- xfree(ptr);
161
+ free(ptr);
143
162
  }
144
163
 
145
164
  static VALUE allocate(VALUE klass) {
@@ -148,11 +167,37 @@ static VALUE allocate(VALUE klass) {
148
167
  obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper);
149
168
  wrapper->encoding = Qnil;
150
169
  wrapper->active = 0;
170
+ wrapper->reconnect_enabled = 0;
151
171
  wrapper->closed = 1;
152
172
  wrapper->client = (MYSQL*)malloc(sizeof(MYSQL));
153
173
  return obj;
154
174
  }
155
175
 
176
+ static VALUE rb_mysql_client_escape(RB_MYSQL_UNUSED VALUE klass, VALUE str) {
177
+ unsigned char *newStr;
178
+ VALUE rb_str;
179
+ unsigned long newLen, oldLen;
180
+
181
+ Check_Type(str, T_STRING);
182
+
183
+ oldLen = RSTRING_LEN(str);
184
+ newStr = malloc(oldLen*2+1);
185
+
186
+ newLen = mysql_escape_string((char *)newStr, StringValuePtr(str), oldLen);
187
+ if (newLen == oldLen) {
188
+ // no need to return a new ruby string if nothing changed
189
+ free(newStr);
190
+ return str;
191
+ } else {
192
+ rb_str = rb_str_new((const char*)newStr, newLen);
193
+ #ifdef HAVE_RUBY_ENCODING_H
194
+ rb_enc_copy(rb_str, str);
195
+ #endif
196
+ free(newStr);
197
+ return rb_str;
198
+ }
199
+ }
200
+
156
201
  static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags) {
157
202
  struct nogvl_connect_args args;
158
203
  GET_CLIENT(self);
@@ -168,7 +213,7 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
168
213
 
169
214
  if (rb_thread_blocking_region(nogvl_connect, &args, RUBY_UBF_IO, 0) == Qfalse) {
170
215
  // unable to connect
171
- return rb_raise_mysql2_error(wrapper->client);
216
+ return rb_raise_mysql2_error(wrapper);
172
217
  }
173
218
 
174
219
  return self;
@@ -206,6 +251,17 @@ static VALUE nogvl_send_query(void *ptr) {
206
251
  return rv == 0 ? Qtrue : Qfalse;
207
252
  }
208
253
 
254
+ static VALUE do_send_query(void *args) {
255
+ struct nogvl_send_query_args *query_args = args;
256
+ mysql_client_wrapper *wrapper = query_args->wrapper;
257
+ if (rb_thread_blocking_region(nogvl_send_query, args, RUBY_UBF_IO, 0) == Qfalse) {
258
+ // an error occurred, we're not active anymore
259
+ MARK_CONN_INACTIVE(self);
260
+ return rb_raise_mysql2_error(wrapper);
261
+ }
262
+ return Qnil;
263
+ }
264
+
209
265
  /*
210
266
  * even though we did rb_thread_select before calling this, a large
211
267
  * response can overflow the socket buffers and cause us to eventually
@@ -220,63 +276,161 @@ static VALUE nogvl_read_query_result(void *ptr) {
220
276
 
221
277
  /* mysql_store_result may (unlikely) read rows off the socket */
222
278
  static VALUE nogvl_store_result(void *ptr) {
223
- MYSQL * client = ptr;
224
- return (VALUE)mysql_store_result(client);
279
+ mysql_client_wrapper *wrapper;
280
+ MYSQL_RES *result;
281
+
282
+ wrapper = (mysql_client_wrapper *)ptr;
283
+ result = mysql_store_result(wrapper->client);
284
+
285
+ // once our result is stored off, this connection is
286
+ // ready for another command to be issued
287
+ wrapper->active = 0;
288
+
289
+ return (VALUE)result;
225
290
  }
226
291
 
227
292
  static VALUE rb_mysql_client_async_result(VALUE self) {
228
293
  MYSQL_RES * result;
294
+ VALUE resultObj;
295
+ #ifdef HAVE_RUBY_ENCODING_H
296
+ mysql2_result_wrapper * result_wrapper;
297
+ #endif
229
298
  GET_CLIENT(self);
230
299
 
300
+ // if we're not waiting on a result, do nothing
301
+ if (!wrapper->active)
302
+ return Qnil;
303
+
231
304
  REQUIRE_OPEN_DB(wrapper);
232
305
  if (rb_thread_blocking_region(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
233
306
  // an error occurred, mark this connection inactive
234
307
  MARK_CONN_INACTIVE(self);
235
- return rb_raise_mysql2_error(wrapper->client);
308
+ return rb_raise_mysql2_error(wrapper);
236
309
  }
237
310
 
238
- result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper->client, RUBY_UBF_IO, 0);
239
-
240
- // we have our result, mark this connection inactive
241
- MARK_CONN_INACTIVE(self);
311
+ result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper, RUBY_UBF_IO, 0);
242
312
 
243
313
  if (result == NULL) {
244
314
  if (mysql_field_count(wrapper->client) != 0) {
245
- rb_raise_mysql2_error(wrapper->client);
315
+ rb_raise_mysql2_error(wrapper);
246
316
  }
247
317
  return Qnil;
248
318
  }
249
319
 
250
- VALUE resultObj = rb_mysql_result_to_obj(result);
320
+ resultObj = rb_mysql_result_to_obj(result);
251
321
  // pass-through query options for result construction later
252
322
  rb_iv_set(resultObj, "@query_options", rb_funcall(rb_iv_get(self, "@query_options"), rb_intern("dup"), 0));
253
323
 
254
324
  #ifdef HAVE_RUBY_ENCODING_H
255
- mysql2_result_wrapper * result_wrapper;
256
325
  GetMysql2Result(resultObj, result_wrapper);
257
326
  result_wrapper->encoding = wrapper->encoding;
258
327
  #endif
259
328
  return resultObj;
260
329
  }
261
330
 
331
+ #ifndef _WIN32
332
+ struct async_query_args {
333
+ int fd;
334
+ VALUE self;
335
+ };
336
+
337
+ static VALUE disconnect_and_raise(VALUE self, VALUE error) {
338
+ GET_CLIENT(self);
339
+
340
+ wrapper->closed = 1;
341
+ wrapper->active = 0;
342
+
343
+ // manually close the socket for read/write
344
+ // this feels dirty, but is there another way?
345
+ shutdown(wrapper->client->net.fd, 2);
346
+
347
+ rb_exc_raise(error);
348
+
349
+ return Qnil;
350
+ }
351
+
352
+ static VALUE do_query(void *args) {
353
+ struct async_query_args *async_args;
354
+ struct timeval tv;
355
+ struct timeval* tvp;
356
+ long int sec;
357
+ int retval;
358
+ VALUE read_timeout;
359
+
360
+ async_args = (struct async_query_args *)args;
361
+ read_timeout = rb_iv_get(async_args->self, "@read_timeout");
362
+
363
+ tvp = NULL;
364
+ if (!NIL_P(read_timeout)) {
365
+ Check_Type(read_timeout, T_FIXNUM);
366
+ tvp = &tv;
367
+ sec = FIX2INT(read_timeout);
368
+ // TODO: support partial seconds?
369
+ // also, this check is here for sanity, we also check up in Ruby
370
+ if (sec >= 0) {
371
+ tvp->tv_sec = sec;
372
+ } else {
373
+ rb_raise(cMysql2Error, "read_timeout must be a positive integer, you passed %ld", sec);
374
+ }
375
+ tvp->tv_usec = 0;
376
+ }
377
+
378
+ for(;;) {
379
+ retval = rb_wait_for_single_fd(async_args->fd, RB_WAITFD_IN, tvp);
380
+
381
+ if (retval == 0) {
382
+ rb_raise(cMysql2Error, "Timeout waiting for a response from the last query. (waited %d seconds)", FIX2INT(read_timeout));
383
+ }
384
+
385
+ if (retval < 0) {
386
+ rb_sys_fail(0);
387
+ }
388
+
389
+ if (retval > 0) {
390
+ break;
391
+ }
392
+ }
393
+
394
+ return Qnil;
395
+ }
396
+ #else
397
+ static VALUE finish_and_mark_inactive(void *args) {
398
+ VALUE self;
399
+ MYSQL_RES *result;
400
+
401
+ self = (VALUE)args;
402
+
403
+ GET_CLIENT(self);
404
+
405
+ if (wrapper->active) {
406
+ // if we got here, the result hasn't been read off the wire yet
407
+ // so lets do that and then throw it away because we have no way
408
+ // of getting it back up to the caller from here
409
+ result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper, RUBY_UBF_IO, 0);
410
+ mysql_free_result(result);
411
+
412
+ wrapper->active = 0;
413
+ }
414
+
415
+ return Qnil;
416
+ }
417
+ #endif
418
+
262
419
  static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
420
+ #ifndef _WIN32
421
+ struct async_query_args async_args;
422
+ #endif
263
423
  struct nogvl_send_query_args args;
264
- fd_set fdset;
265
- int fd, retval;
266
424
  int async = 0;
267
425
  VALUE opts, defaults;
426
+ #ifdef HAVE_RUBY_ENCODING_H
427
+ rb_encoding *conn_enc;
428
+ #endif
268
429
  GET_CLIENT(self);
269
430
 
270
431
  REQUIRE_OPEN_DB(wrapper);
271
432
  args.mysql = wrapper->client;
272
433
 
273
- // see if this connection is still waiting on a result from a previous query
274
- if (wrapper->active == 0) {
275
- // mark this connection active
276
- wrapper->active = 1;
277
- } else {
278
- rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result");
279
- }
280
434
 
281
435
  defaults = rb_iv_get(self, "@query_options");
282
436
  if (rb_scan_args(argc, argv, "11", &args.sql, &opts) == 2) {
@@ -290,85 +444,96 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
290
444
  opts = defaults;
291
445
  }
292
446
 
447
+ Check_Type(args.sql, T_STRING);
293
448
  #ifdef HAVE_RUBY_ENCODING_H
294
- rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
449
+ conn_enc = rb_to_encoding(wrapper->encoding);
295
450
  // ensure the string is in the encoding the connection is expecting
296
451
  args.sql = rb_str_export_to_enc(args.sql, conn_enc);
297
452
  #endif
298
453
 
299
- if (rb_thread_blocking_region(nogvl_send_query, &args, RUBY_UBF_IO, 0) == Qfalse) {
300
- // an error occurred, we're not active anymore
301
- MARK_CONN_INACTIVE(self);
302
- return rb_raise_mysql2_error(wrapper->client);
454
+ // see if this connection is still waiting on a result from a previous query
455
+ if (wrapper->active == 0) {
456
+ // mark this connection active
457
+ wrapper->active = 1;
458
+ } else {
459
+ rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result");
303
460
  }
304
461
 
462
+ args.wrapper = wrapper;
463
+
464
+ #ifndef _WIN32
465
+ rb_rescue2(do_send_query, (VALUE)&args, disconnect_and_raise, self, rb_eException, (VALUE)0);
466
+
305
467
  if (!async) {
306
- // the below code is largely from do_mysql
307
- // http://github.com/datamapper/do
308
- fd = wrapper->client->net.fd;
309
- for(;;) {
310
- FD_ZERO(&fdset);
311
- FD_SET(fd, &fdset);
312
-
313
- retval = rb_thread_select(fd + 1, &fdset, NULL, NULL, NULL);
314
-
315
- if (retval < 0) {
316
- rb_sys_fail(0);
317
- }
318
-
319
- if (retval > 0) {
320
- break;
321
- }
322
- }
468
+ async_args.fd = wrapper->client->net.fd;
469
+ async_args.self = self;
323
470
 
324
- VALUE result = rb_mysql_client_async_result(self);
471
+ rb_rescue2(do_query, (VALUE)&async_args, disconnect_and_raise, self, rb_eException, (VALUE)0);
325
472
 
326
- return result;
473
+ return rb_mysql_client_async_result(self);
327
474
  } else {
328
475
  return Qnil;
329
476
  }
477
+ #else
478
+ do_send_query(&args);
479
+
480
+ // this will just block until the result is ready
481
+ return rb_ensure(rb_mysql_client_async_result, self, finish_and_mark_inactive, self);
482
+ #endif
330
483
  }
331
484
 
332
- static VALUE rb_mysql_client_escape(VALUE self, VALUE str) {
333
- VALUE newStr;
485
+ static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) {
486
+ unsigned char *newStr;
487
+ VALUE rb_str;
334
488
  unsigned long newLen, oldLen;
489
+ #ifdef HAVE_RUBY_ENCODING_H
490
+ rb_encoding *default_internal_enc;
491
+ rb_encoding *conn_enc;
492
+ #endif
335
493
  GET_CLIENT(self);
336
494
 
337
495
  REQUIRE_OPEN_DB(wrapper);
338
496
  Check_Type(str, T_STRING);
339
497
  #ifdef HAVE_RUBY_ENCODING_H
340
- rb_encoding *default_internal_enc = rb_default_internal_encoding();
341
- rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
498
+ default_internal_enc = rb_default_internal_encoding();
499
+ conn_enc = rb_to_encoding(wrapper->encoding);
342
500
  // ensure the string is in the encoding the connection is expecting
343
501
  str = rb_str_export_to_enc(str, conn_enc);
344
502
  #endif
345
503
 
346
504
  oldLen = RSTRING_LEN(str);
347
- newStr = rb_str_new(0, oldLen*2+1);
505
+ newStr = malloc(oldLen*2+1);
348
506
 
349
- newLen = mysql_real_escape_string(wrapper->client, RSTRING_PTR(newStr), StringValuePtr(str), oldLen);
507
+ newLen = mysql_real_escape_string(wrapper->client, (char *)newStr, StringValuePtr(str), oldLen);
350
508
  if (newLen == oldLen) {
351
509
  // no need to return a new ruby string if nothing changed
510
+ free(newStr);
352
511
  return str;
353
512
  } else {
354
- rb_str_resize(newStr, newLen);
513
+ rb_str = rb_str_new((const char*)newStr, newLen);
355
514
  #ifdef HAVE_RUBY_ENCODING_H
356
- rb_enc_associate(newStr, conn_enc);
515
+ rb_enc_associate(rb_str, conn_enc);
357
516
  if (default_internal_enc) {
358
- newStr = rb_str_export_to_enc(newStr, default_internal_enc);
517
+ rb_str = rb_str_export_to_enc(rb_str, default_internal_enc);
359
518
  }
360
519
  #endif
361
- return newStr;
520
+ free(newStr);
521
+ return rb_str;
362
522
  }
363
523
  }
364
524
 
365
525
  static VALUE rb_mysql_client_info(VALUE self) {
366
- VALUE version = rb_hash_new(), client_info;
526
+ VALUE version, client_info;
527
+ #ifdef HAVE_RUBY_ENCODING_H
528
+ rb_encoding *default_internal_enc;
529
+ rb_encoding *conn_enc;
530
+ #endif
367
531
  GET_CLIENT(self);
532
+ version = rb_hash_new();
368
533
 
369
534
  #ifdef HAVE_RUBY_ENCODING_H
370
- rb_encoding *default_internal_enc = rb_default_internal_encoding();
371
- rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
535
+ default_internal_enc = rb_default_internal_encoding();
536
+ conn_enc = rb_to_encoding(wrapper->encoding);
372
537
  #endif
373
538
 
374
539
  rb_hash_aset(version, sym_id, LONG2NUM(mysql_get_client_version()));
@@ -385,12 +550,16 @@ static VALUE rb_mysql_client_info(VALUE self) {
385
550
 
386
551
  static VALUE rb_mysql_client_server_info(VALUE self) {
387
552
  VALUE version, server_info;
553
+ #ifdef HAVE_RUBY_ENCODING_H
554
+ rb_encoding *default_internal_enc;
555
+ rb_encoding *conn_enc;
556
+ #endif
388
557
  GET_CLIENT(self);
389
558
 
390
559
  REQUIRE_OPEN_DB(wrapper);
391
560
  #ifdef HAVE_RUBY_ENCODING_H
392
- rb_encoding *default_internal_enc = rb_default_internal_encoding();
393
- rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
561
+ default_internal_enc = rb_default_internal_encoding();
562
+ conn_enc = rb_to_encoding(wrapper->encoding);
394
563
  #endif
395
564
 
396
565
  version = rb_hash_new();
@@ -408,8 +577,13 @@ static VALUE rb_mysql_client_server_info(VALUE self) {
408
577
 
409
578
  static VALUE rb_mysql_client_socket(VALUE self) {
410
579
  GET_CLIENT(self);
580
+ #ifndef _WIN32
411
581
  REQUIRE_OPEN_DB(wrapper);
412
- return INT2NUM(wrapper->client->net.fd);
582
+ int fd_set_fd = wrapper->client->net.fd;
583
+ return INT2NUM(fd_set_fd);
584
+ #else
585
+ rb_raise(cMysql2Error, "Raw access to the mysql file descriptor isn't supported on Windows");
586
+ #endif
413
587
  }
414
588
 
415
589
  static VALUE rb_mysql_client_last_id(VALUE self) {
@@ -419,17 +593,49 @@ static VALUE rb_mysql_client_last_id(VALUE self) {
419
593
  }
420
594
 
421
595
  static VALUE rb_mysql_client_affected_rows(VALUE self) {
422
- GET_CLIENT(self);
423
596
  my_ulonglong retVal;
597
+ GET_CLIENT(self);
424
598
 
425
599
  REQUIRE_OPEN_DB(wrapper);
426
600
  retVal = mysql_affected_rows(wrapper->client);
427
601
  if (retVal == (my_ulonglong)-1) {
428
- rb_raise_mysql2_error(wrapper->client);
602
+ rb_raise_mysql2_error(wrapper);
429
603
  }
430
604
  return ULL2NUM(retVal);
431
605
  }
432
606
 
607
+ static VALUE rb_mysql_client_thread_id(VALUE self) {
608
+ unsigned long retVal;
609
+ GET_CLIENT(self);
610
+
611
+ REQUIRE_OPEN_DB(wrapper);
612
+ retVal = mysql_thread_id(wrapper->client);
613
+ return ULL2NUM(retVal);
614
+ }
615
+
616
+ static VALUE nogvl_ping(void *ptr) {
617
+ MYSQL *client = ptr;
618
+
619
+ return mysql_ping(client) == 0 ? Qtrue : Qfalse;
620
+ }
621
+
622
+ static VALUE rb_mysql_client_ping(VALUE self) {
623
+ GET_CLIENT(self);
624
+
625
+ if (wrapper->closed) {
626
+ return Qfalse;
627
+ } else {
628
+ return rb_thread_blocking_region(nogvl_ping, wrapper->client, RUBY_UBF_IO, 0);
629
+ }
630
+ }
631
+
632
+ #ifdef HAVE_RUBY_ENCODING_H
633
+ static VALUE rb_mysql_client_encoding(VALUE self) {
634
+ GET_CLIENT(self);
635
+ return wrapper->encoding;
636
+ }
637
+ #endif
638
+
433
639
  static VALUE set_reconnect(VALUE self, VALUE value) {
434
640
  my_bool reconnect;
435
641
  GET_CLIENT(self);
@@ -437,6 +643,7 @@ static VALUE set_reconnect(VALUE self, VALUE value) {
437
643
  if(!NIL_P(value)) {
438
644
  reconnect = value == Qfalse ? 0 : 1;
439
645
 
646
+ wrapper->reconnect_enabled = reconnect;
440
647
  /* set default reconnect behavior */
441
648
  if (mysql_options(wrapper->client, MYSQL_OPT_RECONNECT, &reconnect)) {
442
649
  /* TODO: warning - unable to set reconnect behavior */
@@ -465,13 +672,16 @@ static VALUE set_connect_timeout(VALUE self, VALUE value) {
465
672
 
466
673
  static VALUE set_charset_name(VALUE self, VALUE value) {
467
674
  char * charset_name;
675
+ #ifdef HAVE_RUBY_ENCODING_H
676
+ VALUE new_encoding;
677
+ #endif
468
678
  GET_CLIENT(self);
469
679
 
470
680
  #ifdef HAVE_RUBY_ENCODING_H
471
- VALUE new_encoding;
472
681
  new_encoding = rb_funcall(cMysql2Client, intern_encoding_from_charset, 1, value);
473
682
  if (new_encoding == Qnil) {
474
- rb_raise(cMysql2Error, "Unsupported charset: '%s'", RSTRING_PTR(value));
683
+ VALUE inspect = rb_inspect(value);
684
+ rb_raise(cMysql2Error, "Unsupported charset: '%s'", RSTRING_PTR(inspect));
475
685
  } else {
476
686
  if (wrapper->encoding == Qnil) {
477
687
  wrapper->encoding = new_encoding;
@@ -509,7 +719,7 @@ static VALUE init_connection(VALUE self) {
509
719
 
510
720
  if (rb_thread_blocking_region(nogvl_init, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
511
721
  /* TODO: warning - not enough memory? */
512
- return rb_raise_mysql2_error(wrapper->client);
722
+ return rb_raise_mysql2_error(wrapper);
513
723
  }
514
724
 
515
725
  wrapper->closed = 0;
@@ -517,19 +727,43 @@ static VALUE init_connection(VALUE self) {
517
727
  }
518
728
 
519
729
  void init_mysql2_client() {
730
+ // verify the libmysql we're about to use was the version we were built against
731
+ // https://github.com/luislavena/mysql-gem/commit/a600a9c459597da0712f70f43736e24b484f8a99
732
+ int i;
733
+ int dots = 0;
734
+ const char *lib = mysql_get_client_info();
735
+ for (i = 0; lib[i] != 0 && MYSQL_SERVER_VERSION[i] != 0; i++) {
736
+ if (lib[i] == '.') {
737
+ dots++;
738
+ // we only compare MAJOR and MINOR
739
+ if (dots == 2) break;
740
+ }
741
+ if (lib[i] != MYSQL_SERVER_VERSION[i]) {
742
+ 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);
743
+ return;
744
+ }
745
+ }
746
+
520
747
  cMysql2Client = rb_define_class_under(mMysql2, "Client", rb_cObject);
521
748
 
522
749
  rb_define_alloc_func(cMysql2Client, allocate);
523
750
 
751
+ rb_define_singleton_method(cMysql2Client, "escape", rb_mysql_client_escape, 1);
752
+
524
753
  rb_define_method(cMysql2Client, "close", rb_mysql_client_close, 0);
525
754
  rb_define_method(cMysql2Client, "query", rb_mysql_client_query, -1);
526
- rb_define_method(cMysql2Client, "escape", rb_mysql_client_escape, 1);
755
+ rb_define_method(cMysql2Client, "escape", rb_mysql_client_real_escape, 1);
527
756
  rb_define_method(cMysql2Client, "info", rb_mysql_client_info, 0);
528
757
  rb_define_method(cMysql2Client, "server_info", rb_mysql_client_server_info, 0);
529
758
  rb_define_method(cMysql2Client, "socket", rb_mysql_client_socket, 0);
530
759
  rb_define_method(cMysql2Client, "async_result", rb_mysql_client_async_result, 0);
531
760
  rb_define_method(cMysql2Client, "last_id", rb_mysql_client_last_id, 0);
532
761
  rb_define_method(cMysql2Client, "affected_rows", rb_mysql_client_affected_rows, 0);
762
+ rb_define_method(cMysql2Client, "thread_id", rb_mysql_client_thread_id, 0);
763
+ rb_define_method(cMysql2Client, "ping", rb_mysql_client_ping, 0);
764
+ #ifdef HAVE_RUBY_ENCODING_H
765
+ rb_define_method(cMysql2Client, "encoding", rb_mysql_client_encoding, 0);
766
+ #endif
533
767
 
534
768
  rb_define_private_method(cMysql2Client, "reconnect=", set_reconnect, 1);
535
769
  rb_define_private_method(cMysql2Client, "connect_timeout=", set_connect_timeout, 1);