mysql2 0.3.18 → 0.4.4

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.
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 +69 -17
  5. data/examples/eventmachine.rb +1 -1
  6. data/examples/threaded.rb +4 -6
  7. data/ext/mysql2/client.c +230 -179
  8. data/ext/mysql2/client.h +18 -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 +509 -138
  16. data/ext/mysql2/result.h +12 -6
  17. data/ext/mysql2/statement.c +504 -0
  18. data/ext/mysql2/statement.h +19 -0
  19. data/lib/mysql2/client.rb +71 -25
  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 +456 -362
  29. data/spec/mysql2/error_spec.rb +37 -36
  30. data/spec/mysql2/result_spec.rb +222 -208
  31. data/spec/mysql2/statement_spec.rb +703 -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 +42 -47
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 && !wrapper->automatic_close) {
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
  }
@@ -260,6 +260,7 @@ static VALUE allocate(VALUE klass) {
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
262
  wrapper->active_thread = Qnil;
263
+ wrapper->automatic_close = 1;
263
264
  wrapper->server_version = 0;
264
265
  wrapper->reconnect_enabled = 0;
265
266
  wrapper->connect_timeout = 0;
@@ -267,6 +268,7 @@ static VALUE allocate(VALUE klass) {
267
268
  wrapper->initialized = 0; /* means that that the wrapper is initialized */
268
269
  wrapper->refcount = 1;
269
270
  wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL));
271
+
270
272
  return obj;
271
273
  }
272
274
 
@@ -287,7 +289,7 @@ static VALUE rb_mysql_client_escape(RB_MYSQL_UNUSED VALUE klass, VALUE str) {
287
289
  oldLen = RSTRING_LEN(str);
288
290
  newStr = xmalloc(oldLen*2+1);
289
291
 
290
- newLen = mysql_escape_string((char *)newStr, StringValuePtr(str), oldLen);
292
+ newLen = mysql_escape_string((char *)newStr, RSTRING_PTR(str), oldLen);
291
293
  if (newLen == oldLen) {
292
294
  /* no need to return a new ruby string if nothing changed */
293
295
  xfree(newStr);
@@ -330,20 +332,39 @@ static VALUE rb_mysql_info(VALUE self) {
330
332
  return rb_str;
331
333
  }
332
334
 
335
+ static VALUE rb_mysql_get_ssl_cipher(VALUE self)
336
+ {
337
+ const char *cipher;
338
+ VALUE rb_str;
339
+ GET_CLIENT(self);
340
+
341
+ cipher = mysql_get_ssl_cipher(wrapper->client);
342
+
343
+ if (cipher == NULL) {
344
+ return Qnil;
345
+ }
346
+
347
+ rb_str = rb_str_new2(cipher);
348
+ #ifdef HAVE_RUBY_ENCODING_H
349
+ rb_enc_associate(rb_str, rb_utf8_encoding());
350
+ #endif
351
+
352
+ return rb_str;
353
+ }
354
+
333
355
  static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags) {
334
356
  struct nogvl_connect_args args;
335
- time_t start_time, end_time;
336
- unsigned int elapsed_time, connect_timeout;
357
+ time_t start_time, end_time, elapsed_time, connect_timeout;
337
358
  VALUE rv;
338
359
  GET_CLIENT(self);
339
360
 
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;
361
+ args.host = NIL_P(host) ? NULL : StringValueCStr(host);
362
+ args.unix_socket = NIL_P(socket) ? NULL : StringValueCStr(socket);
363
+ args.port = NIL_P(port) ? 0 : NUM2INT(port);
364
+ args.user = NIL_P(user) ? NULL : StringValueCStr(user);
365
+ args.passwd = NIL_P(pass) ? NULL : StringValueCStr(pass);
366
+ args.db = NIL_P(database) ? NULL : StringValueCStr(database);
367
+ args.mysql = wrapper->client;
347
368
  args.client_flag = NUM2ULONG(flags);
348
369
 
349
370
  if (wrapper->connect_timeout)
@@ -360,7 +381,7 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
360
381
  /* avoid an early timeout due to time truncating milliseconds off the start time */
361
382
  if (elapsed_time > 0)
362
383
  elapsed_time--;
363
- if (elapsed_time >= wrapper->connect_timeout)
384
+ if (elapsed_time >= (time_t)wrapper->connect_timeout)
364
385
  break;
365
386
  connect_timeout = wrapper->connect_timeout - elapsed_time;
366
387
  mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout);
@@ -372,7 +393,7 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
372
393
  if (wrapper->connect_timeout)
373
394
  mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &wrapper->connect_timeout);
