mysql2 0.3.18 → 0.4.2

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