mysql2 0.4.2 → 0.5.5

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 +231 -86
  3. data/ext/mysql2/client.c +527 -128
  4. data/ext/mysql2/client.h +11 -52
  5. data/ext/mysql2/extconf.rb +100 -21
  6. data/ext/mysql2/mysql2_ext.c +8 -2
  7. data/ext/mysql2/mysql2_ext.h +21 -8
  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 +333 -109
  11. data/ext/mysql2/result.h +1 -0
  12. data/ext/mysql2/statement.c +247 -90
  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 +71 -31
  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 +432 -0
  24. data/support/C74CD1D8.asc +104 -0
  25. data/support/mysql_enc_to_ruby.rb +8 -3
  26. data/support/ruby_enc_to_mysql.rb +7 -5
  27. metadata +19 -61
  28. data/examples/eventmachine.rb +0 -21
  29. data/examples/threaded.rb +0 -18
  30. data/spec/configuration.yml.example +0 -17
  31. data/spec/em/em_spec.rb +0 -135
  32. data/spec/my.cnf.example +0 -9
  33. data/spec/mysql2/client_spec.rb +0 -939
  34. data/spec/mysql2/error_spec.rb +0 -84
  35. data/spec/mysql2/result_spec.rb +0 -510
  36. data/spec/mysql2/statement_spec.rb +0 -684
  37. data/spec/rcov.opts +0 -3
  38. data/spec/spec_helper.rb +0 -94
  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
data/ext/mysql2/result.h CHANGED
@@ -6,6 +6,7 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_
6
6
 
7
7
  typedef struct {
8
8
  VALUE fields;
9
+ VALUE fieldTypes;
9
10
  VALUE rows;
10
11
  VALUE client;
11
12
  VALUE encoding;
@@ -1,25 +1,65 @@
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
+ intern_query_options;
8
+
9
+ #ifndef NEW_TYPEDDATA_WRAPPER
10
+ #define TypedData_Get_Struct(obj, type, ignore, sval) Data_Get_Struct(obj, type, sval)
11
+ #endif
7
12
 
8
13
  #define GET_STATEMENT(self) \
9
14
  mysql_stmt_wrapper *stmt_wrapper; \
10
- Data_Get_Struct(self, mysql_stmt_wrapper, stmt_wrapper); \
15
+ TypedData_Get_Struct(self, mysql_stmt_wrapper, &rb_mysql_statement_type, stmt_wrapper); \
11
16
  if (!stmt_wrapper->stmt) { rb_raise(cMysql2Error, "Invalid statement handle"); } \
12
17
  if (stmt_wrapper->closed) { rb_raise(cMysql2Error, "Statement handle already closed"); }
13
18
 
14
-
15
19
  static void rb_mysql_stmt_mark(void * ptr) {
16
20
  mysql_stmt_wrapper *stmt_wrapper = ptr;
17
21
  if (!stmt_wrapper) return;
18
22
 
19
- rb_gc_mark(stmt_wrapper->client);
23
+ rb_gc_mark_movable(stmt_wrapper->client);
24
+ }
25
+
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);
20
42
  }
43
+ #endif
21
44
 
22
- static void *nogvl_stmt_close(void * ptr) {
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) {
23
63
  mysql_stmt_wrapper *stmt_wrapper = ptr;
24
64
  if (stmt_wrapper->stmt) {
25
65
  mysql_stmt_close(stmt_wrapper->stmt);
@@ -28,11 +68,6 @@ static void *nogvl_stmt_close(void * ptr) {
28
68
  return NULL;
29
69
  }
30
70
 
31
- static void rb_mysql_stmt_free(void * ptr) {
32
- mysql_stmt_wrapper *stmt_wrapper = ptr;
33
- decr_mysql2_stmt(stmt_wrapper);
34
- }
35
-
36
71
  void decr_mysql2_stmt(mysql_stmt_wrapper *stmt_wrapper) {
37
72
  stmt_wrapper->refcount--;
38
73
 
@@ -42,14 +77,12 @@ void decr_mysql2_stmt(mysql_stmt_wrapper *stmt_wrapper) {
42
77
  }
43
78
  }
44
79
 
45
-
46
80
  void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) {
47
81
  VALUE e;
48
82
  GET_CLIENT(stmt_wrapper->client);
49
83
  VALUE rb_error_msg = rb_str_new2(mysql_stmt_error(stmt_wrapper->stmt));
50
- 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));
51
85
 