374
395
  if (rv == Qfalse)
375
- return rb_raise_mysql2_error(wrapper);
396
+ rb_raise_mysql2_error(wrapper);
376
397
  }
377
398
 
378
399
  wrapper->server_version = mysql_get_server_version(wrapper->client);
@@ -381,10 +402,12 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
381
402
  }
382
403
 
383
404
  /*
384
- * Immediately disconnect from the server, normally the garbage collector
405
+ * Immediately disconnect from the server; normally the garbage collector
385
406
  * will disconnect automatically when a connection is no longer needed.
386
407
  * Explicitly closing this will free up server resources sooner than waiting
387
408
  * for the garbage collector.
409
+ *
410
+ * @return [nil]
388
411
  */
389
412
  static VALUE rb_mysql_client_close(VALUE self) {
390
413
  GET_CLIENT(self);
@@ -415,8 +438,8 @@ static VALUE do_send_query(void *args) {
415
438
  mysql_client_wrapper *wrapper = query_args->wrapper;
416
439
  if ((VALUE)rb_thread_call_without_gvl(nogvl_send_query, args, RUBY_UBF_IO, 0) == Qfalse) {
417
440
  /* an error occurred, we're not active anymore */
418
- MARK_CONN_INACTIVE(self);
419
- return rb_raise_mysql2_error(wrapper);
441
+ wrapper->active_thread = Qnil;
442
+ rb_raise_mysql2_error(wrapper);
420
443
  }
421
444
  return Qnil;
422
445
  }
@@ -434,10 +457,9 @@ static void *nogvl_read_query_result(void *ptr) {
434
457
  }
435
458
 
436
459
  static void *nogvl_do_result(void *ptr, char use_result) {
437
- mysql_client_wrapper *wrapper;
460
+ mysql_client_wrapper *wrapper = ptr;
438
461
  MYSQL_RES *result;
439
462
 
440
- wrapper = (mysql_client_wrapper *)ptr;
441
463
  if (use_result) {
442
464
  result = mysql_use_result(wrapper->client);
443
465
  } else {
@@ -478,8 +500,8 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
478
500
  REQUIRE_CONNECTED(wrapper);
479
501
  if ((VALUE)rb_thread_call_without_gvl(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
480
502
  /* an error occurred, mark this connection inactive */
481
- MARK_CONN_INACTIVE(self);
482
- return rb_raise_mysql2_error(wrapper);
503
+ wrapper->active_thread = Qnil;
504
+ rb_raise_mysql2_error(wrapper);
483
505
  }
484
506
 
485
507
  is_streaming = rb_hash_aref(rb_iv_get(self, "@current_query_options"), sym_stream);
@@ -491,7 +513,7 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
491
513
 
492
514
  if (result == NULL) {
493
515
  if (mysql_errno(wrapper->client) != 0) {
494
- MARK_CONN_INACTIVE(self);
516
+ wrapper->active_thread = Qnil;
495
517
  rb_raise_mysql2_error(wrapper);
496
518
  }
497
519
  /* no data and no error, so query was not a SELECT */
@@ -499,9 +521,9 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
499
521
  }
500
522
 
501
523
  current = rb_hash_dup(rb_iv_get(self, "@current_query_options"));
502
- RB_GC_GUARD(current);
524
+ (void)RB_GC_GUARD(current);
503
525
  Check_Type(current, T_HASH);
504
- resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result);
526
+ resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil);
505
527
 
506
528
  return resultObj;
507
529
  }
@@ -527,19 +549,16 @@ static VALUE disconnect_and_raise(VALUE self, VALUE error) {
527
549
  }
528
550
 
529
551
  rb_exc_raise(error);
530
-
531
- return Qnil;
532
552
  }
