mysql2 0.4.2 → 0.5.5

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 +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.