52
- #ifdef HAVE_RUBY_ENCODING_H
53
86
  rb_encoding *conn_enc;
54
87
  conn_enc = rb_to_encoding(wrapper->encoding);
55
88
 
@@ -61,7 +94,6 @@ void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) {
61
94
  rb_error_msg = rb_str_export_to_enc(rb_error_msg, default_internal_enc);
62
95
  rb_sql_state = rb_str_export_to_enc(rb_sql_state, default_internal_enc);
63
96
  }
64
- #endif
65
97
 
66
98
  e = rb_funcall(cMysql2Error, intern_new_with_args, 4,
67
99
  rb_error_msg,
@@ -71,7 +103,6 @@ void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) {
71
103
  rb_exc_raise(e);
72
104
  }
73
105
 
74
-
75
106
  /*
76
107
  * used to pass all arguments to mysql_stmt_prepare while inside
77
108
  * nogvl_prepare_statement_args
@@ -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");
@@ -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)) {
@@ -180,21 +208,23 @@ static void *nogvl_execute(void *ptr) {
180
208
  }
181
209
  }
182
210
 
183
- static void *nogvl_stmt_store_result(void *ptr) {
184
- MYSQL_STMT *stmt = ptr;
211
+ static void set_buffer_for_string(MYSQL_BIND* bind_buffer, unsigned long *length_buffer, VALUE string) {
212
+ unsigned long length;
185
213
 
186
- if (mysql_stmt_store_result(stmt)) {
187
- return (void *)Qfalse;
188
- } else {
189
- return (void *)Qtrue;
190
- }
214
+ bind_buffer->buffer = RSTRING_PTR(string);
215
+
216
+ length = RSTRING_LEN(string);
217
+ bind_buffer->buffer_length = length;
218
+ *length_buffer = length;
219
+
220
+ bind_buffer->length = length_buffer;
191
221
  }
192
222
 
193
223
  /* Free each bind_buffer[i].buffer except when params_enc is non-nil, this means
194
224
  * the buffer is a Ruby string pointer and not our memory to manage.
195
225
  */
196
226
  #define FREE_BINDS \