533
553
 
534
554
  static VALUE do_query(void *args) {
535
- struct async_query_args *async_args;
555
+ struct async_query_args *async_args = args;
536
556
  struct timeval tv;
537
- struct timeval* tvp;
557
+ struct timeval *tvp;
538
558
  long int sec;
539
559
  int retval;
540
560
  VALUE read_timeout;
541
561
 
542
- async_args = (struct async_query_args *)args;
543
562
  read_timeout = rb_iv_get(async_args->self, "@read_timeout");
544
563
 
545
564
  tvp = NULL;
@@ -577,11 +596,9 @@ static VALUE do_query(void *args) {
577
596
  }
578
597
  #else
579
598
  static VALUE finish_and_mark_inactive(void *args) {
580
- VALUE self;
599
+ VALUE self = args;
581
600
  MYSQL_RES *result;
582
601
 
583
- self = (VALUE)args;
584
-
585
602
  GET_CLIENT(self);
586
603
 
587
604
  if (!NIL_P(wrapper->active_thread)) {
@@ -598,6 +615,24 @@ static VALUE finish_and_mark_inactive(void *args) {
598
615
  }
599
616
  #endif
600
617
 
618
+ void rb_mysql_client_set_active_thread(VALUE self) {
619
+ VALUE thread_current = rb_thread_current();
620
+ GET_CLIENT(self);
621
+
622
+ // see if this connection is still waiting on a result from a previous query
623
+ if (NIL_P(wrapper->active_thread)) {
624
+ // mark this connection active
625
+ wrapper->active_thread = thread_current;
626
+ } else if (wrapper->active_thread == thread_current) {
627
+ rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result");
628
+ } else {
629
+ VALUE inspect = rb_inspect(wrapper->active_thread);
630
+ const char *thr = StringValueCStr(inspect);
631
+
632
+ rb_raise(cMysql2Error, "This connection is in use by: %s", thr);
633
+ }
634
+ }
635
+
601
636
  /* call-seq:
602
637
  * client.abandon_results!
603
638
  *
@@ -632,74 +667,47 @@ static VALUE rb_mysql_client_abandon_results(VALUE self) {
632
667
  * client.query(sql, options = {})
633
668
  *
634
669
  * Query the database with +sql+, with optional +options+. For the possible
635
- * options, see @@default_query_options on the Mysql2::Client class.
670
+ * options, see default_query_options on the Mysql2::Client class.
636
671
  */
637
- static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
672
+ static VALUE rb_query(VALUE self, VALUE sql, VALUE current) {
638
673
  #ifndef _WIN32
639
674
  struct async_query_args async_args;
640
675
  #endif
641
676
  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
677
  GET_CLIENT(self);
649
678
 
650
679
  REQUIRE_CONNECTED(wrapper);
651
680
  args.mysql = wrapper->client;
652
681
 
653
- current = rb_hash_dup(rb_iv_get(self, "@query_options"));
654
- RB_GC_GUARD(current);
682
+ (void)RB_GC_GUARD(current);
655
683
  Check_Type(current, T_HASH);
656
684
  rb_iv_set(self, "@current_query_options", current);
657
685
 
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);
686
+ Check_Type(sql, T_STRING);
667
687
  #ifdef HAVE_RUBY_ENCODING_H
668
- conn_enc = rb_to_encoding(wrapper->encoding);
669
688
  /* ensure the string is in the encoding the connection is expecting */
670
- args.sql = rb_str_export_to_enc(args.sql, conn_enc);
689
+ args.sql = rb_str_export_to_enc(sql, rb_to_encoding(wrapper->encoding));
690
+ #else
691
+ args.sql = sql;
671
692
  #endif
672
- args.sql_ptr = StringValuePtr(args.sql);
693
+ args.sql_ptr = RSTRING_PTR(args.sql);
673
694
  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
695
  args.wrapper = wrapper;
690
696
 
697
+ rb_mysql_client_set_active_thread(self);
698
+
691
699
  #ifndef _WIN32
692
700
  rb_rescue2(do_send_query, (VALUE)&args, disconnect_and_raise, self, rb_eException, (VALUE)0);
693
701
 
694
- if (!async) {
702
+ if (rb_hash_aref(current, sym_async) == Qtrue) {
703
+ return Qnil;
704
+ } else {
695
705
  async_args.fd = wrapper->client->net.fd;
696
706
  async_args.self = self;
697
707
 
698
708
  rb_rescue2(do_query, (VALUE)&async_args, disconnect_and_raise, self, rb_eException, (VALUE)0);
699
709
 
700
710
  return rb_mysql_client_async_result(self);
701
- } else {
702
- return Qnil;
703
711
  }
704
712
  #else
705
713
  do_send_query(&args);
@@ -736,9 +744,14 @@ static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) {
736
744
  oldLen = RSTRING_LEN(str);
737
745
  newStr = xmalloc(oldLen*2+1);
738
746
 
739
- newLen = mysql_real_escape_string(wrapper->client, (char *)newStr, StringValuePtr(str), oldLen);
747
+ newLen = mysql_real_escape_string(wrapper->client, (char *)newStr, RSTRING_PTR(str), oldLen);
740
748
  if (newLen == oldLen) {
741
749
  /* no need to return a new ruby string if nothing changed */
750
+ #ifdef HAVE_RUBY_ENCODING_H
751
+ if (default_internal_enc) {
752
+ str = rb_str_export_to_enc(str, default_internal_enc);
753
+ }
754
+ #endif
742
755
  xfree(newStr);
743
756
  return str;
744
757
  } else {
@@ -800,17 +813,17 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) {
800
813
  break;
801
814
 
802
815
  case MYSQL_READ_DEFAULT_FILE:
803
- charval = (const char *)StringValuePtr(value);
816
+ charval = (const char *)StringValueCStr(value);
804
817
  retval = charval;
805
818
  break;
806
819
 
807
820
  case MYSQL_READ_DEFAULT_GROUP:
808
- charval = (const char *)StringValuePtr(value);
821
+ charval = (const char *)StringValueCStr(value);
809
822
  retval = charval;
810
823
  break;
811
824
 
812
825
  case MYSQL_INIT_COMMAND:
813
- charval = (const char *)StringValuePtr(value);
826
+ charval = (const char *)StringValueCStr(value);
814
827
  retval = charval;
815
828
  break;
816
829
 
@@ -843,30 +856,23 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) {
843
856
  *
844
857
  * Returns a string that represents the client library version.
845
858
  */
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();
859
+ static VALUE rb_mysql_client_info(RB_MYSQL_UNUSED VALUE klass) {
860
+ VALUE version_info, version, header_version;
861
+ version_info = rb_hash_new();
854
862
 
855
- #ifdef HAVE_RUBY_ENCODING_H
856
- default_internal_enc = rb_default_internal_encoding();
857
- conn_enc = rb_to_encoding(wrapper->encoding);
858
- #endif
863
+ version = rb_str_new2(mysql_get_client_info());
864
+ header_version = rb_str_new2(MYSQL_LINK_VERSION);
859
865
 
860
- rb_hash_aset(version, sym_id, LONG2NUM(mysql_get_client_version()));
861
- client_info = rb_str_new2(mysql_get_client_info());
862
866
  #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
- }
867
+ rb_enc_associate(version, rb_usascii_encoding());
868
+ rb_enc_associate(header_version, rb_usascii_encoding());
867
869
  #endif
868
- rb_hash_aset(version, sym_version, client_info);
869
- return version;
870
+
871
+ rb_hash_aset(version_info, sym_id, LONG2NUM(mysql_get_client_version()));
872
+ rb_hash_aset(version_info, sym_version, version);
873
+ rb_hash_aset(version_info, sym_header_version, header_version);
874
+
875
+ return version_info;
870
876
  }
871
877
 
872
878
  /* call-seq:
@@ -906,19 +912,17 @@ static VALUE rb_mysql_client_server_info(VALUE self) {
906
912
  *
907
913
  * Return the file descriptor number for this client.
908
914
  */
915
+ #ifndef _WIN32
909
916
  static VALUE rb_mysql_client_socket(VALUE self) {
910
917
  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
- }
918
+ REQUIRE_CONNECTED(wrapper);
919
+ return INT2NUM(wrapper->client->net.fd);
920
+ }
918
921
  #else
922
+ static VALUE rb_mysql_client_socket(RB_MYSQL_UNUSED VALUE self) {
919
923
  rb_raise(cMysql2Error, "Raw access to the mysql file descriptor isn't supported on Windows");
920
- #endif
921
924
  }
925
+ #endif
922
926
 
923
927
  /* call-seq:
924
928
  * client.last_id
@@ -987,7 +991,7 @@ static VALUE rb_mysql_client_select_db(VALUE self, VALUE db)
987
991
  REQUIRE_CONNECTED(wrapper);
988
992
 
989
993
  args.mysql = wrapper->client;
990
- args.db = StringValuePtr(db);
994
+ args.db = StringValueCStr(db);
991
995
 
992
996
  if (rb_thread_call_without_gvl(nogvl_select_db, &args, RUBY_UBF_IO, 0) == Qfalse)
993
997
  rb_raise_mysql2_error(wrapper);
@@ -1027,10 +1031,10 @@ static VALUE rb_mysql_client_ping(VALUE self) {
1027
1031
  static VALUE rb_mysql_client_more_results(VALUE self)
1028
1032
  {
1029
1033
  GET_CLIENT(self);
1030
- if (mysql_more_results(wrapper->client) == 0)
1031
- return Qfalse;
1032
- else
1033
- return Qtrue;
1034
+ if (mysql_more_results(wrapper->client) == 0)
1035
+ return Qfalse;
1036
+ else
1037
+ return Qtrue;
1034
1038
  }
1035
1039
 
1036
1040
  /* call-seq:
@@ -1078,9 +1082,9 @@ static VALUE rb_mysql_client_store_result(VALUE self)
1078
1082
  }
1079
1083
 
1080
1084
  current = rb_hash_dup(rb_iv_get(self, "@current_query_options"));
1081
- RB_GC_GUARD(current);
1085
+ (void)RB_GC_GUARD(current);
1082
1086
  Check_Type(current, T_HASH);
1083
- resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result);
1087
+ resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil);
1084
1088
 
1085
1089
  return resultObj;
1086
1090
  }
@@ -1097,6 +1101,39 @@ static VALUE rb_mysql_client_encoding(VALUE self) {
1097
1101
  }
1098
1102
  #endif
1099
1103
 
1104
+ /* call-seq:
1105
+ * client.automatic_close?
1106
+ *
1107
+ * @return [Boolean]
1108
+ */
1109
+ static VALUE get_automatic_close(VALUE self) {
1110
+ GET_CLIENT(self);
1111
+ return wrapper->automatic_close ? Qtrue : Qfalse;
1112
+ }
1113
+
1114
+ /* call-seq:
1115
+ * client.automatic_close = false
1116
+ *
1117
+ * Set this to +false+ to leave the connection open after it is garbage
1118
+ * collected. To avoid "Aborted connection" errors on the server, explicitly
1119
+ * call +close+ when the connection is no longer needed.
1120
+ *
1121
+ * @see http://dev.mysql.com/doc/en/communication-errors.html
1122
+ */
1123
+ static VALUE set_automatic_close(VALUE self, VALUE value) {
1124
+ GET_CLIENT(self);
1125
+ if (RTEST(value)) {
1126
+ wrapper->automatic_close = 1;
1127
+ } else {
1128
+ #ifndef _WIN32
1129
+ wrapper->automatic_close = 0;
1130
+ #else
1131
+ rb_warn("Connections are always closed by garbage collector on Windows");
1132
+ #endif
1133
+ }
1134
+ return value;
1135
+ }
1136
+
1100
1137
  /* call-seq:
1101
1138
  * client.reconnect = true
1102
1139
  *
@@ -1149,7 +1186,6 @@ static VALUE set_write_timeout(VALUE self, VALUE value) {
1149
1186
  static VALUE set_charset_name(VALUE self, VALUE value) {
1150
1187
  char *charset_name;
1151
1188
  #ifdef HAVE_RUBY_ENCODING_H
1152
- size_t charset_name_len;
1153
1189
  const struct mysql2_mysql_enc_name_to_rb_map *mysql2rb;
1154
1190
  rb_encoding *enc;
1155
1191
  VALUE rb_enc;
@@ -1159,8 +1195,7 @@ static VALUE set_charset_name(VALUE self, VALUE value) {
1159
1195
  charset_name = RSTRING_PTR(value);
1160
1196
 
1161
1197
  #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);
1198
+ mysql2rb = mysql2_mysql_enc_name_to_rb(charset_name, (unsigned int)RSTRING_LEN(value));
1164
1199
  if (mysql2rb == NULL || mysql2rb->rb_name == NULL) {
1165
1200
  VALUE inspect = rb_inspect(value);
1166
1201
  rb_raise(cMysql2Error, "Unsupported charset: '%s'", RSTRING_PTR(inspect));
@@ -1183,11 +1218,11 @@ static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE
1183
1218
  GET_CLIENT(self);
1184
1219
 
1185
1220
  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));
1221
+ NIL_P(key) ? NULL : StringValueCStr(key),
1222
+ NIL_P(cert) ? NULL : StringValueCStr(cert),
1223
+ NIL_P(ca) ? NULL : StringValueCStr(ca),
1224
+ NIL_P(capath) ? NULL : StringValueCStr(capath),
1225
+ NIL_P(cipher) ? NULL : StringValueCStr(cipher));
1191
1226
 
1192
1227
  return self;
1193
1228
  }
@@ -1213,14 +1248,26 @@ static VALUE initialize_ext(VALUE self) {
1213
1248
 
1214
1249
  if ((VALUE)rb_thread_call_without_gvl(nogvl_init, wrapper, RUBY_UBF_IO, 0) == Qfalse) {
1215
1250
  /* TODO: warning - not enough memory? */
1216
- return rb_raise_mysql2_error(wrapper);
1251
+ rb_raise_mysql2_error(wrapper);
1217
1252
  }
1218
1253
 
1219
1254
  wrapper->initialized = 1;
1220
1255
  return self;
1221
1256
  }
1222
1257
 
1258
+ /* call-seq: client.prepare # => Mysql2::Statement
1259
+ *
1260
+ * Create a new prepared statement.
1261
+ */
1262
+ static VALUE rb_mysql_client_prepare_statement(VALUE self, VALUE sql) {
1263
+ GET_CLIENT(self);
1264
+ REQUIRE_CONNECTED(wrapper);
1265
+
1266
+ return rb_mysql_stmt_new(self, sql);
1267
+ }
1268
+
1223
1269
  void init_mysql2_client() {
1270
+ #ifdef _WIN32
1224
1271
  /* verify the libmysql we're about to use was the version we were built against
1225
1272
  https://github.com/luislavena/mysql-gem/commit/a600a9c459597da0712f70f43736e24b484f8a99 */
1226
1273
  int i;
@@ -1235,15 +1282,14 @@ void init_mysql2_client() {
1235
1282
  }
1236
1283
  if (lib[i] != MYSQL_LINK_VERSION[i]) {
1237
1284
  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
1285
  }
1240
1286
  }
1287
+ #endif
1241
1288
 
1242
1289
  /* Initializing mysql library, so different threads could call Client.new */
1243
1290
  /* without race condition in the library */
1244
1291
  if (mysql_library_init(0, NULL, NULL) != 0) {
1245
1292
  rb_raise(rb_eRuntimeError, "Could not initialize MySQL client library");
1246
- return;
1247
1293
  }
1248
1294
 
1249
1295
  #if 0
@@ -1254,26 +1300,29 @@ void init_mysql2_client() {
1254
1300
  rb_define_alloc_func(cMysql2Client, allocate);
1255
1301
 
1256
1302
  rb_define_singleton_method(cMysql2Client, "escape", rb_mysql_client_escape, 1);
1303
+ rb_define_singleton_method(cMysql2Client, "info", rb_mysql_client_info, 0);
1257
1304
 
1258
1305
  rb_define_method(cMysql2Client, "close", rb_mysql_client_close, 0);
1259
- rb_define_method(cMysql2Client, "query", rb_mysql_client_query, -1);
1260
1306
  rb_define_method(cMysql2Client, "abandon_results!", rb_mysql_client_abandon_results, 0);
1261
1307
  rb_define_method(cMysql2Client, "escape", rb_mysql_client_real_escape, 1);
1262
- rb_define_method(cMysql2Client, "info", rb_mysql_client_info, 0);
1263
1308
  rb_define_method(cMysql2Client, "server_info", rb_mysql_client_server_info, 0);
1264
1309
  rb_define_method(cMysql2Client, "socket", rb_mysql_client_socket, 0);
1265
1310
  rb_define_method(cMysql2Client, "async_result", rb_mysql_client_async_result, 0);
1266
1311
  rb_define_method(cMysql2Client, "last_id", rb_mysql_client_last_id, 0);
1267
1312
  rb_define_method(cMysql2Client, "affected_rows", rb_mysql_client_affected_rows, 0);
1313
+ rb_define_method(cMysql2Client, "prepare", rb_mysql_client_prepare_statement, 1);
1268
1314
  rb_define_method(cMysql2Client, "thread_id", rb_mysql_client_thread_id, 0);
1269
1315
  rb_define_method(cMysql2Client, "ping", rb_mysql_client_ping, 0);
1270
1316
  rb_define_method(cMysql2Client, "select_db", rb_mysql_client_select_db, 1);
1271
1317
  rb_define_method(cMysql2Client, "more_results?", rb_mysql_client_more_results, 0);
1272
1318
  rb_define_method(cMysql2Client, "next_result", rb_mysql_client_next_result, 0);
1273
1319
  rb_define_method(cMysql2Client, "store_result", rb_mysql_client_store_result, 0);
1320
+ rb_define_method(cMysql2Client, "automatic_close?", get_automatic_close, 0);
1321
+ rb_define_method(cMysql2Client, "automatic_close=", set_automatic_close, 1);
1274
1322
  rb_define_method(cMysql2Client, "reconnect=", set_reconnect, 1);
1275
1323
  rb_define_method(cMysql2Client, "warning_count", rb_mysql_client_warning_count, 0);
1276
1324
  rb_define_method(cMysql2Client, "query_info_string", rb_mysql_info, 0);
1325
+ rb_define_method(cMysql2Client, "ssl_cipher", rb_mysql_get_ssl_cipher, 0);
1277
1326
  #ifdef HAVE_RUBY_ENCODING_H
1278
1327
  rb_define_method(cMysql2Client, "encoding", rb_mysql_client_encoding, 0);
1279
1328
  #endif
@@ -1290,19 +1339,21 @@ void init_mysql2_client() {
1290
1339
  rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5);
1291
1340
  rb_define_private_method(cMysql2Client, "initialize_ext", initialize_ext, 0);
1292
1341
  rb_define_private_method(cMysql2Client, "connect", rb_connect, 7);
1342
+ rb_define_private_method(cMysql2Client, "_query", rb_query, 2);
1293
1343
 
1294
1344
  sym_id = ID2SYM(rb_intern("id"));
1295
1345
  sym_version = ID2SYM(rb_intern("version"));
1346
+ sym_header_version = ID2SYM(rb_intern("header_version"));
1296
1347
  sym_async = ID2SYM(rb_intern("async"));
1297
1348
  sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys"));
1298
1349
  sym_as = ID2SYM(rb_intern("as"));
1299
1350
  sym_array = ID2SYM(rb_intern("array"));
1300
1351
  sym_stream = ID2SYM(rb_intern("stream"));
1301
1352
 
1353
+ intern_brackets = rb_intern("[]");
1302
1354
  intern_merge = rb_intern("merge");
1303
1355
  intern_merge_bang = rb_intern("merge!");
1304
- intern_error_number_eql = rb_intern("error_number=");
1305
- intern_sql_state_eql = rb_intern("sql_state=");
1356
+ intern_new_with_args = rb_intern("new_with_args");
1306
1357
 
1307
1358
  #ifdef CLIENT_LONG_PASSWORD
1308
1359
  rb_const_set(cMysql2Client, rb_intern("LONG_PASSWORD"),