mysql2 0.2.6 → 0.2.7

Sign up to get free protection for your applications and to get access to all the features.
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.2@mysql2 --create
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.7 (March 28th, 2011)
4
+ * various fixes for em_mysql2 and fiber usage
5
+ * use our own Mysql2IndexDefinition class for better compatibility across ActiveRecord versions
6
+ * ensure the query is a string earlier in the Mysql2::Client#query codepath for 1.9
7
+ * only set binary ruby encoding on fields that have a binary flag *and* encoding set
8
+ * a few various optimizations
9
+ * add support for :read_timeout to be set on a connection
10
+ * Fix to install with MariDB on Windows
11
+ * add fibered em connection without activerecord
12
+ * fix some 1.9.3 compilation warnings
13
+ * add LD_RUN_PATH when using hard coded mysql paths - this should help users with MySQL installed in non-standard locations
14
+ * for windows support, duplicate the socket from libmysql and create a temporary CRT fd
15
+ * fix for handling years before 1970 on Windows
16
+ * fixes to the Fiber adapter
17
+ * set wait_timeout maximum on Windows to 2147483
18
+ * update supported range for Time objects
19
+ * upon being required, make sure the libmysql we're using is the one we were built against
20
+ * add Mysql2::Client#thread_id
21
+ * add Mysql2::Client#ping
22
+ * switch connection check in AR adapter to use Mysql2::Client#ping for efficiency
23
+ * prefer linking against thread-safe version of libmysqlclient
24
+ * define RSTRING_NOT_MODIFIED for an awesome rbx speed boost
25
+ * expose Mysql2::Client#encoding in 1.9, make sure we set the error message and sqlstate encodings accordingly
26
+ * do not segfault when raising for invalid charset (found in 1.9.3dev)
27
+
3
28
  ## 0.2.6 (October 19th, 2010)
4
29
  * version bump since the 0.2.5 win32 binary gems were broken
5
30
 
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
@@ -1,4 +1,4 @@
1
- Copyright (c) 2010 Brian Lopez - http://github.com/brianmario
1
+ Copyright (c) 2010-2011 Brian Lopez - http://github.com/brianmario
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -146,7 +146,7 @@ This would be helpful if you wanted to iterate over the results in a streaming m
146
146
 
147
147
  == ActiveRecord
148
148
 
149
- To use the ActiveRecord driver, all you should need to do is have this gem installed and set the adapter in your database.yml to "mysql2".
149
+ To use the ActiveRecord driver (with our without rails), all you should need to do is have this gem installed and set the adapter in your database.yml to "mysql2".
150
150
  That was easy right? :)
151
151
 
152
152
  == Asynchronous ActiveRecord
@@ -232,6 +232,14 @@ then iterating over every row using an #each like method yielding a block:
232
232
  Mysql
233
233
  7.500000 0.210000 7.710000 ( 8.065871)
234
234
 
235
+ == Development
236
+
237
+ To run the tests, you can use RVM and Bundler to create a pristine environment for mysql2 development/hacking.
238
+ Use 'bundle install' to install the necessary development and testing gems:
239
+
240
+ bundle install
241
+ rake
242
+
235
243
  == Special Thanks
236
244
 
237
245
  * Eric Wong - for the contribution (and the informative explanations) of some thread-safety, non-blocking I/O and cleanup patches. You rock dude
@@ -27,9 +27,8 @@ class MysqlModel < ActiveRecord::Base
27
27
  end
28
28
 
29
29
  Benchmark.bmbm do |x|
30
- x.report do
30
+ x.report "Mysql2" do
31
31
  Mysql2Model.establish_connection(mysql2_opts)
32
- puts "Mysql2"
33
32
  number_of.times do
