mysql2 0.4.10 → 0.5.6

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 (52) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +167 -48
  3. data/ext/mysql2/client.c +335 -108
  4. data/ext/mysql2/client.h +10 -41
  5. data/ext/mysql2/extconf.rb +84 -26
  6. data/ext/mysql2/mysql2_ext.c +8 -2
  7. data/ext/mysql2/mysql2_ext.h +21 -4
  8. data/ext/mysql2/mysql_enc_name_to_ruby.h +60 -55
  9. data/ext/mysql2/mysql_enc_to_ruby.h +79 -3
  10. data/ext/mysql2/result.c +298 -92
  11. data/ext/mysql2/result.h +3 -3
  12. data/ext/mysql2/statement.c +137 -81
  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 +55 -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 +19 -15
  22. data/support/3A79BD29.asc +49 -0
  23. data/support/5072E1F5.asc +5 -5
  24. data/support/C74CD1D8.asc +104 -0
  25. data/support/mysql_enc_to_ruby.rb +9 -3
  26. data/support/ruby_enc_to_mysql.rb +8 -5
  27. metadata +19 -62
  28. data/examples/eventmachine.rb +0 -21
  29. data/examples/threaded.rb +0 -18
  30. data/spec/configuration.yml.example +0 -11
  31. data/spec/em/em_spec.rb +0 -136
  32. data/spec/my.cnf.example +0 -9
  33. data/spec/mysql2/client_spec.rb +0 -1039
  34. data/spec/mysql2/error_spec.rb +0 -82
  35. data/spec/mysql2/result_spec.rb +0 -545
  36. data/spec/mysql2/statement_spec.rb +0 -776
  37. data/spec/rcov.opts +0 -3
  38. data/spec/spec_helper.rb +0 -108
  39. data/spec/ssl/ca-cert.pem +0 -17
  40. data/spec/ssl/ca-key.pem +0 -27
  41. data/spec/ssl/ca.cnf +0 -22
  42. data/spec/ssl/cert.cnf +0 -22
  43. data/spec/ssl/client-cert.pem +0 -17
  44. data/spec/ssl/client-key.pem +0 -27
  45. data/spec/ssl/client-req.pem +0 -15
  46. data/spec/ssl/gen_certs.sh +0 -48
  47. data/spec/ssl/pkcs8-client-key.pem +0 -28
  48. data/spec/ssl/pkcs8-server-key.pem +0 -28
  49. data/spec/ssl/server-cert.pem +0 -17
  50. data/spec/ssl/server-key.pem +0 -27
  51. data/spec/ssl/server-req.pem +0 -15
  52. data/spec/test_data +0 -1
@@ -1,16 +1,18 @@
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;
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;
8
+
9
+ #ifndef NEW_TYPEDDATA_WRAPPER
10
+ #define TypedData_Get_Struct(obj, type, ignore, sval) Data_Get_Struct(obj, type, sval)
9
11
  #endif
10
12
 
11
13
  #define GET_STATEMENT(self) \
12
14
  mysql_stmt_wrapper *stmt_wrapper; \
13
- Data_Get_Struct(self, mysql_stmt_wrapper, stmt_wrapper); \
15
+ TypedData_Get_Struct(self, mysql_stmt_wrapper, &rb_mysql_statement_type, stmt_wrapper); \
14
16
  if (!stmt_wrapper->stmt) { rb_raise(cMysql2Error, "Invalid statement handle"); } \
15
17
  if (stmt_wrapper->closed) { rb_raise(cMysql2Error, "Statement handle already closed"); }
16
18
 
@@ -18,10 +20,46 @@ static void rb_mysql_stmt_mark(void * ptr) {
18
20
  mysql_stmt_wrapper *stmt_wrapper = ptr;
19
21
  if (!stmt_wrapper) return;
20
22
 
21
- rb_gc_mark(stmt_wrapper->client);
23
+ rb_gc_mark_movable(stmt_wrapper->client);
22
24
  }
23
25
 