197
- for (i = 0; i < argc; i++) { \
227
+ for (i = 0; i < bind_count; i++) { \
198
228
  if (bind_buffers[i].buffer && NIL_P(params_enc[i])) { \
199
229
  xfree(bind_buffers[i].buffer); \
200
230
  } \
@@ -204,48 +234,94 @@ static void *nogvl_stmt_store_result(void *ptr) {
204
234
  xfree(length_buffers); \
205
235
  }
206
236
 
237
+ /* return 0 if the given bignum can cast as LONG_LONG, otherwise 1 */
238
+ static int my_big2ll(VALUE bignum, LONG_LONG *ptr)
239
+ {
240
+ unsigned LONG_LONG num;
241
+ size_t len;
242
+ // rb_absint_size was added in 2.1.0. See:
243
+ // https://github.com/ruby/ruby/commit/9fea875
244
+ #ifdef HAVE_RB_ABSINT_SIZE
245
+ int nlz_bits = 0;
246
+ len = rb_absint_size(bignum, &nlz_bits);
247
+ #else
248
+ len = RBIGNUM_LEN(bignum) * SIZEOF_BDIGITS;
249
+ #endif
250
+ if (len > sizeof(LONG_LONG)) goto overflow;
251
+ if (RBIGNUM_POSITIVE_P(bignum)) {
252
+ num = rb_big2ull(bignum);
253
+ if (num > LLONG_MAX)
254
+ goto overflow;
255
+ *ptr = num;
256
+ }
257
+ else {
258
+ if (len == 8 &&
259
+ #ifdef HAVE_RB_ABSINT_SIZE
260
+ nlz_bits == 0 &&
261
+ #endif
262
+ // rb_absint_singlebit_p was added in 2.1.0. See:
263
+ // https://github.com/ruby/ruby/commit/e5ff9d5
264
+ #if defined(HAVE_RB_ABSINT_SIZE) && defined(HAVE_RB_ABSINT_SINGLEBIT_P)
265
+ /* Optimized to avoid object allocation for Ruby 2.1+
266
+ * only -0x8000000000000000 is safe if `len == 8 && nlz_bits == 0`
267
+ */
268
+ !rb_absint_singlebit_p(bignum)
269
+ #else
270
+ rb_big_cmp(bignum, LL2NUM(LLONG_MIN)) == INT2FIX(-1)
271
+ #endif
272
+ ) {
273
+ goto overflow;
274
+ }
275
+ *ptr = rb_big2ll(bignum);
276
+ }
277
+ return 0;
278
+ overflow:
279
+ return 1;
280
+ }
281
+
207
282
  /* call-seq: stmt.execute
208
283
  *
209
284
  * Executes the current prepared statement, returns +result+.
210
285
  */
211
- static VALUE execute(int argc, VALUE *argv, VALUE self) {
286
+ static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) {
212
287
  MYSQL_BIND *bind_buffers = NULL;
213
288
  unsigned long *length_buffers = NULL;
214
289
  unsigned long bind_count;
215
- long i;
290
+ unsigned long i;
216
291
  MYSQL_STMT *stmt;
217
292
  MYSQL_RES *metadata;
293
+ VALUE opts;
218
294
  VALUE current;
219
295
  VALUE resultObj;
220
- VALUE *params_enc;
296
+ VALUE *params_enc = NULL;
221
297
  int is_streaming;
222
- #ifdef HAVE_RUBY_ENCODING_H
223
298
  rb_encoding *conn_enc;
224
- #endif
225
299
 
226
300
  GET_STATEMENT(self);
227
301
  GET_CLIENT(stmt_wrapper->client);
228
302
 
229
- #ifdef HAVE_RUBY_ENCODING_H
230
303
  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
304
 
236
305
  stmt = stmt_wrapper->stmt;
237
-
238
306
  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);
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
+ }
241
315
  }
242
316
 
243
317
  // setup any bind variables in the query
244
318
  if (bind_count > 0) {
319
+ // Scratch space for string encoding exports, allocate on the stack
320
+ params_enc = alloca(sizeof(VALUE) * bind_count);
245
321
  bind_buffers = xcalloc(bind_count, sizeof(MYSQL_BIND));
246
322
  length_buffers = xcalloc(bind_count, sizeof(unsigned long));
247
323
 
248
- for (i = 0; i < argc; i++) {
324
+ for (i = 0; i < bind_count; i++) {
249
325
  bind_buffers[i].buffer = NULL;
250
326
  params_enc[i] = Qnil;
251
327
 
@@ -265,9 +341,19 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
265
341
  #endif
266
342
  break;
267
343
  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]);
344
+ {
345
+ LONG_LONG num;
346
+ if (my_big2ll(argv[i], &num) == 0) {
347
+ bind_buffers[i].buffer_type = MYSQL_TYPE_LONGLONG;
348
+ bind_buffers[i].buffer = xmalloc(sizeof(long long int));
349
+ *(LONG_LONG*)(bind_buffers[i].buffer) = num;
350
+ } else {
351
+ /* The bignum was larger than we can fit in LONG_LONG, send it as a string */
352
+ bind_buffers[i].buffer_type = MYSQL_TYPE_NEWDECIMAL;
353
+ params_enc[i] = rb_str_export_to_enc(rb_big2str(argv[i], 10), conn_enc);
354
+ set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
355
+ }
356
+ }
271
357
  break;
272
358
  case T_FLOAT:
273
359
  bind_buffers[i].buffer_type = MYSQL_TYPE_DOUBLE;
@@ -275,17 +361,21 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
275
361
  *(double*)(bind_buffers[i].buffer) = NUM2DBL(argv[i]);
276
362
  break;
277
363
  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
- }
364
+ bind_buffers[i].buffer_type = MYSQL_TYPE_STRING;
365
+
366
+ params_enc[i] = argv[i];
367
+ params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
368
+ set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
369
+ break;
370
+ case T_TRUE:
371
+ bind_buffers[i].buffer_type = MYSQL_TYPE_TINY;
372
+ bind_buffers[i].buffer = xmalloc(sizeof(signed char));
373
+ *(signed char*)(bind_buffers[i].buffer) = 1;
374
+ break;
375
+ case T_FALSE:
376
+ bind_buffers[i].buffer_type = MYSQL_TYPE_TINY;
377
+ bind_buffers[i].buffer = xmalloc(sizeof(signed char));
378
+ *(signed char*)(bind_buffers[i].buffer) = 0;
289
379
  break;
