mysql2 0.4.10 → 0.5.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 (51) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +98 -38
  3. data/ext/mysql2/client.c +223 -76
  4. data/ext/mysql2/client.h +1 -39
  5. data/ext/mysql2/extconf.rb +46 -26
  6. data/ext/mysql2/mysql2_ext.c +8 -2
  7. data/ext/mysql2/mysql2_ext.h +8 -4
  8. data/ext/mysql2/mysql_enc_name_to_ruby.h +60 -56
  9. data/ext/mysql2/mysql_enc_to_ruby.h +64 -3
  10. data/ext/mysql2/result.c +242 -86
  11. data/ext/mysql2/result.h +3 -3
  12. data/ext/mysql2/statement.c +90 -73
  13. data/ext/mysql2/statement.h +0 -2
  14. data/ext/mysql2/wait_for_single_fd.h +2 -1
  15. data/lib/mysql2/client.rb +51 -28
  16. data/lib/mysql2/em.rb +2 -4
  17. data/lib/mysql2/error.rb +52 -22
  18. data/lib/mysql2/result.rb +2 -0
  19. data/lib/mysql2/statement.rb +3 -11
  20. data/lib/mysql2/version.rb +1 -1
  21. data/lib/mysql2.rb +18 -15
  22. data/support/3A79BD29.asc +49 -0
  23. data/support/5072E1F5.asc +5 -5
  24. data/support/mysql_enc_to_ruby.rb +8 -3
  25. data/support/ruby_enc_to_mysql.rb +7 -5
  26. metadata +14 -58
  27. data/examples/eventmachine.rb +0 -21
  28. data/examples/threaded.rb +0 -18
  29. data/spec/configuration.yml.example +0 -11
  30. data/spec/em/em_spec.rb +0 -136
  31. data/spec/my.cnf.example +0 -9
  32. data/spec/mysql2/client_spec.rb +0 -1039
  33. data/spec/mysql2/error_spec.rb +0 -82
  34. data/spec/mysql2/result_spec.rb +0 -545
  35. data/spec/mysql2/statement_spec.rb +0 -776
  36. data/spec/rcov.opts +0 -3
  37. data/spec/spec_helper.rb +0 -108
  38. data/spec/ssl/ca-cert.pem +0 -17
  39. data/spec/ssl/ca-key.pem +0 -27
  40. data/spec/ssl/ca.cnf +0 -22
  41. data/spec/ssl/cert.cnf +0 -22
  42. data/spec/ssl/client-cert.pem +0 -17
  43. data/spec/ssl/client-key.pem +0 -27
  44. data/spec/ssl/client-req.pem +0 -15
  45. data/spec/ssl/gen_certs.sh +0 -48
  46. data/spec/ssl/pkcs8-client-key.pem +0 -28
  47. data/spec/ssl/pkcs8-server-key.pem +0 -28
  48. data/spec/ssl/server-cert.pem +0 -17
  49. data/spec/ssl/server-key.pem +0 -27
  50. data/spec/ssl/server-req.pem +0 -15
  51. data/spec/test_data +0 -1
@@ -1,12 +1,10 @@
1
1
  #include <mysql2_ext.h>
2
2
 
3
- VALUE cMysql2Statement;
4
- extern VALUE mMysql2, cMysql2Error, cBigDecimal, cDateTime, cDate;
5
- static VALUE sym_stream, intern_new_with_args, intern_each, intern_to_s;
6
- static VALUE intern_sec_fraction, intern_usec, intern_sec, intern_min, intern_hour, intern_day, intern_month, intern_year;
7
- #ifndef HAVE_RB_BIG_CMP
8
- static ID id_cmp;
9
- #endif
3
+ extern VALUE mMysql2, cMysql2Error;
4
+ static VALUE cMysql2Statement, cBigDecimal, cDateTime, cDate;
5
+ static VALUE sym_stream, intern_new_with_args, intern_each, intern_to_s, intern_merge_bang;
6
+ static VALUE intern_sec_fraction, intern_usec, intern_sec, intern_min, intern_hour, intern_day, intern_month, intern_year,
7
+ intern_query_options;
10
8
 
11
9
  #define GET_STATEMENT(self) \
12
10
  mysql_stmt_wrapper *stmt_wrapper; \
@@ -21,7 +19,7 @@ static void rb_mysql_stmt_mark(void * ptr) {
21
19
  rb_gc_mark(stmt_wrapper->client);
22
20
  }
23
21
 