24
- static void *nogvl_stmt_close(void * ptr) {
26
+ static void rb_mysql_stmt_free(void *ptr) {
27
+ mysql_stmt_wrapper *stmt_wrapper = ptr;
28
+ decr_mysql2_stmt(stmt_wrapper);
29
+ }
30
+
31
+ static size_t rb_mysql_stmt_memsize(const void * ptr) {
32
+ const mysql_stmt_wrapper *stmt_wrapper = ptr;
33
+ return sizeof(*stmt_wrapper);
34
+ }
35
+
36
+ #ifdef HAVE_RB_GC_MARK_MOVABLE
37
+ static void rb_mysql_stmt_compact(void * ptr) {
38
+ mysql_stmt_wrapper *stmt_wrapper = ptr;
39
+ if (!stmt_wrapper) return;
40
+
41
+ rb_mysql2_gc_location(stmt_wrapper->client);
42
+ }
43
+ #endif
44
+
45
+ static const rb_data_type_t rb_mysql_statement_type = {
46
+ "rb_mysql_statement",
47
+ {
48
+ rb_mysql_stmt_mark,
49
+ rb_mysql_stmt_free,
50
+ rb_mysql_stmt_memsize,
51
+ #ifdef HAVE_RB_GC_MARK_MOVABLE
52
+ rb_mysql_stmt_compact,
53
+ #endif
54
+ },
55
+ 0,
56
+ 0,
57
+ #ifdef RUBY_TYPED_FREE_IMMEDIATELY
58
+ RUBY_TYPED_FREE_IMMEDIATELY,
59
+ #endif
60
+ };
61
+
62
+ static void *nogvl_stmt_close(void *ptr) {
25
63
  mysql_stmt_wrapper *stmt_wrapper = ptr;
26
64
  if (stmt_wrapper->stmt) {
27
65
  mysql_stmt_close(stmt_wrapper->stmt);
@@ -30,11 +68,6 @@ static void *nogvl_stmt_close(void * ptr) {
30
68
  return NULL;
31
69
  }
32
70
 
33
- static void rb_mysql_stmt_free(void * ptr) {
34
- mysql_stmt_wrapper *stmt_wrapper = ptr;
35
- decr_mysql2_stmt(stmt_wrapper);
36
- }
37
-
38
71
  void decr_mysql2_stmt(mysql_stmt_wrapper *stmt_wrapper) {
39
72
  stmt_wrapper->refcount--;
40
73
 
@@ -48,9 +81,8 @@ void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) {
48
81
  VALUE e;
49
82
  GET_CLIENT(stmt_wrapper->client);
50
83
  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));
84
+ VALUE rb_sql_state = rb_str_new2(mysql_stmt_sqlstate(stmt_wrapper->stmt));
52
85
 
53
- #ifdef HAVE_RUBY_ENCODING_H
54
86
  rb_encoding *conn_enc;
55
87
  conn_enc = rb_to_encoding(wrapper->encoding);
56
88
 
@@ -62,7 +94,6 @@ void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) {
62
94
  rb_error_msg = rb_str_export_to_enc(rb_error_msg, default_internal_enc);
63
95
  rb_sql_state = rb_str_export_to_enc(rb_sql_state, default_internal_enc);
64
96
  }
65
- #endif
66
97
 
67
98
  e = rb_funcall(cMysql2Error, intern_new_with_args, 4,
68
99
  rb_error_msg,
@@ -96,13 +127,15 @@ static void *nogvl_prepare_statement(void *ptr) {
96
127
  VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
97
128
  mysql_stmt_wrapper *stmt_wrapper;
98
129
  VALUE rb_stmt;
99
- #ifdef HAVE_RUBY_ENCODING_H
100
130
  rb_encoding *conn_enc;
101
- #endif
102
131
 
103
132
  Check_Type(sql, T_STRING);
104
133
 
134
+ #ifdef NEW_TYPEDDATA_WRAPPER
135
+ rb_stmt = TypedData_Make_Struct(cMysql2Statement, mysql_stmt_wrapper, &rb_mysql_statement_type, stmt_wrapper);
136
+ #else
105
137
  rb_stmt = Data_Make_Struct(cMysql2Statement, mysql_stmt_wrapper, rb_mysql_stmt_mark, rb_mysql_stmt_free, stmt_wrapper);
138
+ #endif
106
139
  {
107
140
  stmt_wrapper->client = rb_client;
108
141
  stmt_wrapper->refcount = 1;
@@ -114,9 +147,7 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
114
147
  {
115
148
  GET_CLIENT(rb_client);
116
149
  stmt_wrapper->stmt = mysql_stmt_init(wrapper->client);
117
- #ifdef HAVE_RUBY_ENCODING_H
118
150
  conn_enc = rb_to_encoding(wrapper->encoding);
119
- #endif
120
151
  }
121
152
  if (stmt_wrapper->stmt == NULL) {
122
153
  rb_raise(cMysql2Error, "Unable to initialize prepared statement: out of memory");
@@ -124,7 +155,7 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
124
155
 
125
156
  // set STMT_ATTR_UPDATE_MAX_LENGTH attr
126
157
  {
127
- bool truth = 1;
158
+ my_bool truth = 1;
128
159
  if (mysql_stmt_attr_set(stmt_wrapper->stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &truth)) {
129
160
  rb_raise(cMysql2Error, "Unable to initialize prepared statement: set STMT_ATTR_UPDATE_MAX_LENGTH");
130
161
  }
@@ -134,11 +165,8 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
134
165
  {
135
166
  struct nogvl_prepare_statement_args args;
136
167
  args.stmt = stmt_wrapper->stmt;
137
- args.sql = sql;
138
- #ifdef HAVE_RUBY_ENCODING_H
139
168
  // 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
169
+ args.sql = rb_str_export_to_enc(sql, conn_enc);
142
170
  args.sql_ptr = RSTRING_PTR(sql);
143
171
  args.sql_len = RSTRING_LEN(sql);
144
172
 
@@ -154,7 +182,7 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
154
182
  *
155
183
  * Returns the number of parameters the prepared statement expects.
156
184
  */
157
- static VALUE param_count(VALUE self) {
185
+ static VALUE rb_mysql_stmt_param_count(VALUE self) {
158
186
  GET_STATEMENT(self);
159
187
 
160
188
  return ULL2NUM(mysql_stmt_param_count(stmt_wrapper->stmt));
@@ -164,13 +192,13 @@ static VALUE param_count(VALUE self) {
164
192
  *
165
193
  * Returns the number of fields the prepared statement returns.
166
194
  */
167
- static VALUE field_count(VALUE self) {
195
+ static VALUE rb_mysql_stmt_field_count(VALUE self) {
168
196
  GET_STATEMENT(self);
169
197
 
170
198
  return UINT2NUM(mysql_stmt_field_count(stmt_wrapper->stmt));
171
199
  }
172
200
 
173
- static void *nogvl_execute(void *ptr) {
201
+ static void *nogvl_stmt_execute(void *ptr) {
174
202
  MYSQL_STMT *stmt = ptr;
175
203
 
176
204
  if (mysql_stmt_execute(stmt)) {
@@ -196,7 +224,7 @@ static void set_buffer_for_string(MYSQL_BIND* bind_buffer, unsigned long *length
196
224
  * the buffer is a Ruby string pointer and not our memory to manage.
197
225
  */
198
226
  #define FREE_BINDS \
199
- for (i = 0; i < argc; i++) { \
227
+ for (i = 0; i < bind_count; i++) { \
200
228
  if (bind_buffers[i].buffer && NIL_P(params_enc[i])) { \
201
229
  xfree(bind_buffers[i].buffer); \
202
230
  } \
@@ -211,6 +239,8 @@ static int my_big2ll(VALUE bignum, LONG_LONG *ptr)
211
239
  {
212
240
  unsigned LONG_LONG num;
213
241
  size_t len;
242
+ // rb_absint_size was added in 2.1.0. See:
243
+ // https://github.com/ruby/ruby/commit/9fea875
214
244
  #ifdef HAVE_RB_ABSINT_SIZE
215
245
  int nlz_bits = 0;
216
246
  len = rb_absint_size(bignum, &nlz_bits);
@@ -229,16 +259,15 @@ static int my_big2ll(VALUE bignum, LONG_LONG *ptr)
229
259
  #ifdef HAVE_RB_ABSINT_SIZE
230
260
  nlz_bits == 0 &&
231
261
  #endif
262
+ // rb_absint_singlebit_p was added in 2.1.0. See:
263
+ // https://github.com/ruby/ruby/commit/e5ff9d5
232
264
  #if defined(HAVE_RB_ABSINT_SIZE) && defined(HAVE_RB_ABSINT_SINGLEBIT_P)
233
265
  /* Optimized to avoid object allocation for Ruby 2.1+
234
266
  * only -0x8000000000000000 is safe if `len == 8 && nlz_bits == 0`
235
267
  */
236
268
  !rb_absint_singlebit_p(bignum)
237
- #elif defined(HAVE_RB_BIG_CMP)
238
- rb_big_cmp(bignum, LL2NUM(LLONG_MIN)) == INT2FIX(-1)
239
269
  #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)
270
+ rb_big_cmp(bignum, LL2NUM(LLONG_MIN)) == INT2FIX(-1)
242
271
  #endif
243
272
  ) {
244
273
  goto overflow;
@@ -254,44 +283,45 @@ overflow:
254
283
  *
255
284
  * Executes the current prepared statement, returns +result+.
256
285
  */
257
- static VALUE execute(int argc, VALUE *argv, VALUE self) {
286
+ static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) {
258
287
  MYSQL_BIND *bind_buffers = NULL;
259
288
  unsigned long *length_buffers = NULL;
260
289
  unsigned long bind_count;
261
- long i;
290
+ unsigned long i;
262
291
  MYSQL_STMT *stmt;
263
292
  MYSQL_RES *metadata;
293
+ VALUE opts;
264
294
  VALUE current;
265
295
  VALUE resultObj;
266
- VALUE *params_enc;
296
+ VALUE *params_enc = NULL;
267
297
  int is_streaming;
268
- #ifdef HAVE_RUBY_ENCODING_H
269
298
  rb_encoding *conn_enc;
270
- #endif
271
299
 
272
300
  GET_STATEMENT(self);
273
301
  GET_CLIENT(stmt_wrapper->client);
274
302
 
275
- #ifdef HAVE_RUBY_ENCODING_H
276
303
  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
304
 
282
305
  stmt = stmt_wrapper->stmt;
283
-
284
306
  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);
307
+
308
+ // Get count of ordinary arguments, and extract hash opts/keyword arguments
309
+ // Use a local scope to avoid leaking the temporary count variable
310
+ {
311
+ int c = rb_scan_args(argc, argv, "*:", NULL, &opts);
312
+ if (c != (long)bind_count) {
313
+ rb_raise(cMysql2Error, "Bind parameter count (%ld) doesn't match number of arguments (%d)", bind_count, c);
314
+ }
287
315
  }
288
316
 
289
317
  // setup any bind variables in the query
290
318
  if (bind_count > 0) {
319
+ // Scratch space for string encoding exports, allocate on the stack
320
+ params_enc = alloca(sizeof(VALUE) * bind_count);
291
321
  bind_buffers = xcalloc(bind_count, sizeof(MYSQL_BIND));
292
322
  length_buffers = xcalloc(bind_count, sizeof(unsigned long));
293
323
 
294
- for (i = 0; i < argc; i++) {
324
+ for (i = 0; i < bind_count; i++) {
295
325
  bind_buffers[i].buffer = NULL;
296
326
  params_enc[i] = Qnil;
297
327
 
@@ -319,12 +349,8 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
319
349
  *(LONG_LONG*)(bind_buffers[i].buffer) = num;
320
350
  } else {
321
351
  /* 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
352
  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
353
+ params_enc[i] = rb_str_export_to_enc(rb_big2str(argv[i], 10), conn_enc);
328
354
  set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
329
355
  }
330
356
  }
@@ -338,9 +364,7 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
338
364
  bind_buffers[i].buffer_type = MYSQL_TYPE_STRING;
339
365
 
340
366
  params_enc[i] = argv[i];
341
- #ifdef HAVE_RUBY_ENCODING_H
342
367
  params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
343
- #endif
344
368
  set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
345
369
  break;
346
370
  case T_TRUE:
@@ -405,9 +429,7 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
405
429
  VALUE rb_val_as_string = rb_funcall(argv[i], intern_to_s, 0);
406
430
 
407
431
  params_enc[i] = rb_val_as_string;
408
- #ifdef HAVE_RUBY_ENCODING_H
409
432
  params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
410
- #endif
411
433
  set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
412
434
  }
413
435
  break;
@@ -421,7 +443,40 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
421
443
  }
422
444
  }
423
445
 
424
- if ((VALUE)rb_thread_call_without_gvl(nogvl_execute, stmt, RUBY_UBF_IO, 0) == Qfalse) {
446
+ // Duplicate the options hash, merge! extra opts, put the copy into the Result object
447
+ current = rb_hash_dup(rb_ivar_get(stmt_wrapper->client, intern_query_options));
448
+ (void)RB_GC_GUARD(current);
449
+ Check_Type(current, T_HASH);
450
+
451
+ // Merge in hash opts/keyword arguments
452
+ if (!NIL_P(opts)) {
453
+ rb_funcall(current, intern_merge_bang, 1, opts);
454
+ }
455
+
456
+ is_streaming = (Qtrue == rb_hash_aref(current, sym_stream));
457
+
458
+ // From stmt_execute to mysql_stmt_result_metadata to stmt_store_result, no
459
+ // Ruby API calls are allowed so that GC is not invoked. If the connection is
460
+ // in results-streaming-mode for Statement A, and in the middle Statement B
461
+ // gets garbage collected, a message will be sent to the server notifying it
462
+ // to release Statement B, resulting in the following error:
463
+ // Commands out of sync; you can't run this command now
464
+ //
465
+ // In streaming mode, statement execute must return a cursor because we
466
+ // cannot prevent other Statement objects from being garbage collected
467
+ // between fetches of each row of the result set. The following error
468
+ // occurs if cursor mode is not set:
469
+ // Row retrieval was canceled by mysql_stmt_close
470
+
471
+ if (is_streaming) {
472
+ unsigned long type = CURSOR_TYPE_READ_ONLY;
473
+ if (mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, &type)) {
474
+ FREE_BINDS;
475
+ rb_raise(cMysql2Error, "Unable to stream prepared statement, could not set CURSOR_TYPE_READ_ONLY");
476
+ }
477
+ }
478
+
479
+ if ((VALUE)rb_thread_call_without_gvl(nogvl_stmt_execute, stmt, RUBY_UBF_IO, 0) == Qfalse) {
425
480
  FREE_BINDS;
426
481
  rb_raise_mysql2_stmt_error(stmt_wrapper);
427
482
  }
@@ -432,29 +487,26 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
432
487
  if (metadata == NULL) {
433
488
  if (mysql_stmt_errno(stmt) != 0) {
434
489
  // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal.
435
- wrapper->active_thread = Qnil;
490
+ wrapper->active_fiber = Qnil;
436
491
  rb_raise_mysql2_stmt_error(stmt_wrapper);
437
492
  }
438
493
  // no data and no error, so query was not a SELECT
439
494
  return Qnil;
440
495
  }
441
496
 
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
497
  if (!is_streaming) {
448
- // recieve the whole result set from the server
498
+ // receive the whole result set from the server
449
499
  if (mysql_stmt_store_result(stmt)) {
450
500
  mysql_free_result(metadata);
451
501
  rb_raise_mysql2_stmt_error(stmt_wrapper);
452
502
  }
453
- wrapper->active_thread = Qnil;
503
+ wrapper->active_fiber = Qnil;
454
504
  }
455
505
 
456
506
  resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, metadata, self);
457
507
 
508
+ rb_mysql_set_server_query_flags(wrapper->client, resultObj);
509
+
458
510
  if (!is_streaming) {
459
511
  // cache all result
460
512
  rb_funcall(resultObj, intern_each, 0);
@@ -467,33 +519,29 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
467
519
  *
468
520
  * Returns a list of fields that will be returned by this statement.
469
521
  */
470
- static VALUE fields(VALUE self) {
522
+ static VALUE rb_mysql_stmt_fields(VALUE self) {
471
523
  MYSQL_FIELD *fields;
472
524
  MYSQL_RES *metadata;
473
525
  unsigned int field_count;
474
526
  unsigned int i;
475
527
  VALUE field_list;
476
528
  MYSQL_STMT* stmt;
477
- #ifdef HAVE_RUBY_ENCODING_H
478
529
  rb_encoding *default_internal_enc, *conn_enc;
479
- #endif
480
530
  GET_STATEMENT(self);
481
531
  GET_CLIENT(stmt_wrapper->client);
482
532
  stmt = stmt_wrapper->stmt;
483
533
 
484
- #ifdef HAVE_RUBY_ENCODING_H
485
534
  default_internal_enc = rb_default_internal_encoding();
486
535
  {
487
536
  GET_CLIENT(stmt_wrapper->client);
488
537
  conn_enc = rb_to_encoding(wrapper->encoding);
489
538
  }
490
- #endif
491
539
 
492
540
  metadata = mysql_stmt_result_metadata(stmt);
493
541
  if (metadata == NULL) {
494
542
  if (mysql_stmt_errno(stmt) != 0) {
495
543
  // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal.
496
- wrapper->active_thread = Qnil;
544
+ wrapper->active_fiber = Qnil;
497
545
  rb_raise_mysql2_stmt_error(stmt_wrapper);
498
546
  }
499
547
  // no data and no error, so query was not a SELECT
@@ -508,12 +556,10 @@ static VALUE fields(VALUE self) {
508
556
  VALUE rb_field;
509
557
 
510
558
  rb_field = rb_str_new(fields[i].name, fields[i].name_length);
511
- #ifdef HAVE_RUBY_ENCODING_H
512
559
  rb_enc_associate(rb_field, conn_enc);
513
560
  if (default_internal_enc) {
514
561
  rb_field = rb_str_export_to_enc(rb_field, default_internal_enc);
515
562
  }
516
- #endif
517
563
 
518
564
  rb_ary_store(field_list, (long)i, rb_field);
519
565
  }
@@ -564,12 +610,23 @@ static VALUE rb_mysql_stmt_close(VALUE self) {
564
610
  }
565
611
 
566
612
  void init_mysql2_statement() {
613
+ cDate = rb_const_get(rb_cObject, rb_intern("Date"));
614
+ rb_global_variable(&cDate);
615
+
616
+ cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
617
+ rb_global_variable(&cDateTime);
618
+
619
+ cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal"));
620
+ rb_global_variable(&cBigDecimal);
621
+
567
622
  cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject);
623
+ rb_undef_alloc_func(cMysql2Statement);
624
+ rb_global_variable(&cMysql2Statement);
568
625
 
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);
626
+ rb_define_method(cMysql2Statement, "param_count", rb_mysql_stmt_param_count, 0);
627
+ rb_define_method(cMysql2Statement, "field_count", rb_mysql_stmt_field_count, 0);
628
+ rb_define_method(cMysql2Statement, "_execute", rb_mysql_stmt_execute, -1);
629
+ rb_define_method(cMysql2Statement, "fields", rb_mysql_stmt_fields, 0);
573
630
  rb_define_method(cMysql2Statement, "last_id", rb_mysql_stmt_last_id, 0);
574
631
  rb_define_method(cMysql2Statement, "affected_rows", rb_mysql_stmt_affected_rows, 0);
575
632
  rb_define_method(cMysql2Statement, "close", rb_mysql_stmt_close, 0);
@@ -589,7 +646,6 @@ void init_mysql2_statement() {
589
646
  intern_year = rb_intern("year");
590
647
 
591
648
  intern_to_s = rb_intern("to_s");
592
- #ifndef HAVE_RB_BIG_CMP
593
- id_cmp = rb_intern("<=>");
594
- #endif
649
+ intern_merge_bang = rb_intern("merge!");
650
+ intern_query_options = rb_intern("@query_options");
595
651
  }
@@ -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,23 @@ 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
24
  opts = Mysql2::Util.key_hash_as_symbols(opts)
24
25
  @read_timeout = nil
25
26
  @query_options = self.class.default_query_options.dup
@@ -31,8 +32,9 @@ module Mysql2
31
32
  opts[:connect_timeout] = 120 unless opts.key?(:connect_timeout)
32
33
 
33
34
  # 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|
35
+ %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
36
  next unless opts.key?(key)
37
+
36
38
  case key
37
39
  when :reconnect, :local_infile, :secure_auth, :automatic_close, :enable_cleartext_plugin
38
40
  send(:"#{key}=", !!opts[key]) # rubocop:disable Style/DoubleNegation
@@ -46,25 +48,30 @@ module Mysql2
46
48
  # force the encoding to utf8
47
49
  self.charset_name = opts[:encoding] || 'utf8'
48
50
 
51
+ mode = parse_ssl_mode(opts[:ssl_mode]) if opts[:ssl_mode]
52
+ if (mode == SSL_MODE_VERIFY_CA || mode == SSL_MODE_VERIFY_IDENTITY) && !opts[:sslca]
53
+ opts[:sslca] = find_default_ca_path
54
+ end
55
+
49
56
  ssl_options = opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslcipher)
50
57
  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]
58
+ self.ssl_mode = mode if mode
52
59
 
53
- case opts[:flags]
60
+ flags = case opts[:flags]
54
61
  when Array
55
- flags = parse_flags_array(opts[:flags], @query_options[:connect_flags])
62
+ parse_flags_array(opts[:flags], @query_options[:connect_flags])
56
63
  when String
57
- flags = parse_flags_array(opts[:flags].split(' '), @query_options[:connect_flags])
64
+ parse_flags_array(opts[:flags].split(' '), @query_options[:connect_flags])
58
65
  when Integer
59
- flags = @query_options[:connect_flags] | opts[:flags]
66
+ @query_options[:connect_flags] | opts[:flags]
60
67
  else
61
- flags = @query_options[:connect_flags]
68
+ @query_options[:connect_flags]
62
69
  end
63
70
 
64
71
  # SSL verify is a connection flag rather than a mysql_ssl_set option
65
72
  flags |= SSL_VERIFY_SERVER_CERT if opts[:sslverify]
66
73
 
67
- if [:user, :pass, :hostname, :dbname, :db, :sock].any? { |k| @query_options.key?(k) }
74
+ if %i[user pass hostname dbname db sock].any? { |k| @query_options.key?(k) }
68
75
  warn "============= WARNING FROM mysql2 ============="
69
76
  warn "The options :user, :pass, :hostname, :dbname, :db, and :sock are deprecated and will be removed at some point in the future."
70
77
  warn "Instead, please use :username, :password, :host, :port, :database, :socket, :flags for the options."
@@ -85,8 +92,9 @@ module Mysql2
85
92
  port = port.to_i unless port.nil?
86
93
  database = database.to_s unless database.nil?
87
94
  socket = socket.to_s unless socket.nil?
95
+ conn_attrs = parse_connect_attrs(opts[:connect_attrs])
88
96
 
89
- connect user, pass, host, port, database, socket, flags
97
+ connect user, pass, host, port, database, socket, flags, conn_attrs
90
98
  end
91
99
 
92
100
  def parse_ssl_mode(mode)
@@ -114,14 +122,32 @@ module Mysql2
114
122
  end
115
123
  end
116
124
 
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
125
+ # Find any default system CA paths to handle system roots
126
+ # by default if stricter validation is requested and no
127
+ # path is provide.
128
+ def find_default_ca_path
129
+ [
130
+ "/etc/ssl/certs/ca-certificates.crt",
131
+ "/etc/pki/tls/certs/ca-bundle.crt",
132
+ "/etc/ssl/ca-bundle.pem",
133
+ "/etc/ssl/cert.pem",
134
+ ].find { |f| File.exist?(f) }
135
+ end
136
+
137
+ # Set default program_name in performance_schema.session_connect_attrs
138
+ # and performance_schema.session_account_connect_attrs
139
+ def parse_connect_attrs(conn_attrs)
140
+ return {} if Mysql2::Client::CONNECT_ATTRS.zero?
141
+
142
+ conn_attrs ||= {}
143
+ conn_attrs[:program_name] ||= $PROGRAM_NAME
144
+ conn_attrs.each_with_object({}) do |(key, value), hash|
145
+ hash[key.to_s] = value.to_s
122
146
  end
123
- else
124
- def query(sql, options = {})
147
+ end
148
+
149
+ def query(sql, options = {})
150
+ Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_NEVER) do
125
151
  _query(sql, @query_options.merge(options))
126
152
  end
127
153
  end
@@ -129,6 +155,7 @@ module Mysql2
129
155
  def query_info
130
156
  info = query_info_string
131
157
  return {} unless info
158
+
132
159
  info_hash = {}
133
160
  info.split.each_slice(2) { |s| info_hash[s[0].downcase.delete(':').to_sym] = s[1].to_i }
134
161
  info_hash
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