mysql2 0.3.18 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -0
  3. data/LICENSE +21 -0
  4. data/README.md +63 -12
  5. data/examples/eventmachine.rb +1 -1
  6. data/examples/threaded.rb +4 -6
  7. data/ext/mysql2/client.c +170 -175
  8. data/ext/mysql2/client.h +21 -1
  9. data/ext/mysql2/extconf.rb +95 -35
  10. data/ext/mysql2/infile.c +2 -2
  11. data/ext/mysql2/mysql2_ext.c +1 -0
  12. data/ext/mysql2/mysql2_ext.h +5 -6
  13. data/ext/mysql2/mysql_enc_name_to_ruby.h +2 -2
  14. data/ext/mysql2/mysql_enc_to_ruby.h +25 -22
  15. data/ext/mysql2/result.c +494 -132
  16. data/ext/mysql2/result.h +12 -6
  17. data/ext/mysql2/statement.c +494 -0
  18. data/ext/mysql2/statement.h +19 -0
  19. data/lib/mysql2/client.rb +68 -22
  20. data/lib/mysql2/console.rb +1 -1
  21. data/lib/mysql2/em.rb +5 -6
  22. data/lib/mysql2/error.rb +18 -27
  23. data/lib/mysql2/field.rb +3 -0
  24. data/lib/mysql2/statement.rb +17 -0
  25. data/lib/mysql2/version.rb +1 -1
  26. data/lib/mysql2.rb +38 -18
  27. data/spec/em/em_spec.rb +21 -21
  28. data/spec/mysql2/client_spec.rb +393 -351
  29. data/spec/mysql2/error_spec.rb +37 -36
  30. data/spec/mysql2/result_spec.rb +213 -208
  31. data/spec/mysql2/statement_spec.rb +684 -0
  32. data/spec/spec_helper.rb +7 -0
  33. data/spec/ssl/ca-cert.pem +17 -0
  34. data/spec/ssl/ca-key.pem +27 -0
  35. data/spec/ssl/ca.cnf +22 -0
  36. data/spec/ssl/cert.cnf +22 -0
  37. data/spec/ssl/client-cert.pem +17 -0
  38. data/spec/ssl/client-key.pem +27 -0
  39. data/spec/ssl/client-req.pem +15 -0
  40. data/spec/ssl/gen_certs.sh +48 -0
  41. data/spec/ssl/pkcs8-client-key.pem +28 -0
  42. data/spec/ssl/pkcs8-server-key.pem +28 -0
  43. data/spec/ssl/server-cert.pem +17 -0
  44. data/spec/ssl/server-key.pem +27 -0
  45. data/spec/ssl/server-req.pem +15 -0
  46. data/support/mysql_enc_to_ruby.rb +7 -8
  47. data/support/ruby_enc_to_mysql.rb +1 -1
  48. metadata +41 -46
data/ext/mysql2/client.c CHANGED
@@ -16,12 +16,12 @@
16
16
 
17
17
  VALUE cMysql2Client;
18
18
  extern VALUE mMysql2, cMysql2Error;
19
- static VALUE sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as, sym_array, sym_stream;
20
- static ID intern_merge, intern_merge_bang, intern_error_number_eql, intern_sql_state_eql;
19
+ static VALUE sym_id, sym_version, sym_header_version, sym_async, sym_symbolize_keys, sym_as, sym_array, sym_stream;
20
+ static ID intern_brackets, intern_merge, intern_merge_bang, intern_new_with_args;
21
21
 
22
22
  #ifndef HAVE_RB_HASH_DUP