34
33
  Mysql2Model.all(:limit => 1000).each{ |r|
35
34
  r.attributes.keys.each{ |k|
@@ -39,9 +38,8 @@ Benchmark.bmbm do |x|
39
38
  end
40
39
  end
41
40
 
42
- x.report do
41
+ x.report "Mysql" do
43
42
  MysqlModel.establish_connection(mysql_opts)
44
- puts "Mysql"
45
43
  number_of.times do
46
44
  MysqlModel.all(:limit => 1000).each{ |r|
47
45
  r.attributes.keys.each{ |k|
@@ -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,8 +14,7 @@ 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" do
19
18
  number_of.times do
20
19
  mysql2_result = mysql2.query sql, :symbolize_keys => true
21
20
  mysql2_result.each do |res|
@@ -26,8 +25,7 @@ Benchmark.bmbm do |x|
26
25
 
27
26
  mysql = Mysql.new("localhost", "root")
28
27
  mysql.query "USE #{database}"
29
- x.report do
30
- puts "Mysql"
28
+ x.report "Mysql" do
31
29
  number_of.times do
32
30
  mysql_result = mysql.query sql
33
31
  mysql_result.each_hash do |res|
@@ -38,8 +36,7 @@ Benchmark.bmbm do |x|
38
36
 
39
37
  do_mysql = DataObjects::Connection.new("mysql://localhost/#{database}")
40
38
  command = DataObjects::Mysql::Command.new do_mysql, sql
41
- x.report do
42
- puts "do_mysql"
39
+ x.report "do_mysql" do
43
40
  number_of.times do
44
41
  do_result = command.execute_reader
45
42
  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"
@@ -5,13 +5,12 @@
5
5
  VALUE cMysql2Client;
6
6
  extern VALUE mMysql2, cMysql2Error;
7
7
  static VALUE intern_encoding_from_charset;
8
- static ID sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as, sym_array;
8
+ static VALUE sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as, sym_array;
9
9
  static ID intern_merge, intern_error_number_eql, intern_sql_state_eql;
10
10
 
11
11
  #define REQUIRE_OPEN_DB(wrapper) \
12
12
  if(wrapper->closed) { \
13
13
  rb_raise(cMysql2Error, "closed MySQL connection"); \
14
- return Qnil; \
15
14
  }
16
15
 
17
16
  #define MARK_CONN_INACTIVE(conn) \
@@ -74,10 +73,24 @@ static void rb_mysql_client_mark(void * wrapper) {
74
73
  }
75
74
  }
76
75
 
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)));
76
+ static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) {
77
+ VALUE rb_error_msg = rb_str_new2(mysql_error(wrapper->client));
78
+ VALUE rb_sql_state = rb_tainted_str_new2(mysql_sqlstate(wrapper->client));
79
+ #ifdef HAVE_RUBY_ENCODING_H
80
+ rb_encoding *default_internal_enc = rb_default_internal_encoding();
81
+ rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
82
+
83
+ rb_enc_associate(rb_error_msg, conn_enc);
84
+ rb_enc_associate(rb_sql_state, conn_enc);
85
+ if (default_internal_enc) {
86
+ rb_error_msg = rb_str_export_to_enc(rb_error_msg, default_internal_enc);
87
+ rb_sql_state = rb_str_export_to_enc(rb_sql_state, default_internal_enc);
88
+ }
89
+ #endif
90
+
91
+ VALUE e = rb_exc_new3(cMysql2Error, rb_error_msg);
92
+ rb_funcall(e, intern_error_number_eql, 1, UINT2NUM(mysql_errno(wrapper->client)));
93
+ rb_funcall(e, intern_sql_state_eql, 1, rb_sql_state);
81
94
  rb_exc_raise(e);
82
95
  return Qnil;
83
96
  }
@@ -105,7 +118,11 @@ static VALUE nogvl_connect(void *ptr) {
105
118
  }
106
119
 
107
120
  static VALUE nogvl_close(void *ptr) {
108
- mysql_client_wrapper *wrapper = ptr;
121
+ mysql_client_wrapper *wrapper;
122
+ #ifndef _WIN32
123
+ int flags;
124
+ #endif
125
+ wrapper = ptr;
109
126
  if (!wrapper->closed) {
110
127
  wrapper->closed = 1;
111
128
 
@@ -119,16 +136,17 @@ static VALUE nogvl_close(void *ptr) {
119
136
  * so ignore any potential fcntl errors since they don't matter
120
137
  */
121
138
  #ifndef _WIN32
122
- int flags = fcntl(wrapper->client->net.fd, F_GETFL);
139
+ flags = fcntl(wrapper->client->net.fd, F_GETFL);
123
140
  if (flags > 0 && !(flags & O_NONBLOCK))
124
141
  fcntl(wrapper->client->net.fd, F_SETFL, flags | O_NONBLOCK);
125
142
  #else
126
- u_long iMode = 1;
143
+ u_long iMode;
144
+ iMode = 1;
127
145
  ioctlsocket(wrapper->client->net.fd, FIONBIO, &iMode);
128
146
  #endif
129
147
 
130
148
  mysql_close(wrapper->client);
131
- free(wrapper->client);
149
+ xfree(wrapper->client);
132
150
  }
133
151
 
134
152
  return Qnil;
@@ -149,7 +167,7 @@ static VALUE allocate(VALUE klass) {
149
167
  wrapper->encoding = Qnil;
150
168
  wrapper->active = 0;
151
169
  wrapper->closed = 1;
152
- wrapper->client = (MYSQL*)malloc(sizeof(MYSQL));
170
+ wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL));
153
171
  return obj;
154
172
  }