24
- static void *nogvl_stmt_close(void * ptr) {
22
+ static void *nogvl_stmt_close(void *ptr) {
25
23
  mysql_stmt_wrapper *stmt_wrapper = ptr;
26
24
  if (stmt_wrapper->stmt) {
27
25
  mysql_stmt_close(stmt_wrapper->stmt);
@@ -30,7 +28,7 @@ static void *nogvl_stmt_close(void * ptr) {
30
28
  return NULL;
31
29
  }
32
30
 
33
- static void rb_mysql_stmt_free(void * ptr) {
31
+ static void rb_mysql_stmt_free(void *ptr) {
34
32
  mysql_stmt_wrapper *stmt_wrapper = ptr;
35
33
  decr_mysql2_stmt(stmt_wrapper);
36
34
  }
@@ -48,9 +46,8 @@ void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) {
48
46
  VALUE e;
49
47
  GET_CLIENT(stmt_wrapper->client);
50
48
  VALUE rb_error_msg = rb_str_new2(mysql_stmt_error(stmt_wrapper->stmt));
51
- VALUE rb_sql_state = rb_tainted_str_new2(mysql_stmt_sqlstate(stmt_wrapper->stmt));
49
+ VALUE rb_sql_state = rb_str_new2(mysql_stmt_sqlstate(stmt_wrapper->stmt));
52
50
 
53
- #ifdef HAVE_RUBY_ENCODING_H
54
51
  rb_encoding *conn_enc;
55
52
  conn_enc = rb_to_encoding(wrapper->encoding);
56
53
 
@@ -62,7 +59,6 @@ void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) {
62
59
  rb_error_msg = rb_str_export_to_enc(rb_error_msg, default_internal_enc);
63
60
  rb_sql_state = rb_str_export_to_enc(rb_sql_state, default_internal_enc);
64
61
  }
65
- #endif
66
62
 
67
63
  e = rb_funcall(cMysql2Error, intern_new_with_args, 4,
68
64
  rb_error_msg,
@@ -96,9 +92,7 @@ static void *nogvl_prepare_statement(void *ptr) {
96
92
  VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
97
93
  mysql_stmt_wrapper *stmt_wrapper;
98
94
  VALUE rb_stmt;
99
- #ifdef HAVE_RUBY_ENCODING_H
100
95
  rb_encoding *conn_enc;
101
- #endif
102
96
 
103
97
  Check_Type(sql, T_STRING);
104
98
 
@@ -114,9 +108,7 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
114
108
  {
115
109
  GET_CLIENT(rb_client);
116
110
  stmt_wrapper->stmt = mysql_stmt_init(wrapper->client);
117
- #ifdef HAVE_RUBY_ENCODING_H
118
111
  conn_enc = rb_to_encoding(wrapper->encoding);
119
- #endif
120
112
  }
121
113
  if (stmt_wrapper->stmt == NULL) {
122
114
  rb_raise(cMysql2Error, "Unable to initialize prepared statement: out of memory");
@@ -124,7 +116,7 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
124
116
 
125
117
  // set STMT_ATTR_UPDATE_MAX_LENGTH attr
126
118
  {
127
- bool truth = 1;
119
+ my_bool truth = 1;
128
120
  if (mysql_stmt_attr_set(stmt_wrapper->stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &truth)) {
129
121
  rb_raise(cMysql2Error, "Unable to initialize prepared statement: set STMT_ATTR_UPDATE_MAX_LENGTH");
130
122
  }
@@ -134,11 +126,8 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
134
126
  {
135
127
  struct nogvl_prepare_statement_args args;
136
128
  args.stmt = stmt_wrapper->stmt;
137
- args.sql = sql;
138
- #ifdef HAVE_RUBY_ENCODING_H
139
129
  // ensure the string is in the encoding the connection is expecting
140
- args.sql = rb_str_export_to_enc(args.sql, conn_enc);
141
- #endif
130
+ args.sql = rb_str_export_to_enc(sql, conn_enc);
142
131
  args.sql_ptr = RSTRING_PTR(sql);
143
132
  args.sql_len = RSTRING_LEN(sql);
144
133
 
@@ -154,7 +143,7 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
154
143
  *
155
144
  * Returns the number of parameters the prepared statement expects.
156
145
  */
157
- static VALUE param_count(VALUE self) {
146
+ static VALUE rb_mysql_stmt_param_count(VALUE self) {
158
147
  GET_STATEMENT(self);
159
148
 
160
149
  return ULL2NUM(mysql_stmt_param_count(stmt_wrapper->stmt));
@@ -164,13 +153,13 @@ static VALUE param_count(VALUE self) {
164
153
  *
165
154
  * Returns the number of fields the prepared statement returns.
166
155
  */
167
- static VALUE field_count(VALUE self) {
156
+ static VALUE rb_mysql_stmt_field_count(VALUE self) {
168
157
  GET_STATEMENT(self);
169
158
 
170
159
  return UINT2NUM(mysql_stmt_field_count(stmt_wrapper->stmt));
171
160
  }
172
161
 
173
- static void *nogvl_execute(void *ptr) {
162
+ static void *nogvl_stmt_execute(void *ptr) {
174
163
  MYSQL_STMT *stmt = ptr;
175
164
 
176
165
  if (mysql_stmt_execute(stmt)) {
@@ -196,7 +185,7 @@ static void set_buffer_for_string(MYSQL_BIND* bind_buffer, unsigned long *length
196
185
  * the buffer is a Ruby string pointer and not our memory to manage.
197
186
  */
198
187
  #define FREE_BINDS \
199
- for (i = 0; i < argc; i++) { \
188
+ for (i = 0; i < bind_count; i++) { \
200
189
  if (bind_buffers[i].buffer && NIL_P(params_enc[i])) { \
201
190
  xfree(bind_buffers[i].buffer); \
202
191
  } \
@@ -211,6 +200,8 @@ static int my_big2ll(VALUE bignum, LONG_LONG *ptr)
211
200
  {
212
201
  unsigned LONG_LONG num;
213
202
  size_t len;
203
+ // rb_absint_size was added in 2.1.0. See:
204
+ // https://github.com/ruby/ruby/commit/9fea875
214
205
  #ifdef HAVE_RB_ABSINT_SIZE
215
206
  int nlz_bits = 0;
216
207
  len = rb_absint_size(bignum, &nlz_bits);
@@ -229,16 +220,15 @@ static int my_big2ll(VALUE bignum, LONG_LONG *ptr)
229
220
  #ifdef HAVE_RB_ABSINT_SIZE
230
221
  nlz_bits == 0 &&
231
222
  #endif
223
+ // rb_absint_singlebit_p was added in 2.1.0. See:
224
+ // https://github.com/ruby/ruby/commit/e5ff9d5
232
225
  #if defined(HAVE_RB_ABSINT_SIZE) && defined(HAVE_RB_ABSINT_SINGLEBIT_P)
233
226
  /* Optimized to avoid object allocation for Ruby 2.1+
234
227
  * only -0x8000000000000000 is safe if `len == 8 && nlz_bits == 0`
235
228
  */
236
229
  !rb_absint_singlebit_p(bignum)
237
- #elif defined(HAVE_RB_BIG_CMP)
238
- rb_big_cmp(bignum, LL2NUM(LLONG_MIN)) == INT2FIX(-1)
239
230
  #else
240
- /* Ruby 1.8.7 and REE doesn't have rb_big_cmp */
241
- rb_funcall(bignum, id_cmp, 1, LL2NUM(LLONG_MIN)) == INT2FIX(-1)
231
+ rb_big_cmp(bignum, LL2NUM(LLONG_MIN)) == INT2FIX(-1)
242
232
  #endif
243
233
  ) {
244
234
  goto overflow;
@@ -254,44 +244,45 @@ overflow:
254
244
  *
255
245
  * Executes the current prepared statement, returns +result+.
256
246
  */
257
- static VALUE execute(int argc, VALUE *argv, VALUE self) {
247
+ static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) {
258
248
  MYSQL_BIND *bind_buffers = NULL;
259
249
  unsigned long *length_buffers = NULL;
260
250
  unsigned long bind_count;
261
- long i;
251
+ unsigned long i;
262
252
  MYSQL_STMT *stmt;
263
253
  MYSQL_RES *metadata;
254
+ VALUE opts;
264
255
  VALUE current;
265
256
  VALUE resultObj;
266
- VALUE *params_enc;
257
+ VALUE *params_enc = NULL;
267
258
  int is_streaming;
268
- #ifdef HAVE_RUBY_ENCODING_H
269
259
  rb_encoding *conn_enc;
270
- #endif
271
260
 
272
261
  GET_STATEMENT(self);
273
262
  GET_CLIENT(stmt_wrapper->client);
274
263
 
275
- #ifdef HAVE_RUBY_ENCODING_H
276
264
  conn_enc = rb_to_encoding(wrapper->encoding);
277
- #endif
278
-
279
- /* Scratch space for string encoding exports, allocate on the stack. */
280
- params_enc = alloca(sizeof(VALUE) * argc);
281
265
 
282
266
  stmt = stmt_wrapper->stmt;
283
-
284
267
  bind_count = mysql_stmt_param_count(stmt);
285
- if (argc != (long)bind_count) {
286
- rb_raise(cMysql2Error, "Bind parameter count (%ld) doesn't match number of arguments (%d)", bind_count, argc);
268
+
269
+ // Get count of ordinary arguments, and extract hash opts/keyword arguments
270
+ // Use a local scope to avoid leaking the temporary count variable
271
+ {
272
+ int c = rb_scan_args(argc, argv, "*:", NULL, &opts);
273
+ if (c != (long)bind_count) {
274
+ rb_raise(cMysql2Error, "Bind parameter count (%ld) doesn't match number of arguments (%d)", bind_count, c);
275
+ }
287
276
  }
288
277
 
289
278
  // setup any bind variables in the query
290
279
  if (bind_count > 0) {
280
+ // Scratch space for string encoding exports, allocate on the stack
281
+ params_enc = alloca(sizeof(VALUE) * bind_count);
291
282
  bind_buffers = xcalloc(bind_count, sizeof(MYSQL_BIND));
292
283
  length_buffers = xcalloc(bind_count, sizeof(unsigned long));
293
284
 
294
- for (i = 0; i < argc; i++) {
285
+ for (i = 0; i < bind_count; i++) {
295
286
  bind_buffers[i].buffer = NULL;
296
287
  params_enc[i] = Qnil;
297
288
 
@@ -319,12 +310,8 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
319
310
  *(LONG_LONG*)(bind_buffers[i].buffer) = num;
320
311
  } else {
321
312
  /* The bignum was larger than we can fit in LONG_LONG, send it as a string */
322
- VALUE rb_val_as_string = rb_big2str(argv[i], 10);
323
313
  bind_buffers[i].buffer_type = MYSQL_TYPE_NEWDECIMAL;
324
- params_enc[i] = rb_val_as_string;
325
- #ifdef HAVE_RUBY_ENCODING_H
326
- params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
327
- #endif
314
+ params_enc[i] = rb_str_export_to_enc(rb_big2str(argv[i], 10), conn_enc);
328
315
  set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
329
316
  }
330
317
  }
@@ -338,9 +325,7 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
338
325
  bind_buffers[i].buffer_type = MYSQL_TYPE_STRING;
339
326
 
340
327
  params_enc[i] = argv[i];
341
- #ifdef HAVE_RUBY_ENCODING_H
342
328
  params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
343
- #endif
344
329
  set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
345
330
  break;
346
331
  case T_TRUE:
@@ -405,9 +390,7 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
405
390
  VALUE rb_val_as_string = rb_funcall(argv[i], intern_to_s, 0);
406
391
 
407
392
  params_enc[i] = rb_val_as_string;
408
- #ifdef HAVE_RUBY_ENCODING_H
409
393
  params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
410
- #endif
411
394
  set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
412
395
  }
413
396
  break;
@@ -421,7 +404,40 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
421
404
  }
422
405
  }
423
406
 
424
- if ((VALUE)rb_thread_call_without_gvl(nogvl_execute, stmt, RUBY_UBF_IO, 0) == Qfalse) {
407
+ // Duplicate the options hash, merge! extra opts, put the copy into the Result object
408
+ current = rb_hash_dup(rb_ivar_get(stmt_wrapper->client, intern_query_options));
409
+ (void)RB_GC_GUARD(current);
410
+ Check_Type(current, T_HASH);
411
+
412
+ // Merge in hash opts/keyword arguments
413
+ if (!NIL_P(opts)) {
414
+ rb_funcall(current, intern_merge_bang, 1, opts);
415
+ }
416
+
417
+ is_streaming = (Qtrue == rb_hash_aref(current, sym_stream));
418
+
419
+ // From stmt_execute to mysql_stmt_result_metadata to stmt_store_result, no
420
+ // Ruby API calls are allowed so that GC is not invoked. If the connection is
421
+ // in results-streaming-mode for Statement A, and in the middle Statement B
422
+ // gets garbage collected, a message will be sent to the server notifying it
423
+ // to release Statement B, resulting in the following error:
424
+ // Commands out of sync; you can't run this command now
425
+ //
426
+ // In streaming mode, statement execute must return a cursor because we
427
+ // cannot prevent other Statement objects from being garbage collected
428
+ // between fetches of each row of the result set. The following error
429
+ // occurs if cursor mode is not set:
430
+ // Row retrieval was canceled by mysql_stmt_close
431
+
432
+ if (is_streaming) {
433
+ unsigned long type = CURSOR_TYPE_READ_ONLY;
434
+ if (mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, &type)) {
435
+ FREE_BINDS;
436
+ rb_raise(cMysql2Error, "Unable to stream prepared statement, could not set CURSOR_TYPE_READ_ONLY");
437
+ }
438
+ }
439
+
440
+ if ((VALUE)rb_thread_call_without_gvl(nogvl_stmt_execute, stmt, RUBY_UBF_IO, 0) == Qfalse) {
425
441
  FREE_BINDS;
426
442
  rb_raise_mysql2_stmt_error(stmt_wrapper);
427
443
  }
@@ -439,13 +455,8 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
439
455
  return Qnil;
440
456
  }
441
457
 
442
- current = rb_hash_dup(rb_iv_get(stmt_wrapper->client, "@query_options"));
443
- (void)RB_GC_GUARD(current);
444
- Check_Type(current, T_HASH);
445
-
446
- is_streaming = (Qtrue == rb_hash_aref(current, sym_stream));
447
458
  if (!is_streaming) {
448
- // recieve the whole result set from the server
459
+ // receive the whole result set from the server
449
460
  if (mysql_stmt_store_result(stmt)) {
450
461
  mysql_free_result(metadata);
451
462
  rb_raise_mysql2_stmt_error(stmt_wrapper);
@@ -455,6 +466,8 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
455
466
 
456
467
  resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, metadata, self);
457
468
 
469
+ rb_mysql_set_server_query_flags(wrapper->client, resultObj);
470
+
458
471
  if (!is_streaming) {
459
472
  // cache all result
460
473
  rb_funcall(resultObj, intern_each, 0);
@@ -467,27 +480,23 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
467
480
  *
468
481
  * Returns a list of fields that will be returned by this statement.
469
482
  */
470
- static VALUE fields(VALUE self) {
483
+ static VALUE rb_mysql_stmt_fields(VALUE self) {
471
484
  MYSQL_FIELD *fields;
472
485
  MYSQL_RES *metadata;
473
486
  unsigned int field_count;
474
487
  unsigned int i;
475
488
  VALUE field_list;
476
489
  MYSQL_STMT* stmt;
477
- #ifdef HAVE_RUBY_ENCODING_H
478
490
  rb_encoding *default_internal_enc, *conn_enc;
479
- #endif
480
491
  GET_STATEMENT(self);
481
492
  GET_CLIENT(stmt_wrapper->client);
482
493
  stmt = stmt_wrapper->stmt;
483
494
 
484
- #ifdef HAVE_RUBY_ENCODING_H
485
495
  default_internal_enc = rb_default_internal_encoding();
486
496
  {
487
497
  GET_CLIENT(stmt_wrapper->client);
488
498
  conn_enc = rb_to_encoding(wrapper->encoding);
489
499
  }
490
- #endif
491
500
 
492
501
  metadata = mysql_stmt_result_metadata(stmt);
493
502
  if (metadata == NULL) {
@@ -508,12 +517,10 @@ static VALUE fields(VALUE self) {
508
517
  VALUE rb_field;
509
518
 
510
519
  rb_field = rb_str_new(fields[i].name, fields[i].name_length);
511
- #ifdef HAVE_RUBY_ENCODING_H
512
520
  rb_enc_associate(rb_field, conn_enc);
513
521
  if (default_internal_enc) {
514
522
  rb_field = rb_str_export_to_enc(rb_field, default_internal_enc);
515
523
  }
516
- #endif
517
524
 
518
525
  rb_ary_store(field_list, (long)i, rb_field);
519
526
  }
@@ -564,12 +571,23 @@ static VALUE rb_mysql_stmt_close(VALUE self) {
564
571
  }
565
572
 
566
573
  void init_mysql2_statement() {
574
+ cDate = rb_const_get(rb_cObject, rb_intern("Date"));
575
+ rb_global_variable(&cDate);
576
+
577
+ cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
578
+ rb_global_variable(&cDateTime);
579
+
580
+ cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal"));
581
+ rb_global_variable(&cBigDecimal);
582
+
567
583
  cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject);
584
+ rb_undef_alloc_func(cMysql2Statement);
585
+ rb_global_variable(&cMysql2Statement);
568
586
 
569
- rb_define_method(cMysql2Statement, "param_count", param_count, 0);
570
- rb_define_method(cMysql2Statement, "field_count", field_count, 0);
571
- rb_define_method(cMysql2Statement, "_execute", execute, -1);
572
- rb_define_method(cMysql2Statement, "fields", fields, 0);
587
+ rb_define_method(cMysql2Statement, "param_count", rb_mysql_stmt_param_count, 0);
588
+ rb_define_method(cMysql2Statement, "field_count", rb_mysql_stmt_field_count, 0);
589
+ rb_define_method(cMysql2Statement, "_execute", rb_mysql_stmt_execute, -1);
590
+ rb_define_method(cMysql2Statement, "fields", rb_mysql_stmt_fields, 0);
573
591
  rb_define_method(cMysql2Statement, "last_id", rb_mysql_stmt_last_id, 0);
574
592
  rb_define_method(cMysql2Statement, "affected_rows", rb_mysql_stmt_affected_rows, 0);
575
593
  rb_define_method(cMysql2Statement, "close", rb_mysql_stmt_close, 0);
@@ -589,7 +607,6 @@ void init_mysql2_statement() {
589
607
  intern_year = rb_intern("year");
590
608
 
591
609
  intern_to_s = rb_intern("to_s");
592
- #ifndef HAVE_RB_BIG_CMP
593
- id_cmp = rb_intern("<=>");
594
- #endif
610
+ intern_merge_bang = rb_intern("merge!");
611
+ intern_query_options = rb_intern("@query_options");
595
612
  }
@@ -1,8 +1,6 @@
1
1
  #ifndef MYSQL2_STATEMENT_H
2
2
  #define MYSQL2_STATEMENT_H
3
3
 
4
- extern VALUE cMysql2Statement;
5
-
6
4
  typedef struct {
7
5
  VALUE client;
8
6
  MYSQL_STMT *stmt;
@@ -1,5 +1,6 @@
1
1
  /*
2
- * backwards compatibility for pre-1.9.3 C API
2
+ * backwards compatibility for Rubinius. See
3
+ * https://github.com/rubinius/rubinius/issues/3771.
3
4
  *
4
5
  * Ruby 1.9.3 provides this API which allows the use of ppoll() on Linux
5
6
  * to minimize select() and malloc() overhead on high-numbered FDs.
data/lib/mysql2/client.rb CHANGED
@@ -4,22 +4,22 @@ module Mysql2
4
4
 
5
5
  def self.default_query_options
6
6
  @default_query_options ||= {
7
- :as => :hash, # the type of object you want each row back as; also supports :array (an array of values)
8
- :async => false, # don't wait for a result after sending the query, you'll have to monitor the socket yourself then eventually call Mysql2::Client#async_result
9
- :cast_booleans => false, # cast tinyint(1) fields as true/false in ruby
10
- :symbolize_keys => false, # return field names as symbols instead of strings
11
- :database_timezone => :local, # timezone Mysql2 will assume datetime objects are stored in
12
- :application_timezone => nil, # timezone Mysql2 will convert to before handing the object back to the caller
13
- :cache_rows => true, # tells Mysql2 to use its internal row cache for results
14
- :connect_flags => REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION,
15
- :cast => true,
16
- :default_file => nil,
17
- :default_group => nil,
7
+ as: :hash, # the type of object you want each row back as; also supports :array (an array of values)
8
+ async: false, # don't wait for a result after sending the query, you'll have to monitor the socket yourself then eventually call Mysql2::Client#async_result
9
+ cast_booleans: false, # cast tinyint(1) fields as true/false in ruby
10
+ symbolize_keys: false, # return field names as symbols instead of strings
11
+ database_timezone: :local, # timezone Mysql2 will assume datetime objects are stored in
12
+ application_timezone: nil, # timezone Mysql2 will convert to before handing the object back to the caller
13
+ cache_rows: true, # tells Mysql2 to use its internal row cache for results
14
+ connect_flags: REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION | CONNECT_ATTRS,
15
+ cast: true,
16
+ default_file: nil,
17
+ default_group: nil,
18
18
  }
19
19
  end
20
20
 
21
21
  def initialize(opts = {})
22
- fail Mysql2::Error, "Options parameter must be a Hash" unless opts.is_a? Hash
22
+ raise Mysql2::Error, "Options parameter must be a Hash" unless opts.is_a? Hash
23
23
  opts = Mysql2::Util.key_hash_as_symbols(opts)
24
24
  @read_timeout = nil
25
25
  @query_options = self.class.default_query_options.dup
@@ -31,7 +31,7 @@ module Mysql2
31
31
  opts[:connect_timeout] = 120 unless opts.key?(:connect_timeout)
32
32
 
33
33
  # TODO: stricter validation rather than silent massaging
34
- [:reconnect, :connect_timeout, :local_infile, :read_timeout, :write_timeout, :default_file, :default_group, :secure_auth, :init_command, :automatic_close, :enable_cleartext_plugin].each do |key|
34
+ %i[reconnect connect_timeout local_infile read_timeout write_timeout default_file default_group secure_auth init_command automatic_close enable_cleartext_plugin default_auth].each do |key|
35
35
  next unless opts.key?(key)
36
36
  case key
37
37
  when :reconnect, :local_infile, :secure_auth, :automatic_close, :enable_cleartext_plugin
@@ -46,25 +46,30 @@ module Mysql2
46
46
  # force the encoding to utf8
47
47
  self.charset_name = opts[:encoding] || 'utf8'
48
48
 
49
+ mode = parse_ssl_mode(opts[:ssl_mode]) if opts[:ssl_mode]
50
+ if (mode == SSL_MODE_VERIFY_CA || mode == SSL_MODE_VERIFY_IDENTITY) && !opts[:sslca]
51
+ opts[:sslca] = find_default_ca_path
52
+ end
53
+
49
54
  ssl_options = opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslcipher)
50
55
  ssl_set(*ssl_options) if ssl_options.any? || opts.key?(:sslverify)
51
- self.ssl_mode = parse_ssl_mode(opts[:ssl_mode]) if opts[:ssl_mode]
56
+ self.ssl_mode = mode if mode
52
57
 
53
- case opts[:flags]
58
+ flags = case opts[:flags]
54
59
  when Array
55
- flags = parse_flags_array(opts[:flags], @query_options[:connect_flags])
60
+ parse_flags_array(opts[:flags], @query_options[:connect_flags])
56
61
  when String
57
- flags = parse_flags_array(opts[:flags].split(' '), @query_options[:connect_flags])
62
+ parse_flags_array(opts[:flags].split(' '), @query_options[:connect_flags])
58
63
  when Integer
59
- flags = @query_options[:connect_flags] | opts[:flags]
64
+ @query_options[:connect_flags] | opts[:flags]
60
65
  else
61
- flags = @query_options[:connect_flags]
66
+ @query_options[:connect_flags]
62
67
  end
63
68
 
64
69
  # SSL verify is a connection flag rather than a mysql_ssl_set option
65
70
  flags |= SSL_VERIFY_SERVER_CERT if opts[:sslverify]
66
71
 
67
- if [:user, :pass, :hostname, :dbname, :db, :sock].any? { |k| @query_options.key?(k) }
72
+ if %i[user pass hostname dbname db sock].any? { |k| @query_options.key?(k) }
68
73
  warn "============= WARNING FROM mysql2 ============="
69
74
  warn "The options :user, :pass, :hostname, :dbname, :db, and :sock are deprecated and will be removed at some point in the future."
70
75
  warn "Instead, please use :username, :password, :host, :port, :database, :socket, :flags for the options."
@@ -85,8 +90,9 @@ module Mysql2
85
90
  port = port.to_i unless port.nil?
86
91
  database = database.to_s unless database.nil?
87
92
  socket = socket.to_s unless socket.nil?
93
+ conn_attrs = parse_connect_attrs(opts[:connect_attrs])
88
94
 
89
- connect user, pass, host, port, database, socket, flags
95
+ connect user, pass, host, port, database, socket, flags, conn_attrs
90
96
  end
91
97
 
92
98
  def parse_ssl_mode(mode)
@@ -114,14 +120,31 @@ module Mysql2
114
120
  end
115
121
  end
116
122
 
117
- if Thread.respond_to?(:handle_interrupt)
118
- def query(sql, options = {})
119
- Thread.handle_interrupt(::Mysql2::Util::TimeoutError => :never) do
120
- _query(sql, @query_options.merge(options))
121
- end
123
+ # Find any default system CA paths to handle system roots
124
+ # by default if stricter validation is requested and no
125
+ # path is provide.
126
+ def find_default_ca_path
127
+ [
128
+ "/etc/ssl/certs/ca-certificates.crt",
129
+ "/etc/pki/tls/certs/ca-bundle.crt",
130
+ "/etc/ssl/ca-bundle.pem",
131
+ "/etc/ssl/cert.pem",
132
+ ].find { |f| File.exist?(f) }
133
+ end
134
+
135
+ # Set default program_name in performance_schema.session_connect_attrs
136
+ # and performance_schema.session_account_connect_attrs
137
+ def parse_connect_attrs(conn_attrs)
138
+ return {} if Mysql2::Client::CONNECT_ATTRS.zero?
139
+ conn_attrs ||= {}
140
+ conn_attrs[:program_name] ||= $PROGRAM_NAME
141
+ conn_attrs.each_with_object({}) do |(key, value), hash|
142
+ hash[key.to_s] = value.to_s
122
143
  end
123
- else
124
- def query(sql, options = {})
144
+ end
145
+
146
+ def query(sql, options = {})
147
+ Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_NEVER) do
125
148
  _query(sql, @query_options.merge(options))
126
149
  end
127
150
  end
data/lib/mysql2/em.rb CHANGED
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  require 'eventmachine'
4
2
  require 'mysql2'
5
3
 
@@ -17,7 +15,7 @@ module Mysql2
17
15
  detach
18
16
  begin
19
17
  result = @client.async_result
20
- rescue => e
18
+ rescue StandardError => e
21
19
  @deferable.fail(e)
22
20
  else
23
21
  @deferable.succeed(result)
@@ -41,7 +39,7 @@ module Mysql2
41
39
 
42
40
  def query(sql, opts = {})
43
41
  if ::EM.reactor_running?
44
- super(sql, opts.merge(:async => true))
42
+ super(sql, opts.merge(async: true))
45
43
  deferable = ::EM::DefaultDeferrable.new
46
44
  @watch = ::EM.watch(socket, Watcher, self, deferable)
47
45
  @watch.notify_readable = true
data/lib/mysql2/error.rb CHANGED
@@ -1,32 +1,66 @@
1
- # encoding: UTF-8
2
-
3
1
  module Mysql2
4
2
  class Error < StandardError
5
3
  ENCODE_OPTS = {
6
- :undef => :replace,
7
- :invalid => :replace,
8
- :replace => '?'.freeze,
4
+ undef: :replace,
5
+ invalid: :replace,
6
+ replace: '?'.freeze,
7
+ }.freeze
8
+
9
+ ConnectionError = Class.new(Error)
10
+ TimeoutError = Class.new(Error)
11
+
12
+ CODES = {
13
+ 1205 => TimeoutError, # ER_LOCK_WAIT_TIMEOUT
14
+
15
+ 1044 => ConnectionError, # ER_DBACCESS_DENIED_ERROR
16
+ 1045 => ConnectionError, # ER_ACCESS_DENIED_ERROR
17
+ 1152 => ConnectionError, # ER_ABORTING_CONNECTION
18
+ 1153 => ConnectionError, # ER_NET_PACKET_TOO_LARGE
19
+ 1154 => ConnectionError, # ER_NET_READ_ERROR_FROM_PIPE
20
+ 1155 => ConnectionError, # ER_NET_FCNTL_ERROR
21
+ 1156 => ConnectionError, # ER_NET_PACKETS_OUT_OF_ORDER
22
+ 1157 => ConnectionError, # ER_NET_UNCOMPRESS_ERROR
23
+ 1158 => ConnectionError, # ER_NET_READ_ERROR
24
+ 1159 => ConnectionError, # ER_NET_READ_INTERRUPTED
25
+ 1160 => ConnectionError, # ER_NET_ERROR_ON_WRITE
26
+ 1161 => ConnectionError, # ER_NET_WRITE_INTERRUPTED
27
+ 1927 => ConnectionError, # ER_CONNECTION_KILLED
28
+
29
+ 2001 => ConnectionError, # CR_SOCKET_CREATE_ERROR
30
+ 2002 => ConnectionError, # CR_CONNECTION_ERROR
31
+ 2003 => ConnectionError, # CR_CONN_HOST_ERROR
32
+ 2004 => ConnectionError, # CR_IPSOCK_ERROR
33
+ 2005 => ConnectionError, # CR_UNKNOWN_HOST
34
+ 2006 => ConnectionError, # CR_SERVER_GONE_ERROR
35
+ 2007 => ConnectionError, # CR_VERSION_ERROR
36
+ 2009 => ConnectionError, # CR_WRONG_HOST_INFO
37
+ 2012 => ConnectionError, # CR_SERVER_HANDSHAKE_ERR
38
+ 2013 => ConnectionError, # CR_SERVER_LOST
39
+ 2020 => ConnectionError, # CR_NET_PACKET_TOO_LARGE
40
+ 2026 => ConnectionError, # CR_SSL_CONNECTION_ERROR
41
+ 2027 => ConnectionError, # CR_MALFORMED_PACKET
42
+ 2047 => ConnectionError, # CR_CONN_UNKNOW_PROTOCOL
43
+ 2048 => ConnectionError, # CR_INVALID_CONN_HANDLE
44
+ 2049 => ConnectionError, # CR_UNUSED_1
9
45
  }.freeze
10
46
 
11
47
  attr_reader :error_number, :sql_state
12
48
 
13
49
  # Mysql gem compatibility
14
- alias_method :errno, :error_number
15
- alias_method :error, :message
50
+ alias errno error_number
51
+ alias error message
16
52
 
17
- def initialize(msg)
18
- @server_version ||= nil
53
+ def initialize(msg, server_version = nil, error_number = nil, sql_state = nil)
54
+ @server_version = server_version
55
+ @error_number = error_number
56
+ @sql_state = sql_state ? sql_state.encode(**ENCODE_OPTS) : nil
19
57
 
20
58
  super(clean_message(msg))
21
59
  end
22
60
 
23
61
  def self.new_with_args(msg, server_version, error_number, sql_state)
24
- err = allocate
25
- err.instance_variable_set('@server_version', server_version)
26
- err.instance_variable_set('@error_number', error_number)
27
- err.instance_variable_set('@sql_state', sql_state.respond_to?(:encode) ? sql_state.encode(ENCODE_OPTS) : sql_state)
28
- err.send(:initialize, msg)
29
- err
62
+ error_class = CODES.fetch(error_number, self)
63
+ error_class.new(msg, server_version, error_number, sql_state)
30
64
  end
31
65
 
32
66
  private
@@ -55,16 +89,12 @@ module Mysql2
55
89
  # encoding, we'll assume UTF-8 and clean the string of anything that's not a
56
90
  # valid UTF-8 character.
57
91
  #
58
- # Except for if we're on 1.8, where we'll do nothing ;)
59
- #
60
- # Returns a valid UTF-8 string in Ruby 1.9+, the original string on Ruby 1.8
92
+ # Returns a valid UTF-8 string.
61
93
  def clean_message(message)
62
- return message unless message.respond_to?(:encode)
63
-
64
94
  if @server_version && @server_version > 50500
65
- message.encode(ENCODE_OPTS)
95
+ message.encode(**ENCODE_OPTS)
66
96
  else
67
- message.encode(Encoding::UTF_8, ENCODE_OPTS)
97
+ message.encode(Encoding::UTF_8, **ENCODE_OPTS)
68
98
  end
69
99
  end
70
100
  end
data/lib/mysql2/result.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  module Mysql2
2
2
  class Result
3
+ attr_reader :server_flags
4
+
3
5
  include Enumerable
4
6
  end
5
7
  end