23
- static VALUE rb_hash_dup(VALUE other) {
24
- return rb_funcall(rb_cHash, rb_intern("[]"), 1, other);
23
+ VALUE rb_hash_dup(VALUE other) {
24
+ return rb_funcall(rb_cHash, intern_brackets, 1, other);
25
25
  }
26
26
  #endif
27
27
 
@@ -30,25 +30,12 @@ static VALUE rb_hash_dup(VALUE other) {
30
30
  rb_raise(cMysql2Error, "MySQL client is not initialized"); \
31
31
  }
32
32
 
33
- #define REQUIRE_CONNECTED(wrapper) \
34
- REQUIRE_INITIALIZED(wrapper) \
35
- if (!wrapper->connected && !wrapper->reconnect_enabled) { \
36
- rb_raise(cMysql2Error, "closed MySQL connection"); \
37
- }
38
-
39
33
  #define REQUIRE_NOT_CONNECTED(wrapper) \
40
34
  REQUIRE_INITIALIZED(wrapper) \
41
35
  if (wrapper->connected) { \
42
36
  rb_raise(cMysql2Error, "MySQL connection is already open"); \
43
37
  }
44
38
 
45
- #define MARK_CONN_INACTIVE(conn) \
46
- wrapper->active_thread = Qnil;
47
-
48
- #define GET_CLIENT(self) \
49
- mysql_client_wrapper *wrapper; \
50
- Data_Get_Struct(self, mysql_client_wrapper, wrapper)
51
-
52
39
  /*
53
40
  * compatability with mysql-connector-c, where LIBMYSQL_VERSION is the correct
54
41
  * variable to use, but MYSQL_SERVER_VERSION gives the correct numbers when
@@ -136,16 +123,17 @@ static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) {
136
123
  rb_enc_associate(rb_sql_state, rb_usascii_encoding());
137
124
  #endif
138
125
 
139
- e = rb_funcall(cMysql2Error, rb_intern("new"), 2, rb_error_msg, LONG2FIX(wrapper->server_version));
140
- rb_funcall(e, intern_error_number_eql, 1, UINT2NUM(mysql_errno(wrapper->client)));
141
- rb_funcall(e, intern_sql_state_eql, 1, rb_sql_state);
126
+ e = rb_funcall(cMysql2Error, intern_new_with_args, 4,
127
+ rb_error_msg,
128
+ LONG2FIX(wrapper->server_version),
129
+ UINT2NUM(mysql_errno(wrapper->client)),
130
+ rb_sql_state);
142
131
  rb_exc_raise(e);
143
- return Qnil;
144
132
  }
145
133
 
146
134
  static void *nogvl_init(void *ptr) {
147
135
  MYSQL *client;
148
- mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr;
136
+ mysql_client_wrapper *wrapper = ptr;
149
137
 
150
138
  /* may initialize embedded server and read /etc/services off disk */
151
139
  client = mysql_init(wrapper->client);
@@ -182,23 +170,31 @@ static void *nogvl_connect(void *ptr) {
182
170
  */
183
171
  static VALUE invalidate_fd(int clientfd)
184
172
  {
185
- #ifdef SOCK_CLOEXEC
173
+ #ifdef O_CLOEXEC
186
174
  /* Atomically set CLOEXEC on the new FD in case another thread forks */
187
175
  int sockfd = open("/dev/null", O_RDWR | O_CLOEXEC);
188
- if (sockfd < 0) {
189
- /* Maybe SOCK_CLOEXEC is defined but not available on this kernel */
190
- int sockfd = open("/dev/null", O_RDWR);
191
- fcntl(sockfd, F_SETFD, FD_CLOEXEC);
192
- }
193
176
  #else
194
- /* Well we don't have SOCK_CLOEXEC, so just set FD_CLOEXEC quickly */
195
- int sockfd = open("/dev/null", O_RDWR);
196
- fcntl(sockfd, F_SETFD, FD_CLOEXEC);
177
+ /* Well we don't have O_CLOEXEC, trigger the fallback code below */
178
+ int sockfd = -1;
197
179
  #endif
198
180
 
199
181
  if (sockfd < 0) {
200
- /*
201
- * Cannot raise here, because one or both of the following may be true:
182
+ /* Either O_CLOEXEC wasn't defined at compile time, or it was defined at
183
+ * compile time, but isn't available at run-time. So we'll just be quick
184
+ * about setting FD_CLOEXEC now.
185
+ */
186
+ int flags;
187
+ sockfd = open("/dev/null", O_RDWR);
188
+ flags = fcntl(sockfd, F_GETFD);
189
+ /* Do the flags dance in case there are more defined flags in the future */
190
+ if (flags != -1) {
191
+ flags |= FD_CLOEXEC;
192
+ fcntl(sockfd, F_SETFD, flags);
193
+ }
194
+ }
195
+
196
+ if (sockfd < 0) {
197
+ /* Cannot raise here, because one or both of the following may be true:
202
198
  * a) we have no GVL (in C Ruby)
203
199
  * b) are running as a GC finalizer
204
200
  */
@@ -213,43 +209,47 @@ static VALUE invalidate_fd(int clientfd)
213
209
  #endif /* _WIN32 */
214
210
 
215
211
  static void *nogvl_close(void *ptr) {
216
- mysql_client_wrapper *wrapper;
217
- wrapper = ptr;
218
- if (wrapper->connected) {
219
- wrapper->active_thread = Qnil;
220
- wrapper->connected = 0;
221
- #ifndef _WIN32
222
- /* Invalidate the socket before calling mysql_close(). This prevents
223
- * mysql_close() from sending a mysql-QUIT or from calling shutdown() on
224
- * the socket. The difference is that invalidate_fd will drop this
225
- * process's reference to the socket only, while a QUIT or shutdown()
226
- * would render the underlying connection unusable, interrupting other
227
- * processes which share this object across a fork().
228
- */
229
- if (invalidate_fd(wrapper->client->net.fd) == Qfalse) {
230
- fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely, leaking some memory\n");
231
- close(wrapper->client->net.fd);
232
- return NULL;
233
- }
234
- #endif
212
+ mysql_client_wrapper *wrapper = ptr;
235
213
 
236
- mysql_close(wrapper->client); /* only used to free memory at this point */
214
+ if (wrapper->client) {
215
+ mysql_close(wrapper->client);
216
+ xfree(wrapper->client);
217
+ wrapper->client = NULL;
218
+ wrapper->connected = 0;
219
+ wrapper->active_thread = Qnil;
237
220
  }
238
221
 
239
222
  return NULL;
240
223
  }
241
224
 
225
+ /* this is called during GC */
242
226
  static void rb_mysql_client_free(void *ptr) {
243
- mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr;
227
+ mysql_client_wrapper *wrapper = ptr;
244
228
  decr_mysql2_client(wrapper);
245
229
  }
246
230
 
247
231
  void decr_mysql2_client(mysql_client_wrapper *wrapper)
248
232
  {
249
233
  wrapper->refcount--;
234
+
250
235
  if (wrapper->refcount == 0) {
236
+ #ifndef _WIN32
237
+ if (wrapper->connected) {
238
+ /* The client is being garbage collected while connected. Prevent
239
+ * mysql_close() from sending a mysql-QUIT or from calling shutdown() on
240
+ * the socket by invalidating it. invalidate_fd() will drop this
241
+ * process's reference to the socket only, while a QUIT or shutdown()
242
+ * would render the underlying connection unusable, interrupting other
243
+ * processes which share this object across a fork().
244
+ */
245
+ if (invalidate_fd(wrapper->client->net.fd) == Qfalse) {
246
+ fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely\n");
247
+ close(wrapper->client->net.fd);
248
+ }
249
+ }
250
+ #endif
251
+
251
252
  nogvl_close(wrapper);
252
- xfree(wrapper->client);
253
253
  xfree(wrapper);
254
254
  }
255
255
  }
@@ -259,7 +259,7 @@ static VALUE allocate(VALUE klass) {
259
259
  mysql_client_wrapper * wrapper;
260
260
  obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper);
261
261
  wrapper->encoding = Qnil;
262
- wrapper->active_thread = Qnil;
262
+ MARK_CONN_INACTIVE(self);
263
263
  wrapper->server_version = 0;
264
264
  wrapper->reconnect_enabled = 0;
265
265
  wrapper->connect_timeout = 0;
@@ -267,6 +267,7 @@ static VALUE allocate(VALUE klass) {
267
267
  wrapper->initialized = 0; /* means that that the wrapper is initialized */
268
268
  wrapper->refcount = 1;
269
269
  wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL));
270
+
270
271
  return obj;
271
272
  }
272
273
 
@@ -287,7 +288,7 @@ static VALUE rb_mysql_client_escape(RB_MYSQL_UNUSED VALUE klass, VALUE str) {
287
288
  oldLen = RSTRING_LEN(str);
288
289
  newStr = xmalloc(oldLen*2+1);
289
290
 
290
- newLen = mysql_escape_string((char *)newStr, StringValuePtr(str), oldLen);
291
+ newLen = mysql_escape_string((char *)newStr, RSTRING_PTR(str), oldLen);
291
292
  if (newLen == oldLen) {
292
293
  /* no need to return a new ruby string if nothing changed */
293
294
  xfree(newStr);
@@ -332,18 +333,17 @@ static VALUE rb_mysql_info(VALUE self) {
332
333
 
333
334
  static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags) {
334
335
  struct nogvl_connect_args args;
335
- time_t start_time, end_time;
336
- unsigned int elapsed_time, connect_timeout;
336
+ time_t start_time, end_time, elapsed_time, connect_timeout;
337
337
  VALUE rv;
338
338
  GET_CLIENT(self);
339
339
 
340
- args.host = NIL_P(host) ? NULL : StringValuePtr(host);
341
- args.unix_socket = NIL_P(socket) ? NULL : StringValuePtr(socket);
342
- args.port = NIL_P(port) ? 0 : NUM2INT(port);
343
- args.user = NIL_P(user) ? NULL : StringValuePtr(user);
344
- args.passwd = NIL_P(pass) ? NULL : StringValuePtr(pass);
345
- args.db = NIL_P(database) ? NULL : StringValuePtr(database);
346
- args.mysql = wrapper->client;
340
+ args.host = NIL_P(host) ? NULL : StringValueCStr(host);
341
+ args.unix_socket = NIL_P(socket) ? NULL : StringValueCStr(socket);
342
+ args.port = NIL_P(port) ? 0 : NUM2INT(port);
343
+ args.user = NIL_P(user) ? NULL : StringValueCStr(user);
344
+ args.passwd = NIL_P(pass) ? NULL : StringValueCStr(pass);
345
+ args.db = NIL_P(database) ? NULL : StringValueCStr(database);
346
+ args.mysql = wrapper->client;
347
347
  args.client_flag = NUM2ULONG(flags);
348
348
 
349
349
  if (wrapper->connect_timeout)
@@ -360,7 +360,7 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
360
360
  /* avoid an early timeout due to time truncating milliseconds off the start time */
361
361
  if (elapsed_time > 0)
362
362
  elapsed_time--;
363
- if (elapsed_time >= wrapper->connect_timeout)
363
+ if (elapsed_time >= (time_t)wrapper->connect_timeout)
364
364
  break;
365
365
  connect_timeout = wrapper->connect_timeout - elapsed_time;
366
366
  mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout);
@@ -381,10 +381,13 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
381
381
  }
382
382
 
383
383
  /*
384
- * Immediately disconnect from the server, normally the garbage collector
385
- * will disconnect automatically when a connection is no longer needed.
386
- * Explicitly closing this will free up server resources sooner than waiting
387
- * for the garbage collector.
384
+ * Terminate the connection; call this when the connection is no longer needed.
385
+ * The garbage collector can close the connection, but doing so emits an
386
+ * "Aborted connection" error on the server and increments the Aborted_clients
387
+ * status variable.
388
+ *
389
+ * @see http://dev.mysql.com/doc/en/communication-errors.html
390
+ * @return [void]
388
391
  */
389
392
  static VALUE rb_mysql_client_close(VALUE self) {
390
393
  GET_CLIENT(self);
@@ -434,10 +437,9 @@ static void *nogvl_read_query_result(void *ptr) {
434
437
  }
435
438
 
436
439
  static void *nogvl_do_result(void *ptr, char use_result) {
437
- mysql_client_wrapper *wrapper;
440
+ mysql_client_wrapper *wrapper = ptr;
438
441
  MYSQL_RES *result;
439
442
 
440
- wrapper = (mysql_client_wrapper *)ptr;
441
443
  if (use_result) {
442
444
  result = mysql_use_result(wrapper->client);
443
445
  } else {
@@ -446,7 +448,7 @@ static void *nogvl_do_result(void *ptr, char use_result) {
446
448
 
447
449
  /* once our result is stored off, this connection is
448
450
  ready for another command to be issued */
449
- wrapper->active_thread = Qnil;
451
+ MARK_CONN_INACTIVE(self);
450
452
 
451
453
  return result;
452
454
  }
@@ -499,9 +501,9 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
499
501
  }
500
502
 
501
503
  current = rb_hash_dup(rb_iv_get(self, "@current_query_options"));
502
- RB_GC_GUARD(current);
504
+ (void)RB_GC_GUARD(current);
503
505
  Check_Type(current, T_HASH);
504
- resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result);
506
+ resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil);
505
507
 
506
508
  return resultObj;
507
509
  }
@@ -515,7 +517,7 @@ struct async_query_args {
515
517
  static VALUE disconnect_and_raise(VALUE self, VALUE error) {
516
518
  GET_CLIENT(self);
517
519
 
518
- wrapper->active_thread = Qnil;
520
+ MARK_CONN_INACTIVE(self);
519
521
  wrapper->connected = 0;
520
522
 
521
523
  /* Invalidate the MySQL socket to prevent further communication.
@@ -527,19 +529,16 @@ static VALUE disconnect_and_raise(VALUE self, VALUE error) {
527
529
  }
528
530
 
529
531
  rb_exc_raise(error);
530
-
531
- return Qnil;
532
532
  }
533
533
 
534
534
  static VALUE do_query(void *args) {
535
- struct async_query_args *async_args;
535
+ struct async_query_args *async_args = args;
536
536
  struct timeval tv;
537
- struct timeval* tvp;
537
+ struct timeval *tvp;
538
538
  long int sec;
539
539
  int retval;
540
540
  VALUE read_timeout;
541
541
 
542
- async_args = (struct async_query_args *)args;
543
542
  read_timeout = rb_iv_get(async_args->self, "@read_timeout");
544
543
 
545
544
  tvp = NULL;
@@ -577,11 +576,9 @@ static VALUE do_query(void *args) {
577
576
  }
578
577
  #else
579
578
  static VALUE finish_and_mark_inactive(void *args) {
580
- VALUE self;
579
+ VALUE self = args;
581
580
  MYSQL_RES *result;
582
581
 
583
- self = (VALUE)args;
584
-
585
582
  GET_CLIENT(self);
586
583
 
587
584
  if (!NIL_P(wrapper->active_thread)) {
@@ -591,13 +588,31 @@ static VALUE finish_and_mark_inactive(void *args) {
591
588
  result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0);
592
589
  mysql_free_result(result);
593
590
 
594
- wrapper->active_thread = Qnil;
591
+ MARK_CONN_INACTIVE(self);
595
592
  }
596
593
 
597
594
  return Qnil;
598
595
  }
599
596
  #endif
600
597
 
598
+ void rb_mysql_client_set_active_thread(VALUE self) {
599
+ VALUE thread_current = rb_thread_current();
600
+ GET_CLIENT(self);
601
+
602
+ // see if this connection is still waiting on a result from a previous query
603
+ if (NIL_P(wrapper->active_thread)) {
604
+ // mark this connection active
605
+ wrapper->active_thread = thread_current;
606
+ } else if (wrapper->active_thread == thread_current) {
607
+ rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result");
608
+ } else {
609
+ VALUE inspect = rb_inspect(wrapper->active_thread);
610
+ const char *thr = StringValueCStr(inspect);
611
+
612
+ rb_raise(cMysql2Error, "This connection is in use by: %s", thr);
613
+ }
614
+ }
615
+
601
616
  /* call-seq:
602
617
  * client.abandon_results!
603
618
  *
@@ -632,74 +647,47 @@ static VALUE rb_mysql_client_abandon_results(VALUE self) {
632
647
  * client.query(sql, options = {})
633
648
  *
634
649
  * Query the database with +sql+, with optional +options+. For the possible
635
- * options, see @@default_query_options on the Mysql2::Client class.
650
+ * options, see default_query_options on the Mysql2::Client class.
636
651
  */
637
- static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
652
+ static VALUE rb_query(VALUE self, VALUE sql, VALUE current) {
638
653
  #ifndef _WIN32
639
654
  struct async_query_args async_args;
640
655
  #endif
641
656
  struct nogvl_send_query_args args;
642
- int async = 0;
643
- VALUE opts, current;
644
- VALUE thread_current = rb_thread_current();
645
- #ifdef HAVE_RUBY_ENCODING_H
646
- rb_encoding *conn_enc;
647
- #endif
648
657
  GET_CLIENT(self);
649
658
 
650
659
  REQUIRE_CONNECTED(wrapper);
651
660
  args.mysql = wrapper->client;
652
661
 
653
- current = rb_hash_dup(rb_iv_get(self, "@query_options"));
654
- RB_GC_GUARD(current);
662
+ (void)RB_GC_GUARD(current);
655
663
  Check_Type(current, T_HASH);
656
664
  rb_iv_set(self, "@current_query_options", current);
657
665
 
658
- if (rb_scan_args(argc, argv, "11", &args.sql, &opts) == 2) {
659
- rb_funcall(current, intern_merge_bang, 1, opts);
660
-
661
- if (rb_hash_aref(current, sym_async) == Qtrue) {
662
- async = 1;
663
- }
664
- }
665
-
666
- Check_Type(args.sql, T_STRING);
666
+ Check_Type(sql, T_STRING);
667
667
  #ifdef HAVE_RUBY_ENCODING_H
668
- conn_enc = rb_to_encoding(wrapper->encoding);
669
668
  /* ensure the string is in the encoding the connection is expecting */
670
- args.sql = rb_str_export_to_enc(args.sql, conn_enc);
669
+ args.sql = rb_str_export_to_enc(sql, rb_to_encoding(wrapper->encoding));
670
+ #else
671
+ args.sql = sql;
671
672
  #endif
672
- args.sql_ptr = StringValuePtr(args.sql);
673
+ args.sql_ptr = RSTRING_PTR(args.sql);
673
674
  args.sql_len = RSTRING_LEN(args.sql);
674
-
675
- /* see if this connection is still waiting on a result from a previous query */
676
- if (NIL_P(wrapper->active_thread)) {
677
- /* mark this connection active */
678
- wrapper->active_thread = thread_current;
679
- } else if (wrapper->active_thread == thread_current) {
680
- rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result");
681
- } else {
682
- VALUE inspect = rb_inspect(wrapper->active_thread);
683
- const char *thr = StringValueCStr(inspect);
684
-
685
- rb_raise(cMysql2Error, "This connection is in use by: %s", thr);
686
- RB_GC_GUARD(inspect);
687
- }
688
-
689
675
  args.wrapper = wrapper;
690
676
 
677
+ rb_mysql_client_set_active_thread(self);
678
+
691
679
  #ifndef _WIN32
692
680
  rb_rescue2(do_send_query, (VALUE)&args, disconnect_and_raise, self, rb_eException, (VALUE)0);
693
681
 
694
- if (!async) {
682
+ if (rb_hash_aref(current, sym_async) == Qtrue) {
683
+ return Qnil;
684
+ } else {
695
685
  async_args.fd = wrapper->client->net.fd;
696
686
  async_args.self = self;
697
687
 
698
688
  rb_rescue2(do_query, (VALUE)&async_args, disconnect_and_raise, self, rb_eException, (VALUE)0);
699
689
 
700
690
  return rb_mysql_client_async_result(self);
701
- } else {
702
- return Qnil;
703
691
  }
704
692
  #else
705
693
  do_send_query(&args);
@@ -736,9 +724,14 @@ static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) {
736
724
  oldLen = RSTRING_LEN(str);
737
725
  newStr = xmalloc(oldLen*2+1);
738
726
 
739
- newLen = mysql_real_escape_string(wrapper->client, (char *)newStr, StringValuePtr(str), oldLen);
727
+ newLen = mysql_real_escape_string(wrapper->client, (char *)newStr, RSTRING_PTR(str), oldLen);
740
728
  if (newLen == oldLen) {
741
729
  /* no need to return a new ruby string if nothing changed */
730
+ #ifdef HAVE_RUBY_ENCODING_H
731
+ if (default_internal_enc) {
732
+ str = rb_str_export_to_enc(str, default_internal_enc);
733
+ }
734
+ #endif
742
735
  xfree(newStr);
743
736
  return str;
744
737
  } else {
@@ -800,17 +793,17 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) {
800
793
  break;
801
794
 
802
795
  case MYSQL_READ_DEFAULT_FILE:
803
- charval = (const char *)StringValuePtr(value);
796
+ charval = (const char *)StringValueCStr(value);
804
797
  retval = charval;
805
798
  break;
806
799
 
807
800
  case MYSQL_READ_DEFAULT_GROUP:
808
- charval = (const char *)StringValuePtr(value);
801
+ charval = (const char *)StringValueCStr(value);
809
802
  retval = charval;
810
803
  break;
811
804
 
812
805
  case MYSQL_INIT_COMMAND:
813
- charval = (const char *)StringValuePtr(value);
806
+ charval = (const char *)StringValueCStr(value);
814
807
  retval = charval;
815
808
  break;
816
809
 
@@ -843,30 +836,23 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) {
843
836
  *
844
837
  * Returns a string that represents the client library version.
845
838
  */
846
- static VALUE rb_mysql_client_info(VALUE self) {
847
- VALUE version, client_info;
848
- #ifdef HAVE_RUBY_ENCODING_H
849
- rb_encoding *default_internal_enc;
850
- rb_encoding *conn_enc;
851
- GET_CLIENT(self);
852
- #endif
853
- version = rb_hash_new();
839
+ static VALUE rb_mysql_client_info(RB_MYSQL_UNUSED VALUE klass) {
840
+ VALUE version_info, version, header_version;
841
+ version_info = rb_hash_new();
854
842
 
855
- #ifdef HAVE_RUBY_ENCODING_H
856
- default_internal_enc = rb_default_internal_encoding();
857
- conn_enc = rb_to_encoding(wrapper->encoding);
858
- #endif
843
+ version = rb_str_new2(mysql_get_client_info());
844
+ header_version = rb_str_new2(MYSQL_LINK_VERSION);
859
845
 
860
- rb_hash_aset(version, sym_id, LONG2NUM(mysql_get_client_version()));
861
- client_info = rb_str_new2(mysql_get_client_info());
862
846
  #ifdef HAVE_RUBY_ENCODING_H
863
- rb_enc_associate(client_info, conn_enc);
864
- if (default_internal_enc) {
865
- client_info = rb_str_export_to_enc(client_info, default_internal_enc);
866
- }
847
+ rb_enc_associate(version, rb_usascii_encoding());
848
+ rb_enc_associate(header_version, rb_usascii_encoding());
867
849
  #endif
868
- rb_hash_aset(version, sym_version, client_info);
869
- return version;
850
+
851
+ rb_hash_aset(version_info, sym_id, LONG2NUM(mysql_get_client_version()));
852
+ rb_hash_aset(version_info, sym_version, version);
853
+ rb_hash_aset(version_info, sym_header_version, header_version);
854
+
855
+ return version_info;
870
856
  }
871
857
 
872
858
  /* call-seq:
@@ -906,19 +892,17 @@ static VALUE rb_mysql_client_server_info(VALUE self) {
906
892
  *
907
893
  * Return the file descriptor number for this client.
908
894
  */
895
+ #ifndef _WIN32
909
896
  static VALUE rb_mysql_client_socket(VALUE self) {
910
897
  GET_CLIENT(self);
911
- #ifndef _WIN32
912
- {
913
- int fd_set_fd;
914
- REQUIRE_CONNECTED(wrapper);
915
- fd_set_fd = wrapper->client->net.fd;
916
- return INT2NUM(fd_set_fd);
917
- }
898
+ REQUIRE_CONNECTED(wrapper);
899
+ return INT2NUM(wrapper->client->net.fd);
900
+ }
918
901
  #else
902
+ static VALUE rb_mysql_client_socket(RB_MYSQL_UNUSED VALUE self) {
919
903
  rb_raise(cMysql2Error, "Raw access to the mysql file descriptor isn't supported on Windows");
920
- #endif
921
904
  }
905
+ #endif
922
906
 
923
907
  /* call-seq:
924
908
  * client.last_id
@@ -987,7 +971,7 @@ static VALUE rb_mysql_client_select_db(VALUE self, VALUE db)
987
971
  REQUIRE_CONNECTED(wrapper);
988
972
 
989
973
  args.mysql = wrapper->client;
990
- args.db = StringValuePtr(db);
974
+ args.db = StringValueCStr(db);
991
975
 
992
976
  if (rb_thread_call_without_gvl(nogvl_select_db, &args, RUBY_UBF_IO, 0) == Qfalse)
993
977
  rb_raise_mysql2_error(wrapper);
@@ -1078,9 +1062,9 @@ static VALUE rb_mysql_client_store_result(VALUE self)
1078
1062
  }
1079
1063
 
1080
1064
  current = rb_hash_dup(rb_iv_get(self, "@current_query_options"));
1081
- RB_GC_GUARD(current);
1065
+ (void)RB_GC_GUARD(current);
1082
1066
  Check_Type(current, T_HASH);
1083
- resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result);
1067
+ resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil);
1084
1068
 
1085
1069
  return resultObj;
1086
1070
  }
@@ -1149,7 +1133,6 @@ static VALUE set_write_timeout(VALUE self, VALUE value) {
1149
1133
  static VALUE set_charset_name(VALUE self, VALUE value) {
1150
1134
  char *charset_name;
1151
1135
  #ifdef HAVE_RUBY_ENCODING_H
1152
- size_t charset_name_len;
1153
1136
  const struct mysql2_mysql_enc_name_to_rb_map *mysql2rb;
1154
1137
  rb_encoding *enc;
1155
1138
  VALUE rb_enc;
@@ -1159,8 +1142,7 @@ static VALUE set_charset_name(VALUE self, VALUE value) {
1159
1142
  charset_name = RSTRING_PTR(value);
1160
1143
 
1161
1144
  #ifdef HAVE_RUBY_ENCODING_H
1162
- charset_name_len = RSTRING_LEN(value);
1163
- mysql2rb = mysql2_mysql_enc_name_to_rb(charset_name, charset_name_len);
1145
+ mysql2rb = mysql2_mysql_enc_name_to_rb(charset_name, (unsigned int)RSTRING_LEN(value));
1164
1146
  if (mysql2rb == NULL || mysql2rb->rb_name == NULL) {
1165
1147
  VALUE inspect = rb_inspect(value);
1166
1148
  rb_raise(cMysql2Error, "Unsupported charset: '%s'", RSTRING_PTR(inspect));
@@ -1183,11 +1165,11 @@ static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE
1183
1165
  GET_CLIENT(self);
1184
1166
 
1185
1167
  mysql_ssl_set(wrapper->client,
1186
- NIL_P(key) ? NULL : StringValuePtr(key),
1187
- NIL_P(cert) ? NULL : StringValuePtr(cert),
1188
- NIL_P(ca) ? NULL : StringValuePtr(ca),
1189
- NIL_P(capath) ? NULL : StringValuePtr(capath),
1190
- NIL_P(cipher) ? NULL : StringValuePtr(cipher));
1168
+ NIL_P(key) ? NULL : StringValueCStr(key),
1169
+ NIL_P(cert) ? NULL : StringValueCStr(cert),
1170
+ NIL_P(ca) ? NULL : StringValueCStr(ca),
1171
+ NIL_P(capath) ? NULL : StringValueCStr(capath),
1172
+ NIL_P(cipher) ? NULL : StringValueCStr(cipher));
1191
1173
 
1192
1174
  return self;
1193
1175
  }
@@ -1220,7 +1202,19 @@ static VALUE initialize_ext(VALUE self) {
1220
1202
  return self;
1221
1203
  }
1222
1204
 
1205
+ /* call-seq: client.prepare # => Mysql2::Statement
1206
+ *
1207
+ * Create a new prepared statement.
1208
+ */
1209
+ static VALUE rb_mysql_client_prepare_statement(VALUE self, VALUE sql) {
1210
+ GET_CLIENT(self);
1211
+ REQUIRE_CONNECTED(wrapper);
1212
+
1213
+ return rb_mysql_stmt_new(self, sql);
1214
+ }
1215
+
1223
1216
  void init_mysql2_client() {
1217
+ #ifdef _WIN32
1224
1218
  /* verify the libmysql we're about to use was the version we were built against
1225
1219
  https://github.com/luislavena/mysql-gem/commit/a600a9c459597da0712f70f43736e24b484f8a99 */
1226
1220
  int i;
@@ -1235,15 +1229,14 @@ void init_mysql2_client() {
1235
1229
  }
1236
1230
  if (lib[i] != MYSQL_LINK_VERSION[i]) {
1237
1231
  rb_raise(rb_eRuntimeError, "Incorrect MySQL client library version! This gem was compiled for %s but the client library is %s.", MYSQL_LINK_VERSION, lib);
1238
- return;
1239
1232
  }
1240
1233
  }
1234
+ #endif
1241
1235
 
1242
1236
  /* Initializing mysql library, so different threads could call Client.new */
1243
1237
  /* without race condition in the library */
1244
1238
  if (mysql_library_init(0, NULL, NULL) != 0) {
1245
1239
  rb_raise(rb_eRuntimeError, "Could not initialize MySQL client library");
1246
- return;
1247
1240
  }
1248
1241
 
1249
1242
  #if 0
@@ -1254,17 +1247,17 @@ void init_mysql2_client() {
1254
1247
  rb_define_alloc_func(cMysql2Client, allocate);
1255
1248
 
1256
1249
  rb_define_singleton_method(cMysql2Client, "escape", rb_mysql_client_escape, 1);
1250
+ rb_define_singleton_method(cMysql2Client, "info", rb_mysql_client_info, 0);
1257
1251
 
1258
1252
  rb_define_method(cMysql2Client, "close", rb_mysql_client_close, 0);
1259
- rb_define_method(cMysql2Client, "query", rb_mysql_client_query, -1);
1260
1253
  rb_define_method(cMysql2Client, "abandon_results!", rb_mysql_client_abandon_results, 0);
1261
1254
  rb_define_method(cMysql2Client, "escape", rb_mysql_client_real_escape, 1);
1262
- rb_define_method(cMysql2Client, "info", rb_mysql_client_info, 0);
1263
1255
  rb_define_method(cMysql2Client, "server_info", rb_mysql_client_server_info, 0);
1264
1256
  rb_define_method(cMysql2Client, "socket", rb_mysql_client_socket, 0);
1265
1257
  rb_define_method(cMysql2Client, "async_result", rb_mysql_client_async_result, 0);
1266
1258
  rb_define_method(cMysql2Client, "last_id", rb_mysql_client_last_id, 0);
1267
1259
  rb_define_method(cMysql2Client, "affected_rows", rb_mysql_client_affected_rows, 0);
1260
+ rb_define_method(cMysql2Client, "prepare", rb_mysql_client_prepare_statement, 1);
1268
1261
  rb_define_method(cMysql2Client, "thread_id", rb_mysql_client_thread_id, 0);
1269
1262
  rb_define_method(cMysql2Client, "ping", rb_mysql_client_ping, 0);
1270
1263
  rb_define_method(cMysql2Client, "select_db", rb_mysql_client_select_db, 1);
@@ -1290,19 +1283,21 @@ void init_mysql2_client() {
1290
1283
  rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5);
1291
1284
  rb_define_private_method(cMysql2Client, "initialize_ext", initialize_ext, 0);
1292
1285
  rb_define_private_method(cMysql2Client, "connect", rb_connect, 7);
1286
+ rb_define_private_method(cMysql2Client, "_query", rb_query, 2);
1293
1287
 
1294
1288
  sym_id = ID2SYM(rb_intern("id"));
1295
1289
  sym_version = ID2SYM(rb_intern("version"));
1290
+ sym_header_version = ID2SYM(rb_intern("header_version"));
1296
1291
  sym_async = ID2SYM(rb_intern("async"));
1297
1292
  sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys"));
1298
1293
  sym_as = ID2SYM(rb_intern("as"));
1299
1294
  sym_array = ID2SYM(rb_intern("array"));
1300
1295
  sym_stream = ID2SYM(rb_intern("stream"));
1301
1296
 
1297
+ intern_brackets = rb_intern("[]");
1302
1298
  intern_merge = rb_intern("merge");
1303
1299
  intern_merge_bang = rb_intern("merge!");
1304
- intern_error_number_eql = rb_intern("error_number=");
1305
- intern_sql_state_eql = rb_intern("sql_state=");
1300
+ intern_new_with_args = rb_intern("new_with_args");
1306
1301
 
1307
1302
  #ifdef CLIENT_LONG_PASSWORD
1308
1303
  rb_const_set(cMysql2Client, rb_intern("LONG_PASSWORD"),