290
380
  default:
291
381
  // TODO: what Ruby type should support MYSQL_TYPE_TIME
@@ -298,7 +388,13 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
298
388
 
299
389
  memset(&t, 0, sizeof(MYSQL_TIME));
300
390
  t.neg = 0;
301
- t.second_part = FIX2INT(rb_funcall(rb_time, intern_usec, 0));
391
+
392
+ if (CLASS_OF(argv[i]) == rb_cTime) {
393
+ t.second_part = FIX2INT(rb_funcall(rb_time, intern_usec, 0));
394
+ } else if (CLASS_OF(argv[i]) == cDateTime) {
395
+ t.second_part = NUM2DBL(rb_funcall(rb_time, intern_sec_fraction, 0)) * 1000000;
396
+ }
397
+
302
398
  t.second = FIX2INT(rb_funcall(rb_time, intern_sec, 0));
303
399
  t.minute = FIX2INT(rb_funcall(rb_time, intern_min, 0));
304
400
  t.hour = FIX2INT(rb_funcall(rb_time, intern_hour, 0));
@@ -324,6 +420,17 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
324
420
  *(MYSQL_TIME*)(bind_buffers[i].buffer) = t;
325
421
  } else if (CLASS_OF(argv[i]) == cBigDecimal) {
326
422
  bind_buffers[i].buffer_type = MYSQL_TYPE_NEWDECIMAL;
423
+
424
+ // DECIMAL are represented with the "string representation of the
425
+ // original server-side value", see
426
+ // https://dev.mysql.com/doc/refman/5.7/en/c-api-prepared-statement-type-conversions.html
427
+ // This should be independent of the locale used both on the server
428
+ // and the client side.
429
+ VALUE rb_val_as_string = rb_funcall(argv[i], intern_to_s, 0);
430
+
431
+ params_enc[i] = rb_val_as_string;
432
+ params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
433
+ set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
327
434
  }
328
435
  break;
329
436
  }
@@ -336,7 +443,40 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
336
443
  }
337
444
  }
338
445
 
339
- 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) {
340
480
  FREE_BINDS;
341
481
  rb_raise_mysql2_stmt_error(stmt_wrapper);
342
482
  }
@@ -347,30 +487,26 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
347
487
  if (metadata == NULL) {
348
488
  if (mysql_stmt_errno(stmt) != 0) {
349
489
  // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal.
350
-
351
- MARK_CONN_INACTIVE(stmt_wrapper->client);
490
+ wrapper->active_fiber = Qnil;
352
491
  rb_raise_mysql2_stmt_error(stmt_wrapper);
353
492
  }
354
493
  // no data and no error, so query was not a SELECT
355
494
  return Qnil;
356
495
  }
357
496
 
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
497
  if (!is_streaming) {
364
- // 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) {
498
+ // receive the whole result set from the server
499
+ if (mysql_stmt_store_result(stmt)) {
366
500
  mysql_free_result(metadata);
367
501
  rb_raise_mysql2_stmt_error(stmt_wrapper);
368
502
  }
369
- MARK_CONN_INACTIVE(stmt_wrapper->client);
503
+ wrapper->active_fiber = Qnil;
370
504
  }
371
505
 
372
506
  resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, metadata, self);
373
507
 