155
173
 
@@ -168,7 +186,7 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
168
186
 
169
187
  if (rb_thread_blocking_region(nogvl_connect, &args, RUBY_UBF_IO, 0) == Qfalse) {
170
188
  // unable to connect
171
- return rb_raise_mysql2_error(wrapper->client);
189
+ return rb_raise_mysql2_error(wrapper);
172
190
  }
173
191
 
174
192
  return self;
@@ -226,13 +244,17 @@ static VALUE nogvl_store_result(void *ptr) {
226
244
 
227
245
  static VALUE rb_mysql_client_async_result(VALUE self) {
228
246
  MYSQL_RES * result;
247
+ VALUE resultObj;
248
+ #ifdef HAVE_RUBY_ENCODING_H
249
+ mysql2_result_wrapper * result_wrapper;
250
+ #endif
229
251
  GET_CLIENT(self);
230
252
 
231
253
  REQUIRE_OPEN_DB(wrapper);
232
254
  if (rb_thread_blocking_region(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
233
255
  // an error occurred, mark this connection inactive
234
256
  MARK_CONN_INACTIVE(self);
235
- return rb_raise_mysql2_error(wrapper->client);
257
+ return rb_raise_mysql2_error(wrapper);
236
258
  }
237
259
 
238
260
  result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper->client, RUBY_UBF_IO, 0);
@@ -242,17 +264,16 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
242
264
 
243
265
  if (result == NULL) {
244
266
  if (mysql_field_count(wrapper->client) != 0) {
245
- rb_raise_mysql2_error(wrapper->client);
267
+ rb_raise_mysql2_error(wrapper);
246
268
  }
247
269
  return Qnil;
248
270
  }
249
271
 
250
- VALUE resultObj = rb_mysql_result_to_obj(result);
272
+ resultObj = rb_mysql_result_to_obj(result);
251
273
  // pass-through query options for result construction later
252
274
  rb_iv_set(resultObj, "@query_options", rb_funcall(rb_iv_get(self, "@query_options"), rb_intern("dup"), 0));
253
275
 
254
276
  #ifdef HAVE_RUBY_ENCODING_H
255
- mysql2_result_wrapper * result_wrapper;
256
277
  GetMysql2Result(resultObj, result_wrapper);
257
278
  result_wrapper->encoding = wrapper->encoding;
258
279
  #endif
@@ -264,7 +285,14 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
264
285
  fd_set fdset;
265
286
  int fd, retval;
266
287
  int async = 0;
267
- VALUE opts, defaults;
288
+ VALUE opts, defaults, read_timeout;
289
+ #ifdef HAVE_RUBY_ENCODING_H
290
+ rb_encoding *conn_enc;
291
+ #endif
292
+ struct timeval tv;
293
+ struct timeval* tvp;
294
+ long int sec;
295
+ VALUE result;
268
296
  GET_CLIENT(self);
269
297
 
270
298
  REQUIRE_OPEN_DB(wrapper);
@@ -290,8 +318,9 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
290
318
  opts = defaults;
291
319
  }
292
320
 
321
+ Check_Type(args.sql, T_STRING);
293
322
  #ifdef HAVE_RUBY_ENCODING_H
294
- rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
323
+ conn_enc = rb_to_encoding(wrapper->encoding);
295
324
  // ensure the string is in the encoding the connection is expecting
296
325
  args.sql = rb_str_export_to_enc(args.sql, conn_enc);
297
326
  #endif
@@ -299,7 +328,24 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
299
328
  if (rb_thread_blocking_region(nogvl_send_query, &args, RUBY_UBF_IO, 0) == Qfalse) {
300
329
  // an error occurred, we're not active anymore
301
330
  MARK_CONN_INACTIVE(self);
302
- return rb_raise_mysql2_error(wrapper->client);
331
+ return rb_raise_mysql2_error(wrapper);
332
+ }
333
+
334
+ read_timeout = rb_iv_get(self, "@read_timeout");
335
+
336
+ tvp = NULL;
337
+ if (!NIL_P(read_timeout)) {
338
+ Check_Type(read_timeout, T_FIXNUM);
339
+ tvp = &tv;
340
+ sec = FIX2INT(read_timeout);
341
+ // TODO: support partial seconds?
342
+ // also, this check is here for sanity, we also check up in Ruby
343
+ if (sec >= 0) {
344
+ tvp->tv_sec = sec;
345
+ } else {
346
+ rb_raise(cMysql2Error, "read_timeout must be a positive integer, you passed %ld", sec);
347
+ }
348
+ tvp->tv_usec = 0;
303
349
  }
304
350
 
305
351
  if (!async) {
@@ -307,10 +353,32 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
307
353
  // http://github.com/datamapper/do
308
354
  fd = wrapper->client->net.fd;
309
355
  for(;;) {
356
+ int fd_set_fd = fd;
357
+
358
+ #ifdef _WIN32
359
+ WSAPROTOCOL_INFO wsa_pi;
360
+ // dupicate the SOCKET from libmysql
361
+ int r = WSADuplicateSocket(fd, GetCurrentProcessId(), &wsa_pi);
362
+ SOCKET s = WSASocket(wsa_pi.iAddressFamily, wsa_pi.iSocketType, wsa_pi.iProtocol, &wsa_pi, 0, 0);
363
+ // create the CRT fd so ruby can get back to the SOCKET
364
+ fd_set_fd = _open_osfhandle(s, O_RDWR|O_BINARY);
365
+ #endif
366
+
310
367
  FD_ZERO(&fdset);
311
- FD_SET(fd, &fdset);
368
+ FD_SET(fd_set_fd, &fdset);
312
369
 
313
- retval = rb_thread_select(fd + 1, &fdset, NULL, NULL, NULL);
370
+ retval = rb_thread_select(fd_set_fd + 1, &fdset, NULL, NULL, tvp);
371
+
372
+ #ifdef _WIN32
373
+ // cleanup the CRT fd
374
+ _close(fd_set_fd);
375
+ // cleanup the duplicated SOCKET
376
+ closesocket(s);
377
+ #endif
378
+
379
+ if (retval == 0) {
380
+ rb_raise(cMysql2Error, "Timeout waiting for a response from the last query. (waited %d seconds)", FIX2INT(read_timeout));
381
+ }
314
382
 
315
383
  if (retval < 0) {
316
384
  rb_sys_fail(0);
@@ -321,7 +389,7 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
321
389
  }
322
390
  }
323
391
 
324
- VALUE result = rb_mysql_client_async_result(self);
392
+ result = rb_mysql_client_async_result(self);
325
393
 
326
394
  return result;
327
395
  } else {
@@ -330,45 +398,57 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
330
398
  }
331
399
 
332
400
  static VALUE rb_mysql_client_escape(VALUE self, VALUE str) {
333
- VALUE newStr;
401
+ unsigned char *newStr;
402
+ VALUE rb_str;
334
403
  unsigned long newLen, oldLen;
404
+ #ifdef HAVE_RUBY_ENCODING_H
405
+ rb_encoding *default_internal_enc;
406
+ rb_encoding *conn_enc;
407
+ #endif
335
408
  GET_CLIENT(self);
336
409
 
337
410
  REQUIRE_OPEN_DB(wrapper);
338
411
  Check_Type(str, T_STRING);
339
412
  #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);
413
+ default_internal_enc = rb_default_internal_encoding();
414
+ conn_enc = rb_to_encoding(wrapper->encoding);
342
415
  // ensure the string is in the encoding the connection is expecting
343
416
  str = rb_str_export_to_enc(str, conn_enc);
344
417
  #endif
345
418
 
346
419
  oldLen = RSTRING_LEN(str);
347
- newStr = rb_str_new(0, oldLen*2+1);
420
+ newStr = xmalloc(oldLen*2+1);
348
421
 
349
- newLen = mysql_real_escape_string(wrapper->client, RSTRING_PTR(newStr), StringValuePtr(str), oldLen);
422
+ newLen = mysql_real_escape_string(wrapper->client, (char *)newStr, StringValuePtr(str), oldLen);
350
423
  if (newLen == oldLen) {
351
424
  // no need to return a new ruby string if nothing changed
425
+ xfree(newStr);
352
426
  return str;
353
427
  } else {
354
- rb_str_resize(newStr, newLen);
428
+ rb_str = rb_str_new((const char*)newStr, newLen);
355
429
  #ifdef HAVE_RUBY_ENCODING_H
356
- rb_enc_associate(newStr, conn_enc);
430
+ rb_enc_associate(rb_str, conn_enc);
357
431
  if (default_internal_enc) {
358
- newStr = rb_str_export_to_enc(newStr, default_internal_enc);
432
+ rb_str = rb_str_export_to_enc(rb_str, default_internal_enc);
359
433
  }
360
434
  #endif
361
- return newStr;
435
+ xfree(newStr);
436
+ return rb_str;
362
437
  }
363
438
  }
364
439
 
365
440
  static VALUE rb_mysql_client_info(VALUE self) {
366
- VALUE version = rb_hash_new(), client_info;
441
+ VALUE version, client_info;
442
+ #ifdef HAVE_RUBY_ENCODING_H
443
+ rb_encoding *default_internal_enc;
444
+ rb_encoding *conn_enc;
445
+ #endif
367
446
  GET_CLIENT(self);
447
+ version = rb_hash_new();
368
448
 
369
449
  #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);
450
+ default_internal_enc = rb_default_internal_encoding();
451
+ conn_enc = rb_to_encoding(wrapper->encoding);
372
452
  #endif
373
453
 
374
454
  rb_hash_aset(version, sym_id, LONG2NUM(mysql_get_client_version()));
@@ -385,12 +465,16 @@ static VALUE rb_mysql_client_info(VALUE self) {
385
465
 
386
466
  static VALUE rb_mysql_client_server_info(VALUE self) {
387
467
  VALUE version, server_info;
468
+ #ifdef HAVE_RUBY_ENCODING_H
469
+ rb_encoding *default_internal_enc;
470
+ rb_encoding *conn_enc;
471
+ #endif
388
472
  GET_CLIENT(self);
389
473
 
390
474
  REQUIRE_OPEN_DB(wrapper);
391
475
  #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);
476
+ default_internal_enc = rb_default_internal_encoding();
477
+ conn_enc = rb_to_encoding(wrapper->encoding);
394
478
  #endif
395
479
 
396
480
  version = rb_hash_new();
@@ -419,17 +503,46 @@ static VALUE rb_mysql_client_last_id(VALUE self) {
419
503
  }
420
504
 
421
505
  static VALUE rb_mysql_client_affected_rows(VALUE self) {
422
- GET_CLIENT(self);
423
506
  my_ulonglong retVal;
507
+ GET_CLIENT(self);
424
508
 
425
509
  REQUIRE_OPEN_DB(wrapper);
426
510
  retVal = mysql_affected_rows(wrapper->client);
427
511
  if (retVal == (my_ulonglong)-1) {
428
- rb_raise_mysql2_error(wrapper->client);
512
+ rb_raise_mysql2_error(wrapper);
429
513
  }
430
514
  return ULL2NUM(retVal);
431
515
  }
432
516
 
517
+ static VALUE rb_mysql_client_thread_id(VALUE self) {
518
+ unsigned long retVal;
519
+ GET_CLIENT(self);
520
+
521
+ REQUIRE_OPEN_DB(wrapper);
522
+ retVal = mysql_thread_id(wrapper->client);
523
+ return ULL2NUM(retVal);
524
+ }
525
+
526
+ static VALUE nogvl_ping(void *ptr)
527
+ {
528
+ MYSQL *client = ptr;
529
+
530
+ return mysql_ping(client) == 0 ? Qtrue : Qfalse;
531
+ }
532
+
533
+ static VALUE rb_mysql_client_ping(VALUE self) {
534
+ GET_CLIENT(self);
535
+
536
+ return rb_thread_blocking_region(nogvl_ping, wrapper->client, RUBY_UBF_IO, 0);
537
+ }
538
+
539
+ #ifdef HAVE_RUBY_ENCODING_H
540
+ static VALUE rb_mysql_client_encoding(VALUE self) {
541
+ GET_CLIENT(self);
542
+ return wrapper->encoding;
543
+ }
544
+ #endif
545
+
433
546
  static VALUE set_reconnect(VALUE self, VALUE value) {
434
547
  my_bool reconnect;
435
548
  GET_CLIENT(self);
@@ -465,13 +578,16 @@ static VALUE set_connect_timeout(VALUE self, VALUE value) {
465
578
 
466
579
  static VALUE set_charset_name(VALUE self, VALUE value) {
467
580
  char * charset_name;
581
+ #ifdef HAVE_RUBY_ENCODING_H
582
+ VALUE new_encoding;
583
+ #endif
468
584
  GET_CLIENT(self);
469
585
 
470
586
  #ifdef HAVE_RUBY_ENCODING_H
471
- VALUE new_encoding;
472
587
  new_encoding = rb_funcall(cMysql2Client, intern_encoding_from_charset, 1, value);
473
588
  if (new_encoding == Qnil) {
474
- rb_raise(cMysql2Error, "Unsupported charset: '%s'", RSTRING_PTR(value));
589
+ VALUE inspect = rb_inspect(value);
590
+ rb_raise(cMysql2Error, "Unsupported charset: '%s'", RSTRING_PTR(inspect));
475
591
  } else {
476
592
  if (wrapper->encoding == Qnil) {
477
593
  wrapper->encoding = new_encoding;
@@ -509,7 +625,7 @@ static VALUE init_connection(VALUE self) {
509
625
 
510
626
  if (rb_thread_blocking_region(nogvl_init, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
511
627
  /* TODO: warning - not enough memory? */
512
- return rb_raise_mysql2_error(wrapper->client);
628
+ return rb_raise_mysql2_error(wrapper);
513
629
  }
514
630
 
515
631
  wrapper->closed = 0;
@@ -517,6 +633,23 @@ static VALUE init_connection(VALUE self) {
517
633
  }
518
634
 
519
635
  void init_mysql2_client() {
636
+ // verify the libmysql we're about to use was the version we were built against
637
+ // https://github.com/luislavena/mysql-gem/commit/a600a9c459597da0712f70f43736e24b484f8a99
638
+ int i;
639
+ int dots = 0;
640
+ const char *lib = mysql_get_client_info();
641
+ for (i = 0; lib[i] != 0 && MYSQL_SERVER_VERSION[i] != 0; i++) {
642
+ if (lib[i] == '.') {
643
+ dots++;
644
+ // we only compare MAJOR and MINOR
645
+ if (dots == 2) break;
646
+ }
647
+ if (lib[i] != MYSQL_SERVER_VERSION[i]) {
648
+ 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);
649
+ return;
650
+ }
651
+ }
652
+
520
653
  cMysql2Client = rb_define_class_under(mMysql2, "Client", rb_cObject);
521
654
 
522
655
  rb_define_alloc_func(cMysql2Client, allocate);
@@ -530,6 +663,11 @@ void init_mysql2_client() {
530
663
  rb_define_method(cMysql2Client, "async_result", rb_mysql_client_async_result, 0);
531
664
  rb_define_method(cMysql2Client, "last_id", rb_mysql_client_last_id, 0);
532
665
  rb_define_method(cMysql2Client, "affected_rows", rb_mysql_client_affected_rows, 0);
666
+ rb_define_method(cMysql2Client, "thread_id", rb_mysql_client_thread_id, 0);
667
+ rb_define_method(cMysql2Client, "ping", rb_mysql_client_ping, 0);
668
+ #ifdef HAVE_RUBY_ENCODING_H
669
+ rb_define_method(cMysql2Client, "encoding", rb_mysql_client_encoding, 0);
670
+ #endif
533
671
 
534
672
  rb_define_private_method(cMysql2Client, "reconnect=", set_reconnect, 1);
535
673
  rb_define_private_method(cMysql2Client, "connect_timeout=", set_connect_timeout, 1);