mysql2 0.2.6-x86-mingw32 → 0.2.16-x86-mingw32

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