mysql2 0.4.10 → 0.5.6

Sign up to get free protection for your applications and to get access to all the features.
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