508
+ rb_mysql_set_server_query_flags(wrapper->client, resultObj);
509
+
374
510
  if (!is_streaming) {
375
511
  // cache all result
376
512
  rb_funcall(resultObj, intern_each, 0);
@@ -383,42 +519,47 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
383
519
  *
384
520
  * Returns a list of fields that will be returned by this statement.
385
521
  */
386
- static VALUE fields(VALUE self) {
522
+ static VALUE rb_mysql_stmt_fields(VALUE self) {
387
523
  MYSQL_FIELD *fields;
388
524
  MYSQL_RES *metadata;
389
525
  unsigned int field_count;
390
526
  unsigned int i;
391
527
  VALUE field_list;
392
528
  MYSQL_STMT* stmt;
393
- #ifdef HAVE_RUBY_ENCODING_H
394
529
  rb_encoding *default_internal_enc, *conn_enc;
395
- #endif
396
530
  GET_STATEMENT(self);
531
+ GET_CLIENT(stmt_wrapper->client);
397
532
  stmt = stmt_wrapper->stmt;
398
533
 
399
- #ifdef HAVE_RUBY_ENCODING_H
400
534
  default_internal_enc = rb_default_internal_encoding();
401
535
  {
402
536
  GET_CLIENT(stmt_wrapper->client);
403
537
  conn_enc = rb_to_encoding(wrapper->encoding);
404
538
  }
405
- #endif
406
539
 
407
- metadata = mysql_stmt_result_metadata(stmt);
540
+ metadata = mysql_stmt_result_metadata(stmt);
541
+ if (metadata == NULL) {
542
+ if (mysql_stmt_errno(stmt) != 0) {
543
+ // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal.
544
+ wrapper->active_fiber = Qnil;
545
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
546
+ }
547
+ // no data and no error, so query was not a SELECT
548
+ return Qnil;
549
+ }
550
+
408
551
  fields = mysql_fetch_fields(metadata);
409
552
  field_count = mysql_stmt_field_count(stmt);
410
553
  field_list = rb_ary_new2((long)field_count);
411
554
 
412
- for(i = 0; i < field_count; i++) {
555
+ for (i = 0; i < field_count; i++) {
413
556
  VALUE rb_field;
414
557
 
415
558
  rb_field = rb_str_new(fields[i].name, fields[i].name_length);
416
- #ifdef HAVE_RUBY_ENCODING_H
417
559
  rb_enc_associate(rb_field, conn_enc);
418
560
  if (default_internal_enc) {
419
561
  rb_field = rb_str_export_to_enc(rb_field, default_internal_enc);
420
562
  }
421
- #endif
422
563
 
423
564
  rb_ary_store(field_list, (long)i, rb_field);
424
565
  }
@@ -469,12 +610,23 @@ static VALUE rb_mysql_stmt_close(VALUE self) {
469
610
  }
470
611
 
471
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
+
472
622
  cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject);
623
+ rb_undef_alloc_func(cMysql2Statement);
624
+ rb_global_variable(&cMysql2Statement);
473
625
 
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);
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);
478
630
  rb_define_method(cMysql2Statement, "last_id", rb_mysql_stmt_last_id, 0);
479
631
  rb_define_method(cMysql2Statement, "affected_rows", rb_mysql_stmt_affected_rows, 0);
480
632
  rb_define_method(cMysql2Statement, "close", rb_mysql_stmt_close, 0);
@@ -484,6 +636,7 @@ void init_mysql2_statement() {
484
636
  intern_new_with_args = rb_intern("new_with_args");
485
637
  intern_each = rb_intern("each");
486
638
 
639
+ intern_sec_fraction = rb_intern("sec_fraction");
487
640
  intern_usec = rb_intern("usec");
488
641
  intern_sec = rb_intern("sec");
489
642
  intern_min = rb_intern("min");
@@ -491,4 +644,8 @@ void init_mysql2_statement() {
491
644
  intern_day = rb_intern("day");
492
645
  intern_month = rb_intern("month");
493
646
  intern_year = rb_intern("year");
647
+
648
+ intern_to_s = rb_intern("to_s");
649
+ intern_merge_bang = rb_intern("merge!");
650
+ intern_query_options = rb_intern("@query_options");
494
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.