mysql2 0.4.2 → 0.5.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.
@@ -1,9 +1,9 @@
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;
6
- static VALUE intern_usec, intern_sec, intern_min, intern_hour, intern_day, intern_month, intern_year;
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
7
 
8
8
  #define GET_STATEMENT(self) \
9
9
  mysql_stmt_wrapper *stmt_wrapper; \
@@ -11,7 +11,6 @@ static VALUE intern_usec, intern_sec, intern_min, intern_hour, intern_day, inter
11
11
  if (!stmt_wrapper->stmt) { rb_raise(cMysql2Error, "Invalid statement handle"); } \
12
12
  if (stmt_wrapper->closed) { rb_raise(cMysql2Error, "Statement handle already closed"); }
13
13
 
14
-
15
14
  static void rb_mysql_stmt_mark(void * ptr) {
16
15
  mysql_stmt_wrapper *stmt_wrapper = ptr;
17
16
  if (!stmt_wrapper) return;
@@ -19,7 +18,7 @@ static void rb_mysql_stmt_mark(void * ptr) {
19
18
  rb_gc_mark(stmt_wrapper->client);
20
19
  }
21
20
 
22
- static void *nogvl_stmt_close(void * ptr) {
21
+ static void *nogvl_stmt_close(void *ptr) {
23
22
  mysql_stmt_wrapper *stmt_wrapper = ptr;
24
23
  if (stmt_wrapper->stmt) {
25
24
  mysql_stmt_close(stmt_wrapper->stmt);
@@ -28,7 +27,7 @@ static void *nogvl_stmt_close(void * ptr) {
28
27
  return NULL;
29
28
  }
30
29
 
31
- static void rb_mysql_stmt_free(void * ptr) {
30
+ static void rb_mysql_stmt_free(void *ptr) {
32
31
  mysql_stmt_wrapper *stmt_wrapper = ptr;
33
32
  decr_mysql2_stmt(stmt_wrapper);
34
33
  }
@@ -42,14 +41,12 @@ void decr_mysql2_stmt(mysql_stmt_wrapper *stmt_wrapper) {
42
41
  }
43
42
  }
44
43
 
45
-
46
44
  void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) {
47
45
  VALUE e;
48
46
  GET_CLIENT(stmt_wrapper->client);
49
47
  VALUE rb_error_msg = rb_str_new2(mysql_stmt_error(stmt_wrapper->stmt));
50
48
  VALUE rb_sql_state = rb_tainted_str_new2(mysql_stmt_sqlstate(stmt_wrapper->stmt));
51
49
 
52
- #ifdef HAVE_RUBY_ENCODING_H
53
50
  rb_encoding *conn_enc;
54
51
  conn_enc = rb_to_encoding(wrapper->encoding);
55
52
 
@@ -61,7 +58,6 @@ void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) {
61
58
  rb_error_msg = rb_str_export_to_enc(rb_error_msg, default_internal_enc);
62
59
  rb_sql_state = rb_str_export_to_enc(rb_sql_state, default_internal_enc);
63
60
  }
64
- #endif
65
61
 
66
62
  e = rb_funcall(cMysql2Error, intern_new_with_args, 4,
67
63
  rb_error_msg,
@@ -71,7 +67,6 @@ void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) {
71
67
  rb_exc_raise(e);
72
68
  }
73
69
 
74
-
75
70
  /*
76
71
  * used to pass all arguments to mysql_stmt_prepare while inside
77
72
  * nogvl_prepare_statement_args
@@ -96,9 +91,7 @@ static void *nogvl_prepare_statement(void *ptr) {
96
91
  VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
97
92
  mysql_stmt_wrapper *stmt_wrapper;
98
93
  VALUE rb_stmt;
99
- #ifdef HAVE_RUBY_ENCODING_H
100
94
  rb_encoding *conn_enc;
101
- #endif
102
95
 
103
96
  Check_Type(sql, T_STRING);
104
97
 
@@ -114,9 +107,7 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
114
107
  {
115
108
  GET_CLIENT(rb_client);
116
109
  stmt_wrapper->stmt = mysql_stmt_init(wrapper->client);
117
- #ifdef HAVE_RUBY_ENCODING_H
118
110
  conn_enc = rb_to_encoding(wrapper->encoding);
119
- #endif
120
111
  }
121
112
  if (stmt_wrapper->stmt == NULL) {
122
113
  rb_raise(cMysql2Error, "Unable to initialize prepared statement: out of memory");
@@ -134,11 +125,8 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
134
125
  {
135
126
  struct nogvl_prepare_statement_args args;
136
127
  args.stmt = stmt_wrapper->stmt;
137
- args.sql = sql;
138
- #ifdef HAVE_RUBY_ENCODING_H
139
128
  // 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
129
+ args.sql = rb_str_export_to_enc(sql, conn_enc);
142
130
  args.sql_ptr = RSTRING_PTR(sql);
143
131
  args.sql_len = RSTRING_LEN(sql);
144
132
 
@@ -154,7 +142,7 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
154
142
  *
155
143
  * Returns the number of parameters the prepared statement expects.
156
144
  */
157
- static VALUE param_count(VALUE self) {
145
+ static VALUE rb_mysql_stmt_param_count(VALUE self) {
158
146
  GET_STATEMENT(self);
159
147
 
160
148
  return ULL2NUM(mysql_stmt_param_count(stmt_wrapper->stmt));
@@ -164,13 +152,13 @@ static VALUE param_count(VALUE self) {
164
152
  *
165
153
  * Returns the number of fields the prepared statement returns.
166
154
  */
167
- static VALUE field_count(VALUE self) {
155
+ static VALUE rb_mysql_stmt_field_count(VALUE self) {
168
156
  GET_STATEMENT(self);
169
157
 
170
158
  return UINT2NUM(mysql_stmt_field_count(stmt_wrapper->stmt));
171
159
  }
172
160
 
173
- static void *nogvl_execute(void *ptr) {
161
+ static void *nogvl_stmt_execute(void *ptr) {
174
162
  MYSQL_STMT *stmt = ptr;
175
163
 
176
164
  if (mysql_stmt_execute(stmt)) {
@@ -180,21 +168,23 @@ static void *nogvl_execute(void *ptr) {
180
168
  }
181
169
  }
182
170
 
183
- static void *nogvl_stmt_store_result(void *ptr) {
184
- MYSQL_STMT *stmt = ptr;
171
+ static void set_buffer_for_string(MYSQL_BIND* bind_buffer, unsigned long *length_buffer, VALUE string) {
172
+ unsigned long length;
185
173
 
186
- if (mysql_stmt_store_result(stmt)) {
187
- return (void *)Qfalse;
188
- } else {
189
- return (void *)Qtrue;
190
- }
174
+ bind_buffer->buffer = RSTRING_PTR(string);
175
+
176
+ length = RSTRING_LEN(string);
177
+ bind_buffer->buffer_length = length;
178
+ *length_buffer = length;
179
+
180
+ bind_buffer->length = length_buffer;
191
181
  }
192
182
 
193
183
  /* Free each bind_buffer[i].buffer except when params_enc is non-nil, this means
194
184
  * the buffer is a Ruby string pointer and not our memory to manage.
195
185
  */
196
186
  #define FREE_BINDS \
197
- for (i = 0; i < argc; i++) { \
187
+ for (i = 0; i < bind_count; i++) { \
198
188
  if (bind_buffers[i].buffer && NIL_P(params_enc[i])) { \
199
189
  xfree(bind_buffers[i].buffer); \
200
190
  } \
@@ -204,48 +194,94 @@ static void *nogvl_stmt_store_result(void *ptr) {
204
194
  xfree(length_buffers); \
205
195
  }
206
196
 
197
+ /* return 0 if the given bignum can cast as LONG_LONG, otherwise 1 */
198
+ static int my_big2ll(VALUE bignum, LONG_LONG *ptr)
199
+ {
200
+ unsigned LONG_LONG num;
201
+ size_t len;
202
+ // rb_absint_size was added in 2.1.0. See:
203
+ // https://github.com/ruby/ruby/commit/9fea875
204
+ #ifdef HAVE_RB_ABSINT_SIZE
205
+ int nlz_bits = 0;
206
+ len = rb_absint_size(bignum, &nlz_bits);
207
+ #else
208
+ len = RBIGNUM_LEN(bignum) * SIZEOF_BDIGITS;
209
+ #endif
210
+ if (len > sizeof(LONG_LONG)) goto overflow;
211
+ if (RBIGNUM_POSITIVE_P(bignum)) {
212
+ num = rb_big2ull(bignum);
213
+ if (num > LLONG_MAX)
214
+ goto overflow;
215
+ *ptr = num;
216
+ }
217
+ else {
218
+ if (len == 8 &&
219
+ #ifdef HAVE_RB_ABSINT_SIZE
220
+ nlz_bits == 0 &&
221
+ #endif
222
+ // rb_absint_singlebit_p was added in 2.1.0. See:
223
+ // https://github.com/ruby/ruby/commit/e5ff9d5
224
+ #if defined(HAVE_RB_ABSINT_SIZE) && defined(HAVE_RB_ABSINT_SINGLEBIT_P)
225
+ /* Optimized to avoid object allocation for Ruby 2.1+
226
+ * only -0x8000000000000000 is safe if `len == 8 && nlz_bits == 0`
227
+ */
228
+ !rb_absint_singlebit_p(bignum)
229
+ #else
230
+ rb_big_cmp(bignum, LL2NUM(LLONG_MIN)) == INT2FIX(-1)
231
+ #endif
232
+ ) {
233
+ goto overflow;
234
+ }
235
+ *ptr = rb_big2ll(bignum);
236
+ }
237
+ return 0;
238
+ overflow:
239
+ return 1;
240
+ }
241
+
207
242
  /* call-seq: stmt.execute
208
243
  *
209
244
  * Executes the current prepared statement, returns +result+.
210
245
  */
211
- static VALUE execute(int argc, VALUE *argv, VALUE self) {
246
+ static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) {
212
247
  MYSQL_BIND *bind_buffers = NULL;
213
248
  unsigned long *length_buffers = NULL;
214
249
  unsigned long bind_count;
215
- long i;
250
+ unsigned long i;
216
251
  MYSQL_STMT *stmt;
217
252
  MYSQL_RES *metadata;
253
+ VALUE opts;
218
254
  VALUE current;
219
255
  VALUE resultObj;
220
- VALUE *params_enc;
256
+ VALUE *params_enc = NULL;
221
257
  int is_streaming;
222
- #ifdef HAVE_RUBY_ENCODING_H
223
258
  rb_encoding *conn_enc;
224
- #endif
225
259
 
226
260
  GET_STATEMENT(self);
227
261
  GET_CLIENT(stmt_wrapper->client);
228
262
 
229
- #ifdef HAVE_RUBY_ENCODING_H
230
263
  conn_enc = rb_to_encoding(wrapper->encoding);
231
- #endif
232
-
233
- /* Scratch space for string encoding exports, allocate on the stack. */
234
- params_enc = alloca(sizeof(VALUE) * argc);
235
264
 
236
265
  stmt = stmt_wrapper->stmt;
237
-
238
266
  bind_count = mysql_stmt_param_count(stmt);
239
- if (argc != (long)bind_count) {
240
- rb_raise(cMysql2Error, "Bind parameter count (%ld) doesn't match number of arguments (%d)", bind_count, argc);
267
+
268
+ // Get count of ordinary arguments, and extract hash opts/keyword arguments
269
+ // Use a local scope to avoid leaking the temporary count variable
270
+ {
271
+ int c = rb_scan_args(argc, argv, "*:", NULL, &opts);
272
+ if (c != (long)bind_count) {
273
+ rb_raise(cMysql2Error, "Bind parameter count (%ld) doesn't match number of arguments (%d)", bind_count, c);
274
+ }
241
275
  }
242
276
 
243
277
  // setup any bind variables in the query
244
278
  if (bind_count > 0) {
279
+ // Scratch space for string encoding exports, allocate on the stack
280
+ params_enc = alloca(sizeof(VALUE) * bind_count);
245
281
  bind_buffers = xcalloc(bind_count, sizeof(MYSQL_BIND));
246
282
  length_buffers = xcalloc(bind_count, sizeof(unsigned long));
247
283
 
248
- for (i = 0; i < argc; i++) {
284
+ for (i = 0; i < bind_count; i++) {
249
285
  bind_buffers[i].buffer = NULL;
250
286
  params_enc[i] = Qnil;
251
287
 
@@ -265,9 +301,19 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
265
301
  #endif
266
302
  break;
267
303
  case T_BIGNUM:
268
- bind_buffers[i].buffer_type = MYSQL_TYPE_LONGLONG;
269
- bind_buffers[i].buffer = xmalloc(sizeof(long long int));
270
- *(LONG_LONG*)(bind_buffers[i].buffer) = rb_big2ll(argv[i]);
304
+ {
305
+ LONG_LONG num;
306
+ if (my_big2ll(argv[i], &num) == 0) {
307
+ bind_buffers[i].buffer_type = MYSQL_TYPE_LONGLONG;
308
+ bind_buffers[i].buffer = xmalloc(sizeof(long long int));
309
+ *(LONG_LONG*)(bind_buffers[i].buffer) = num;
310
+ } else {
311
+ /* The bignum was larger than we can fit in LONG_LONG, send it as a string */
312
+ bind_buffers[i].buffer_type = MYSQL_TYPE_NEWDECIMAL;
313
+ params_enc[i] = rb_str_export_to_enc(rb_big2str(argv[i], 10), conn_enc);
314
+ set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
315
+ }
316
+ }
271
317
  break;
272
318
  case T_FLOAT:
273
319
  bind_buffers[i].buffer_type = MYSQL_TYPE_DOUBLE;
@@ -275,17 +321,21 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
275
321
  *(double*)(bind_buffers[i].buffer) = NUM2DBL(argv[i]);
276
322
  break;
277
323
  case T_STRING:
278
- {
279
- params_enc[i] = argv[i];
280
- #ifdef HAVE_RUBY_ENCODING_H
281
- params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
282
- #endif
283
- bind_buffers[i].buffer_type = MYSQL_TYPE_STRING;
284
- bind_buffers[i].buffer = RSTRING_PTR(params_enc[i]);
285
- bind_buffers[i].buffer_length = RSTRING_LEN(params_enc[i]);
286
- length_buffers[i] = bind_buffers[i].buffer_length;
287
- bind_buffers[i].length = &length_buffers[i];
288
- }
324
+ bind_buffers[i].buffer_type = MYSQL_TYPE_STRING;
325
+
326
+ params_enc[i] = argv[i];
327
+ params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
328
+ set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
329
+ break;
330
+ case T_TRUE:
331
+ bind_buffers[i].buffer_type = MYSQL_TYPE_TINY;
332
+ bind_buffers[i].buffer = xmalloc(sizeof(signed char));
333
+ *(signed char*)(bind_buffers[i].buffer) = 1;
334
+ break;
335
+ case T_FALSE:
336
+ bind_buffers[i].buffer_type = MYSQL_TYPE_TINY;
337
+ bind_buffers[i].buffer = xmalloc(sizeof(signed char));
338
+ *(signed char*)(bind_buffers[i].buffer) = 0;
289
339
  break;
290
340
  default:
291
341
  // TODO: what Ruby type should support MYSQL_TYPE_TIME
@@ -298,7 +348,13 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
298
348
 
299
349
  memset(&t, 0, sizeof(MYSQL_TIME));
300
350
  t.neg = 0;
301
- t.second_part = FIX2INT(rb_funcall(rb_time, intern_usec, 0));
351
+
352
+ if (CLASS_OF(argv[i]) == rb_cTime) {
353
+ t.second_part = FIX2INT(rb_funcall(rb_time, intern_usec, 0));
354
+ } else if (CLASS_OF(argv[i]) == cDateTime) {
355
+ t.second_part = NUM2DBL(rb_funcall(rb_time, intern_sec_fraction, 0)) * 1000000;
356
+ }
357
+
302
358
  t.second = FIX2INT(rb_funcall(rb_time, intern_sec, 0));
303
359
  t.minute = FIX2INT(rb_funcall(rb_time, intern_min, 0));
304
360
  t.hour = FIX2INT(rb_funcall(rb_time, intern_hour, 0));
@@ -324,6 +380,17 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
324
380
  *(MYSQL_TIME*)(bind_buffers[i].buffer) = t;
325
381
  } else if (CLASS_OF(argv[i]) == cBigDecimal) {
326
382
  bind_buffers[i].buffer_type = MYSQL_TYPE_NEWDECIMAL;
383
+
384
+ // DECIMAL are represented with the "string representation of the
385
+ // original server-side value", see
386
+ // https://dev.mysql.com/doc/refman/5.7/en/c-api-prepared-statement-type-conversions.html
387
+ // This should be independent of the locale used both on the server
388
+ // and the client side.
389
+ VALUE rb_val_as_string = rb_funcall(argv[i], intern_to_s, 0);
390
+
391
+ params_enc[i] = rb_val_as_string;
392
+ params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
393
+ set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
327
394
  }
328
395
  break;
329
396
  }
@@ -336,7 +403,40 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
336
403
  }
337
404
  }
338
405
 
339
- if ((VALUE)rb_thread_call_without_gvl(nogvl_execute, stmt, RUBY_UBF_IO, 0) == Qfalse) {
406
+ // Duplicate the options hash, merge! extra opts, put the copy into the Result object
407
+ current = rb_hash_dup(rb_iv_get(stmt_wrapper->client, "@query_options"));
408
+ (void)RB_GC_GUARD(current);
409
+ Check_Type(current, T_HASH);
410
+
411
+ // Merge in hash opts/keyword arguments
412
+ if (!NIL_P(opts)) {
413
+ rb_funcall(current, intern_merge_bang, 1, opts);
414
+ }
415
+
416
+ is_streaming = (Qtrue == rb_hash_aref(current, sym_stream));
417
+
418
+ // From stmt_execute to mysql_stmt_result_metadata to stmt_store_result, no
419
+ // Ruby API calls are allowed so that GC is not invoked. If the connection is
420
+ // in results-streaming-mode for Statement A, and in the middle Statement B
421
+ // gets garbage collected, a message will be sent to the server notifying it
422
+ // to release Statement B, resulting in the following error:
423
+ // Commands out of sync; you can't run this command now
424
+ //
425
+ // In streaming mode, statement execute must return a cursor because we
426
+ // cannot prevent other Statement objects from being garbage collected
427
+ // between fetches of each row of the result set. The following error
428
+ // occurs if cursor mode is not set:
429
+ // Row retrieval was canceled by mysql_stmt_close
430
+
431
+ if (is_streaming) {
432
+ unsigned long type = CURSOR_TYPE_READ_ONLY;
433
+ if (mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, &type)) {
434
+ FREE_BINDS;
435
+ rb_raise(cMysql2Error, "Unable to stream prepared statement, could not set CURSOR_TYPE_READ_ONLY");
436
+ }
437
+ }
438
+
439
+ if ((VALUE)rb_thread_call_without_gvl(nogvl_stmt_execute, stmt, RUBY_UBF_IO, 0) == Qfalse) {
340
440
  FREE_BINDS;
341
441
  rb_raise_mysql2_stmt_error(stmt_wrapper);
342
442
  }
@@ -347,30 +447,26 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
347
447
  if (metadata == NULL) {
348
448
  if (mysql_stmt_errno(stmt) != 0) {
349
449
  // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal.
350
-
351
- MARK_CONN_INACTIVE(stmt_wrapper->client);
450
+ wrapper->active_thread = Qnil;
352
451
  rb_raise_mysql2_stmt_error(stmt_wrapper);
353
452
  }
354
453
  // no data and no error, so query was not a SELECT
355
454
  return Qnil;
356
455
  }
357
456
 
358
- current = rb_hash_dup(rb_iv_get(stmt_wrapper->client, "@query_options"));
359
- (void)RB_GC_GUARD(current);
360
- Check_Type(current, T_HASH);
361
-
362
- is_streaming = (Qtrue == rb_hash_aref(current, sym_stream));
363
457
  if (!is_streaming) {
364
458
  // recieve the whole result set from the server
365
- if (rb_thread_call_without_gvl(nogvl_stmt_store_result, stmt, RUBY_UBF_IO, 0) == Qfalse) {
459
+ if (mysql_stmt_store_result(stmt)) {
366
460
  mysql_free_result(metadata);
367
461
  rb_raise_mysql2_stmt_error(stmt_wrapper);
368
462
  }
369
- MARK_CONN_INACTIVE(stmt_wrapper->client);
463
+ wrapper->active_thread = Qnil;
370
464
  }
371
465
 
372
466
  resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, metadata, self);
373
467
 
468
+ rb_mysql_set_server_query_flags(wrapper->client, resultObj);
469
+
374
470
  if (!is_streaming) {
375
471
  // cache all result
376
472
  rb_funcall(resultObj, intern_each, 0);
@@ -383,42 +479,47 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
383
479
  *
384
480
  * Returns a list of fields that will be returned by this statement.
385
481
  */
386
- static VALUE fields(VALUE self) {
482
+ static VALUE rb_mysql_stmt_fields(VALUE self) {
387
483
  MYSQL_FIELD *fields;
388
484
  MYSQL_RES *metadata;
389
485
  unsigned int field_count;
390
486
  unsigned int i;
391
487
  VALUE field_list;
392
488
  MYSQL_STMT* stmt;
393
- #ifdef HAVE_RUBY_ENCODING_H
394
489
  rb_encoding *default_internal_enc, *conn_enc;
395
- #endif
396
490
  GET_STATEMENT(self);
491
+ GET_CLIENT(stmt_wrapper->client);
397
492
  stmt = stmt_wrapper->stmt;
398
493
 
399
- #ifdef HAVE_RUBY_ENCODING_H
400
494
  default_internal_enc = rb_default_internal_encoding();
401
495
  {
402
496
  GET_CLIENT(stmt_wrapper->client);
403
497
  conn_enc = rb_to_encoding(wrapper->encoding);
404
498
  }
405
- #endif
406
499
 
407
- metadata = mysql_stmt_result_metadata(stmt);
500
+ metadata = mysql_stmt_result_metadata(stmt);
501
+ if (metadata == NULL) {
502
+ if (mysql_stmt_errno(stmt) != 0) {
503
+ // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal.
504
+ wrapper->active_thread = Qnil;
505
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
506
+ }
507
+ // no data and no error, so query was not a SELECT
508
+ return Qnil;
509
+ }
510
+
408
511
  fields = mysql_fetch_fields(metadata);
409
512
  field_count = mysql_stmt_field_count(stmt);
410
513
  field_list = rb_ary_new2((long)field_count);
411
514
 
412
- for(i = 0; i < field_count; i++) {
515
+ for (i = 0; i < field_count; i++) {
413
516
  VALUE rb_field;
414
517
 
415
518
  rb_field = rb_str_new(fields[i].name, fields[i].name_length);
416
- #ifdef HAVE_RUBY_ENCODING_H
417
519
  rb_enc_associate(rb_field, conn_enc);
418
520
  if (default_internal_enc) {
419
521
  rb_field = rb_str_export_to_enc(rb_field, default_internal_enc);
420
522
  }
421
- #endif
422
523
 
423
524
  rb_ary_store(field_list, (long)i, rb_field);
424
525
  }
@@ -469,12 +570,15 @@ static VALUE rb_mysql_stmt_close(VALUE self) {
469
570
  }
470
571
 
471
572
  void init_mysql2_statement() {
472
- cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject);
573
+ cDate = rb_const_get(rb_cObject, rb_intern("Date"));
574
+ cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
575
+ cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal"));
473
576
 
474
- rb_define_method(cMysql2Statement, "param_count", param_count, 0);
475
- rb_define_method(cMysql2Statement, "field_count", field_count, 0);
476
- rb_define_method(cMysql2Statement, "_execute", execute, -1);
477
- rb_define_method(cMysql2Statement, "fields", fields, 0);
577
+ cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject);
578
+ rb_define_method(cMysql2Statement, "param_count", rb_mysql_stmt_param_count, 0);
579
+ rb_define_method(cMysql2Statement, "field_count", rb_mysql_stmt_field_count, 0);
580
+ rb_define_method(cMysql2Statement, "_execute", rb_mysql_stmt_execute, -1);
581
+ rb_define_method(cMysql2Statement, "fields", rb_mysql_stmt_fields, 0);
478
582
  rb_define_method(cMysql2Statement, "last_id", rb_mysql_stmt_last_id, 0);
479
583
  rb_define_method(cMysql2Statement, "affected_rows", rb_mysql_stmt_affected_rows, 0);
480
584
  rb_define_method(cMysql2Statement, "close", rb_mysql_stmt_close, 0);
@@ -484,6 +588,7 @@ void init_mysql2_statement() {
484
588
  intern_new_with_args = rb_intern("new_with_args");
485
589
  intern_each = rb_intern("each");
486
590
 
591
+ intern_sec_fraction = rb_intern("sec_fraction");
487
592
  intern_usec = rb_intern("usec");
488
593
  intern_sec = rb_intern("sec");
489
594
  intern_min = rb_intern("min");
@@ -491,4 +596,7 @@ void init_mysql2_statement() {
491
596
  intern_day = rb_intern("day");
492
597
  intern_month = rb_intern("month");
493
598
  intern_year = rb_intern("year");
599
+
600
+ intern_to_s = rb_intern("to_s");
601
+ intern_merge_bang = rb_intern("merge!");
494
602
  }
@@ -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,21 +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
+ raise Mysql2::Error, "Options parameter must be a Hash" unless opts.is_a? Hash
22
23
  opts = Mysql2::Util.key_hash_as_symbols(opts)
23
24
  @read_timeout = nil
24
25
  @query_options = self.class.default_query_options.dup
@@ -30,13 +31,13 @@ module Mysql2
30
31
  opts[:connect_timeout] = 120 unless opts.key?(:connect_timeout)
31
32
 
32
33
  # TODO: stricter validation rather than silent massaging
33
- [:reconnect, :connect_timeout, :local_infile, :read_timeout, :write_timeout, :default_file, :default_group, :secure_auth, :init_command].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].each do |key|
34
35
  next unless opts.key?(key)
35
36
  case key
36
- when :reconnect, :local_infile, :secure_auth
37
+ when :reconnect, :local_infile, :secure_auth, :automatic_close, :enable_cleartext_plugin
37
38
  send(:"#{key}=", !!opts[key]) # rubocop:disable Style/DoubleNegation
38
39
  when :connect_timeout, :read_timeout, :write_timeout
39
- send(:"#{key}=", opts[key].to_i)
40
+ send(:"#{key}=", Integer(opts[key])) unless opts[key].nil?
40
41
  else
41
42
  send(:"#{key}=", opts[key])
42
43
  end
@@ -46,25 +47,26 @@ module Mysql2
46
47
  self.charset_name = opts[:encoding] || 'utf8'
47
48
 
48
49
  ssl_options = opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslcipher)
49
- ssl_set(*ssl_options) if ssl_options.any?
50
+ 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]
50
52
 
51
- case opts[:flags]
53
+ flags = case opts[:flags]
52
54
  when Array
53
- flags = parse_flags_array(opts[:flags], @query_options[:connect_flags])
55
+ parse_flags_array(opts[:flags], @query_options[:connect_flags])
54
56
  when String
55
- flags = parse_flags_array(opts[:flags].split(' '), @query_options[:connect_flags])
57
+ parse_flags_array(opts[:flags].split(' '), @query_options[:connect_flags])
56
58
  when Integer
57
- flags = @query_options[:connect_flags] | opts[:flags]
59
+ @query_options[:connect_flags] | opts[:flags]
58
60
  else
59
- flags = @query_options[:connect_flags]
61
+ @query_options[:connect_flags]
60
62
  end
61
63
 
62
64
  # SSL verify is a connection flag rather than a mysql_ssl_set option
63
- flags |= SSL_VERIFY_SERVER_CERT if opts[:sslverify] && ssl_options.any?
65
+ flags |= SSL_VERIFY_SERVER_CERT if opts[:sslverify]
64
66
 
65
- if [:user, :pass, :hostname, :dbname, :db, :sock].any? { |k| @query_options.key?(k) }
67
+ if %i[user pass hostname dbname db sock].any? { |k| @query_options.key?(k) }
66
68
  warn "============= WARNING FROM mysql2 ============="
67
- warn "The options :user, :pass, :hostname, :dbname, :db, and :sock will be deprecated at some point in the future."
69
+ warn "The options :user, :pass, :hostname, :dbname, :db, and :sock are deprecated and will be removed at some point in the future."
68
70
  warn "Instead, please use :username, :password, :host, :port, :database, :socket, :flags for the options."
69
71
  warn "============= END WARNING FROM mysql2 ========="
70
72
  end
@@ -83,8 +85,20 @@ module Mysql2
83
85
  port = port.to_i unless port.nil?
84
86
  database = database.to_s unless database.nil?
85
87
  socket = socket.to_s unless socket.nil?
88
+ conn_attrs = parse_connect_attrs(opts[:connect_attrs])
86
89
 
87
- connect user, pass, host, port, database, socket, flags
90
+ connect user, pass, host, port, database, socket, flags, conn_attrs
91
+ end
92
+
93
+ def parse_ssl_mode(mode)
94
+ m = mode.to_s.upcase
95
+ if m.start_with?('SSL_MODE_')
96
+ return Mysql2::Client.const_get(m) if Mysql2::Client.const_defined?(m)
97
+ else
98
+ x = 'SSL_MODE_' + m
99
+ return Mysql2::Client.const_get(x) if Mysql2::Client.const_defined?(x)
100
+ end
101
+ warn "Unknown MySQL ssl_mode flag: #{mode}"
88
102
  end
89
103
 
90
104
  def parse_flags_array(flags, initial = 0)
@@ -101,14 +115,19 @@ module Mysql2
101
115
  end
102
116
  end
103
117
 
104
- if Thread.respond_to?(:handle_interrupt)
105
- def query(sql, options = {})
106
- Thread.handle_interrupt(::Mysql2::Util::TimeoutError => :never) do
107
- _query(sql, @query_options.merge(options))
108
- end
118
+ # Set default program_name in performance_schema.session_connect_attrs
119
+ # and performance_schema.session_account_connect_attrs
120
+ def parse_connect_attrs(conn_attrs)
121
+ return {} if Mysql2::Client::CONNECT_ATTRS.zero?
122
+ conn_attrs ||= {}
123
+ conn_attrs[:program_name] ||= $PROGRAM_NAME
124
+ conn_attrs.each_with_object({}) do |(key, value), hash|
125
+ hash[key.to_s] = value.to_s
109
126
  end
110
- else
111
- def query(sql, options = {})
127
+ end
128
+
129
+ def query(sql, options = {})
130
+ Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_CLASS => :never) do
112
131
  _query(sql, @query_options.merge(options))
113
132
  end
114
133
  end