mysql2 0.3.18 → 0.4.4

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 +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"),