mysql2 0.3.10 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1 -230
  3. data/LICENSE +21 -0
  4. data/README.md +405 -80
  5. data/ext/mysql2/client.c +1110 -335
  6. data/ext/mysql2/client.h +18 -32
  7. data/ext/mysql2/extconf.rb +228 -37
  8. data/ext/mysql2/infile.c +122 -0
  9. data/ext/mysql2/infile.h +1 -0
  10. data/ext/mysql2/mysql2_ext.c +3 -1
  11. data/ext/mysql2/mysql2_ext.h +18 -16
  12. data/ext/mysql2/mysql_enc_name_to_ruby.h +172 -0
  13. data/ext/mysql2/mysql_enc_to_ruby.h +310 -0
  14. data/ext/mysql2/result.c +671 -226
  15. data/ext/mysql2/result.h +15 -6
  16. data/ext/mysql2/statement.c +604 -0
  17. data/ext/mysql2/statement.h +17 -0
  18. data/ext/mysql2/wait_for_single_fd.h +2 -1
  19. data/lib/mysql2/client.rb +125 -212
  20. data/lib/mysql2/console.rb +5 -0
  21. data/lib/mysql2/em.rb +24 -8
  22. data/lib/mysql2/error.rb +93 -8
  23. data/lib/mysql2/field.rb +3 -0
  24. data/lib/mysql2/result.rb +2 -0
  25. data/lib/mysql2/statement.rb +11 -0
  26. data/lib/mysql2/version.rb +1 -1
  27. data/lib/mysql2.rb +70 -5
  28. data/support/5072E1F5.asc +432 -0
  29. data/support/libmysql.def +219 -0
  30. data/support/mysql_enc_to_ruby.rb +86 -0
  31. data/support/ruby_enc_to_mysql.rb +63 -0
  32. metadata +45 -214
  33. data/.gitignore +0 -12
  34. data/.rspec +0 -3
  35. data/.rvmrc +0 -1
  36. data/.travis.yml +0 -7
  37. data/Gemfile +0 -3
  38. data/MIT-LICENSE +0 -20
  39. data/Rakefile +0 -5
  40. data/benchmark/active_record.rb +0 -51
  41. data/benchmark/active_record_threaded.rb +0 -42
  42. data/benchmark/allocations.rb +0 -33
  43. data/benchmark/escape.rb +0 -36
  44. data/benchmark/query_with_mysql_casting.rb +0 -80
  45. data/benchmark/query_without_mysql_casting.rb +0 -56
  46. data/benchmark/sequel.rb +0 -37
  47. data/benchmark/setup_db.rb +0 -119
  48. data/benchmark/threaded.rb +0 -44
  49. data/examples/eventmachine.rb +0 -21
  50. data/examples/threaded.rb +0 -20
  51. data/mysql2.gemspec +0 -29
  52. data/spec/em/em_spec.rb +0 -50
  53. data/spec/mysql2/client_spec.rb +0 -465
  54. data/spec/mysql2/error_spec.rb +0 -69
  55. data/spec/mysql2/result_spec.rb +0 -388
  56. data/spec/rcov.opts +0 -3
  57. data/spec/spec_helper.rb +0 -67
  58. data/tasks/benchmarks.rake +0 -20
  59. data/tasks/compile.rake +0 -71
  60. data/tasks/rspec.rake +0 -16
  61. data/tasks/vendor_mysql.rake +0 -40
data/ext/mysql2/result.h CHANGED
@@ -1,20 +1,29 @@
1
1
  #ifndef MYSQL2_RESULT_H
2
2
  #define MYSQL2_RESULT_H
3
3
 
4
- void init_mysql2_result();
5
- VALUE rb_mysql_result_to_obj(MYSQL_RES * r);
4
+ void init_mysql2_result(void);
5
+ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r, VALUE statement);
6
6
 
7
7
  typedef struct {
8
8
  VALUE fields;
9
9
  VALUE rows;
10
+ VALUE client;
10
11
  VALUE encoding;
11
- unsigned int numberOfFields;
12
- unsigned long numberOfRows;
12
+ VALUE statement;
13
+ my_ulonglong numberOfFields;
14
+ my_ulonglong numberOfRows;
13
15
  unsigned long lastRowProcessed;
16
+ char is_streaming;
17
+ char streamingComplete;
14
18
  char resultFreed;
15
19
  MYSQL_RES *result;
20
+ mysql_stmt_wrapper *stmt_wrapper;
21
+ mysql_client_wrapper *client_wrapper;
22
+ /* statement result bind buffers */
23
+ MYSQL_BIND *result_buffers;
24
+ my_bool *is_null;
25
+ my_bool *error;
26
+ unsigned long *length;
16
27
  } mysql2_result_wrapper;
17
28
 
18
- #define GetMysql2Result(obj, sval) (sval = (mysql2_result_wrapper*)DATA_PTR(obj));
19
-
20
29
  #endif
@@ -0,0 +1,604 @@
1
+ #include <mysql2_ext.h>
2
+
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
+ #define GET_STATEMENT(self) \
10
+ mysql_stmt_wrapper *stmt_wrapper; \
11
+ Data_Get_Struct(self, mysql_stmt_wrapper, stmt_wrapper); \
12
+ if (!stmt_wrapper->stmt) { rb_raise(cMysql2Error, "Invalid statement handle"); } \
13
+ if (stmt_wrapper->closed) { rb_raise(cMysql2Error, "Statement handle already closed"); }
14
+
15
+ static void rb_mysql_stmt_mark(void * ptr) {
16
+ mysql_stmt_wrapper *stmt_wrapper = ptr;
17
+ if (!stmt_wrapper) return;
18
+
19
+ rb_gc_mark(stmt_wrapper->client);
20
+ }
21
+
22
+ static void *nogvl_stmt_close(void *ptr) {
23
+ mysql_stmt_wrapper *stmt_wrapper = ptr;
24
+ if (stmt_wrapper->stmt) {
25
+ mysql_stmt_close(stmt_wrapper->stmt);
26
+ stmt_wrapper->stmt = NULL;
27
+ }
28
+ return NULL;
29
+ }
30
+
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
+ void decr_mysql2_stmt(mysql_stmt_wrapper *stmt_wrapper) {
37
+ stmt_wrapper->refcount--;
38
+
39
+ if (stmt_wrapper->refcount == 0) {
40
+ nogvl_stmt_close(stmt_wrapper);
41
+ xfree(stmt_wrapper);
42
+ }
43
+ }
44
+
45
+ void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) {
46
+ VALUE e;
47
+ GET_CLIENT(stmt_wrapper->client);
48
+ VALUE rb_error_msg = rb_str_new2(mysql_stmt_error(stmt_wrapper->stmt));
49
+ VALUE rb_sql_state = rb_tainted_str_new2(mysql_stmt_sqlstate(stmt_wrapper->stmt));
50
+
51
+ rb_encoding *conn_enc;
52
+ conn_enc = rb_to_encoding(wrapper->encoding);
53
+
54
+ rb_encoding *default_internal_enc = rb_default_internal_encoding();
55
+
56
+ rb_enc_associate(rb_error_msg, conn_enc);
57
+ rb_enc_associate(rb_sql_state, conn_enc);
58
+ if (default_internal_enc) {
59
+ rb_error_msg = rb_str_export_to_enc(rb_error_msg, default_internal_enc);
60
+ rb_sql_state = rb_str_export_to_enc(rb_sql_state, default_internal_enc);
61
+ }
62
+
63
+ e = rb_funcall(cMysql2Error, intern_new_with_args, 4,
64
+ rb_error_msg,
65
+ LONG2FIX(wrapper->server_version),
66
+ UINT2NUM(mysql_stmt_errno(stmt_wrapper->stmt)),
67
+ rb_sql_state);
68
+ rb_exc_raise(e);
69
+ }
70
+
71
+ /*
72
+ * used to pass all arguments to mysql_stmt_prepare while inside
73
+ * nogvl_prepare_statement_args
74
+ */
75
+ struct nogvl_prepare_statement_args {
76
+ MYSQL_STMT *stmt;
77
+ VALUE sql;
78
+ const char *sql_ptr;
79
+ unsigned long sql_len;
80
+ };
81
+
82
+ static void *nogvl_prepare_statement(void *ptr) {
83
+ struct nogvl_prepare_statement_args *args = ptr;
84
+
85
+ if (mysql_stmt_prepare(args->stmt, args->sql_ptr, args->sql_len)) {
86
+ return (void*)Qfalse;
87
+ } else {
88
+ return (void*)Qtrue;
89
+ }
90
+ }
91
+
92
+ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
93
+ mysql_stmt_wrapper *stmt_wrapper;
94
+ VALUE rb_stmt;
95
+ rb_encoding *conn_enc;
96
+
97
+ Check_Type(sql, T_STRING);
98
+
99
+ rb_stmt = Data_Make_Struct(cMysql2Statement, mysql_stmt_wrapper, rb_mysql_stmt_mark, rb_mysql_stmt_free, stmt_wrapper);
100
+ {
101
+ stmt_wrapper->client = rb_client;
102
+ stmt_wrapper->refcount = 1;
103
+ stmt_wrapper->closed = 0;
104
+ stmt_wrapper->stmt = NULL;
105
+ }
106
+
107
+ // instantiate stmt
108
+ {
109
+ GET_CLIENT(rb_client);
110
+ stmt_wrapper->stmt = mysql_stmt_init(wrapper->client);
111
+ conn_enc = rb_to_encoding(wrapper->encoding);
112
+ }
113
+ if (stmt_wrapper->stmt == NULL) {
114
+ rb_raise(cMysql2Error, "Unable to initialize prepared statement: out of memory");
115
+ }
116
+
117
+ // set STMT_ATTR_UPDATE_MAX_LENGTH attr
118
+ {
119
+ my_bool truth = 1;
120
+ if (mysql_stmt_attr_set(stmt_wrapper->stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &truth)) {
121
+ rb_raise(cMysql2Error, "Unable to initialize prepared statement: set STMT_ATTR_UPDATE_MAX_LENGTH");
122
+ }
123
+ }
124
+
125
+ // call mysql_stmt_prepare w/o gvl
126
+ {
127
+ struct nogvl_prepare_statement_args args;
128
+ args.stmt = stmt_wrapper->stmt;
129
+ // ensure the string is in the encoding the connection is expecting
130
+ args.sql = rb_str_export_to_enc(sql, conn_enc);
131
+ args.sql_ptr = RSTRING_PTR(sql);
132
+ args.sql_len = RSTRING_LEN(sql);
133
+
134
+ if ((VALUE)rb_thread_call_without_gvl(nogvl_prepare_statement, &args, RUBY_UBF_IO, 0) == Qfalse) {
135
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
136
+ }
137
+ }
138
+
139
+ return rb_stmt;
140
+ }
141
+
142
+ /* call-seq: stmt.param_count # => Numeric
143
+ *
144
+ * Returns the number of parameters the prepared statement expects.
145
+ */
146
+ static VALUE rb_mysql_stmt_param_count(VALUE self) {
147
+ GET_STATEMENT(self);
148
+
149
+ return ULL2NUM(mysql_stmt_param_count(stmt_wrapper->stmt));
150
+ }
151
+
152
+ /* call-seq: stmt.field_count # => Numeric
153
+ *
154
+ * Returns the number of fields the prepared statement returns.
155
+ */
156
+ static VALUE rb_mysql_stmt_field_count(VALUE self) {
157
+ GET_STATEMENT(self);
158
+
159
+ return UINT2NUM(mysql_stmt_field_count(stmt_wrapper->stmt));
160
+ }
161
+
162
+ static void *nogvl_stmt_execute(void *ptr) {
163
+ MYSQL_STMT *stmt = ptr;
164
+
165
+ if (mysql_stmt_execute(stmt)) {
166
+ return (void*)Qfalse;
167
+ } else {
168
+ return (void*)Qtrue;
169
+ }
170
+ }
171
+
172
+ static void set_buffer_for_string(MYSQL_BIND* bind_buffer, unsigned long *length_buffer, VALUE string) {
173
+ unsigned long length;
174
+
175
+ bind_buffer->buffer = RSTRING_PTR(string);
176
+
177
+ length = RSTRING_LEN(string);
178
+ bind_buffer->buffer_length = length;
179
+ *length_buffer = length;
180
+
181
+ bind_buffer->length = length_buffer;
182
+ }
183
+
184
+ /* Free each bind_buffer[i].buffer except when params_enc is non-nil, this means
185
+ * the buffer is a Ruby string pointer and not our memory to manage.
186
+ */
187
+ #define FREE_BINDS \
188
+ for (i = 0; i < bind_count; i++) { \
189
+ if (bind_buffers[i].buffer && NIL_P(params_enc[i])) { \
190
+ xfree(bind_buffers[i].buffer); \
191
+ } \
192
+ } \
193
+ if (argc > 0) { \
194
+ xfree(bind_buffers); \
195
+ xfree(length_buffers); \
196
+ }
197
+
198
+ /* return 0 if the given bignum can cast as LONG_LONG, otherwise 1 */
199
+ static int my_big2ll(VALUE bignum, LONG_LONG *ptr)
200
+ {
201
+ unsigned LONG_LONG num;
202
+ size_t len;
203
+ // rb_absint_size was added in 2.1.0. See:
204
+ // https://github.com/ruby/ruby/commit/9fea875
205
+ #ifdef HAVE_RB_ABSINT_SIZE
206
+ int nlz_bits = 0;
207
+ len = rb_absint_size(bignum, &nlz_bits);
208
+ #else
209
+ len = RBIGNUM_LEN(bignum) * SIZEOF_BDIGITS;
210
+ #endif
211
+ if (len > sizeof(LONG_LONG)) goto overflow;
212
+ if (RBIGNUM_POSITIVE_P(bignum)) {
213
+ num = rb_big2ull(bignum);
214
+ if (num > LLONG_MAX)
215
+ goto overflow;
216
+ *ptr = num;
217
+ }
218
+ else {
219
+ if (len == 8 &&
220
+ #ifdef HAVE_RB_ABSINT_SIZE
221
+ nlz_bits == 0 &&
222
+ #endif
223
+ // rb_absint_singlebit_p was added in 2.1.0. See:
224
+ // https://github.com/ruby/ruby/commit/e5ff9d5
225
+ #if defined(HAVE_RB_ABSINT_SIZE) && defined(HAVE_RB_ABSINT_SINGLEBIT_P)
226
+ /* Optimized to avoid object allocation for Ruby 2.1+
227
+ * only -0x8000000000000000 is safe if `len == 8 && nlz_bits == 0`
228
+ */
229
+ !rb_absint_singlebit_p(bignum)
230
+ #else
231
+ rb_big_cmp(bignum, LL2NUM(LLONG_MIN)) == INT2FIX(-1)
232
+ #endif
233
+ ) {
234
+ goto overflow;
235
+ }
236
+ *ptr = rb_big2ll(bignum);
237
+ }
238
+ return 0;
239
+ overflow:
240
+ return 1;
241
+ }
242
+
243
+ /* call-seq: stmt.execute
244
+ *
245
+ * Executes the current prepared statement, returns +result+.
246
+ */
247
+ static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) {
248
+ MYSQL_BIND *bind_buffers = NULL;
249
+ unsigned long *length_buffers = NULL;
250
+ unsigned long bind_count;
251
+ unsigned long i;
252
+ MYSQL_STMT *stmt;
253
+ MYSQL_RES *metadata;
254
+ VALUE opts;
255
+ VALUE current;
256
+ VALUE resultObj;
257
+ VALUE *params_enc = NULL;
258
+ int is_streaming;
259
+ rb_encoding *conn_enc;
260
+
261
+ GET_STATEMENT(self);
262
+ GET_CLIENT(stmt_wrapper->client);
263
+
264
+ conn_enc = rb_to_encoding(wrapper->encoding);
265
+
266
+ stmt = stmt_wrapper->stmt;
267
+ bind_count = mysql_stmt_param_count(stmt);
268
+
269
+ // Get count of ordinary arguments, and extract hash opts/keyword arguments
270
+ // Use a local scope to avoid leaking the temporary count variable
271
+ {
272
+ int c = rb_scan_args(argc, argv, "*:", NULL, &opts);
273
+ if (c != (long)bind_count) {
274
+ rb_raise(cMysql2Error, "Bind parameter count (%ld) doesn't match number of arguments (%d)", bind_count, c);
275
+ }
276
+ }
277
+
278
+ // setup any bind variables in the query
279
+ if (bind_count > 0) {
280
+ // Scratch space for string encoding exports, allocate on the stack
281
+ params_enc = alloca(sizeof(VALUE) * bind_count);
282
+ bind_buffers = xcalloc(bind_count, sizeof(MYSQL_BIND));
283
+ length_buffers = xcalloc(bind_count, sizeof(unsigned long));
284
+
285
+ for (i = 0; i < bind_count; i++) {
286
+ bind_buffers[i].buffer = NULL;
287
+ params_enc[i] = Qnil;
288
+
289
+ switch (TYPE(argv[i])) {
290
+ case T_NIL:
291
+ bind_buffers[i].buffer_type = MYSQL_TYPE_NULL;
292
+ break;
293
+ case T_FIXNUM:
294
+ #if SIZEOF_INT < SIZEOF_LONG
295
+ bind_buffers[i].buffer_type = MYSQL_TYPE_LONGLONG;
296
+ bind_buffers[i].buffer = xmalloc(sizeof(long long int));
297
+ *(long*)(bind_buffers[i].buffer) = FIX2LONG(argv[i]);
298
+ #else
299
+ bind_buffers[i].buffer_type = MYSQL_TYPE_LONG;
300
+ bind_buffers[i].buffer = xmalloc(sizeof(int));
301
+ *(long*)(bind_buffers[i].buffer) = FIX2INT(argv[i]);
302
+ #endif
303
+ break;
304
+ case T_BIGNUM:
305
+ {
306
+ LONG_LONG num;
307
+ if (my_big2ll(argv[i], &num) == 0) {
308
+ bind_buffers[i].buffer_type = MYSQL_TYPE_LONGLONG;
309
+ bind_buffers[i].buffer = xmalloc(sizeof(long long int));
310
+ *(LONG_LONG*)(bind_buffers[i].buffer) = num;
311
+ } else {
312
+ /* The bignum was larger than we can fit in LONG_LONG, send it as a string */
313
+ bind_buffers[i].buffer_type = MYSQL_TYPE_NEWDECIMAL;
314
+ params_enc[i] = rb_str_export_to_enc(rb_big2str(argv[i], 10), conn_enc);
315
+ set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
316
+ }
317
+ }
318
+ break;
319
+ case T_FLOAT:
320
+ bind_buffers[i].buffer_type = MYSQL_TYPE_DOUBLE;
321
+ bind_buffers[i].buffer = xmalloc(sizeof(double));
322
+ *(double*)(bind_buffers[i].buffer) = NUM2DBL(argv[i]);
323
+ break;
324
+ case T_STRING:
325
+ bind_buffers[i].buffer_type = MYSQL_TYPE_STRING;
326
+
327
+ params_enc[i] = argv[i];
328
+ params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
329
+ set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
330
+ break;
331
+ case T_TRUE:
332
+ bind_buffers[i].buffer_type = MYSQL_TYPE_TINY;
333
+ bind_buffers[i].buffer = xmalloc(sizeof(signed char));
334
+ *(signed char*)(bind_buffers[i].buffer) = 1;
335
+ break;
336
+ case T_FALSE:
337
+ bind_buffers[i].buffer_type = MYSQL_TYPE_TINY;
338
+ bind_buffers[i].buffer = xmalloc(sizeof(signed char));
339
+ *(signed char*)(bind_buffers[i].buffer) = 0;
340
+ break;
341
+ default:
342
+ // TODO: what Ruby type should support MYSQL_TYPE_TIME
343
+ if (CLASS_OF(argv[i]) == rb_cTime || CLASS_OF(argv[i]) == cDateTime) {
344
+ MYSQL_TIME t;
345
+ VALUE rb_time = argv[i];
346
+
347
+ bind_buffers[i].buffer_type = MYSQL_TYPE_DATETIME;
348
+ bind_buffers[i].buffer = xmalloc(sizeof(MYSQL_TIME));
349
+
350
+ memset(&t, 0, sizeof(MYSQL_TIME));
351
+ t.neg = 0;
352
+
353
+ if (CLASS_OF(argv[i]) == rb_cTime) {
354
+ t.second_part = FIX2INT(rb_funcall(rb_time, intern_usec, 0));
355
+ } else if (CLASS_OF(argv[i]) == cDateTime) {
356
+ t.second_part = NUM2DBL(rb_funcall(rb_time, intern_sec_fraction, 0)) * 1000000;
357
+ }
358
+
359
+ t.second = FIX2INT(rb_funcall(rb_time, intern_sec, 0));
360
+ t.minute = FIX2INT(rb_funcall(rb_time, intern_min, 0));
361
+ t.hour = FIX2INT(rb_funcall(rb_time, intern_hour, 0));
362
+ t.day = FIX2INT(rb_funcall(rb_time, intern_day, 0));
363
+ t.month = FIX2INT(rb_funcall(rb_time, intern_month, 0));
364
+ t.year = FIX2INT(rb_funcall(rb_time, intern_year, 0));
365
+
366
+ *(MYSQL_TIME*)(bind_buffers[i].buffer) = t;
367
+ } else if (CLASS_OF(argv[i]) == cDate) {
368
+ MYSQL_TIME t;
369
+ VALUE rb_time = argv[i];
370
+
371
+ bind_buffers[i].buffer_type = MYSQL_TYPE_DATE;
372
+ bind_buffers[i].buffer = xmalloc(sizeof(MYSQL_TIME));
373
+
374
+ memset(&t, 0, sizeof(MYSQL_TIME));
375
+ t.second_part = 0;
376
+ t.neg = 0;
377
+ t.day = FIX2INT(rb_funcall(rb_time, intern_day, 0));
378
+ t.month = FIX2INT(rb_funcall(rb_time, intern_month, 0));
379
+ t.year = FIX2INT(rb_funcall(rb_time, intern_year, 0));
380
+
381
+ *(MYSQL_TIME*)(bind_buffers[i].buffer) = t;
382
+ } else if (CLASS_OF(argv[i]) == cBigDecimal) {
383
+ bind_buffers[i].buffer_type = MYSQL_TYPE_NEWDECIMAL;
384
+
385
+ // DECIMAL are represented with the "string representation of the
386
+ // original server-side value", see
387
+ // https://dev.mysql.com/doc/refman/5.7/en/c-api-prepared-statement-type-conversions.html
388
+ // This should be independent of the locale used both on the server
389
+ // and the client side.
390
+ VALUE rb_val_as_string = rb_funcall(argv[i], intern_to_s, 0);
391
+
392
+ params_enc[i] = rb_val_as_string;
393
+ params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
394
+ set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
395
+ }
396
+ break;
397
+ }
398
+ }
399
+
400
+ // copies bind_buffers into internal storage
401
+ if (mysql_stmt_bind_param(stmt, bind_buffers)) {
402
+ FREE_BINDS;
403
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
404
+ }
405
+ }
406
+
407
+ // Duplicate the options hash, merge! extra opts, put the copy into the Result object
408
+ current = rb_hash_dup(rb_ivar_get(stmt_wrapper->client, intern_query_options));
409
+ (void)RB_GC_GUARD(current);
410
+ Check_Type(current, T_HASH);
411
+
412
+ // Merge in hash opts/keyword arguments
413
+ if (!NIL_P(opts)) {
414
+ rb_funcall(current, intern_merge_bang, 1, opts);
415
+ }
416
+
417
+ is_streaming = (Qtrue == rb_hash_aref(current, sym_stream));
418
+
419
+ // From stmt_execute to mysql_stmt_result_metadata to stmt_store_result, no
420
+ // Ruby API calls are allowed so that GC is not invoked. If the connection is
421
+ // in results-streaming-mode for Statement A, and in the middle Statement B
422
+ // gets garbage collected, a message will be sent to the server notifying it
423
+ // to release Statement B, resulting in the following error:
424
+ // Commands out of sync; you can't run this command now
425
+ //
426
+ // In streaming mode, statement execute must return a cursor because we
427
+ // cannot prevent other Statement objects from being garbage collected
428
+ // between fetches of each row of the result set. The following error
429
+ // occurs if cursor mode is not set:
430
+ // Row retrieval was canceled by mysql_stmt_close
431
+
432
+ if (is_streaming) {
433
+ unsigned long type = CURSOR_TYPE_READ_ONLY;
434
+ if (mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, &type)) {
435
+ FREE_BINDS;
436
+ rb_raise(cMysql2Error, "Unable to stream prepared statement, could not set CURSOR_TYPE_READ_ONLY");
437
+ }
438
+ }
439
+
440
+ if ((VALUE)rb_thread_call_without_gvl(nogvl_stmt_execute, stmt, RUBY_UBF_IO, 0) == Qfalse) {
441
+ FREE_BINDS;
442
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
443
+ }
444
+
445
+ FREE_BINDS;
446
+
447
+ metadata = mysql_stmt_result_metadata(stmt);
448
+ if (metadata == NULL) {
449
+ if (mysql_stmt_errno(stmt) != 0) {
450
+ // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal.
451
+ wrapper->active_thread = Qnil;
452
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
453
+ }
454
+ // no data and no error, so query was not a SELECT
455
+ return Qnil;
456
+ }
457
+
458
+ if (!is_streaming) {
459
+ // recieve the whole result set from the server
460
+ if (mysql_stmt_store_result(stmt)) {
461
+ mysql_free_result(metadata);
462
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
463
+ }
464
+ wrapper->active_thread = Qnil;
465
+ }
466
+
467
+ resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, metadata, self);
468
+
469
+ rb_mysql_set_server_query_flags(wrapper->client, resultObj);
470
+
471
+ if (!is_streaming) {
472
+ // cache all result
473
+ rb_funcall(resultObj, intern_each, 0);
474
+ }
475
+
476
+ return resultObj;
477
+ }
478
+
479
+ /* call-seq: stmt.fields # => array
480
+ *
481
+ * Returns a list of fields that will be returned by this statement.
482
+ */
483
+ static VALUE rb_mysql_stmt_fields(VALUE self) {
484
+ MYSQL_FIELD *fields;
485
+ MYSQL_RES *metadata;
486
+ unsigned int field_count;
487
+ unsigned int i;
488
+ VALUE field_list;
489
+ MYSQL_STMT* stmt;
490
+ rb_encoding *default_internal_enc, *conn_enc;
491
+ GET_STATEMENT(self);
492
+ GET_CLIENT(stmt_wrapper->client);
493
+ stmt = stmt_wrapper->stmt;
494
+
495
+ default_internal_enc = rb_default_internal_encoding();
496
+ {
497
+ GET_CLIENT(stmt_wrapper->client);
498
+ conn_enc = rb_to_encoding(wrapper->encoding);
499
+ }
500
+
501
+ metadata = mysql_stmt_result_metadata(stmt);
502
+ if (metadata == NULL) {
503
+ if (mysql_stmt_errno(stmt) != 0) {
504
+ // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal.
505
+ wrapper->active_thread = Qnil;
506
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
507
+ }
508
+ // no data and no error, so query was not a SELECT
509
+ return Qnil;
510
+ }
511
+
512
+ fields = mysql_fetch_fields(metadata);
513
+ field_count = mysql_stmt_field_count(stmt);
514
+ field_list = rb_ary_new2((long)field_count);
515
+
516
+ for (i = 0; i < field_count; i++) {
517
+ VALUE rb_field;
518
+
519
+ rb_field = rb_str_new(fields[i].name, fields[i].name_length);
520
+ rb_enc_associate(rb_field, conn_enc);
521
+ if (default_internal_enc) {
522
+ rb_field = rb_str_export_to_enc(rb_field, default_internal_enc);
523
+ }
524
+
525
+ rb_ary_store(field_list, (long)i, rb_field);
526
+ }
527
+
528
+ mysql_free_result(metadata);
529
+ return field_list;
530
+ }
531
+
532
+ /* call-seq:
533
+ * stmt.last_id
534
+ *
535
+ * Returns the AUTO_INCREMENT value from the executed INSERT or UPDATE.
536
+ */
537
+ static VALUE rb_mysql_stmt_last_id(VALUE self) {
538
+ GET_STATEMENT(self);
539
+ return ULL2NUM(mysql_stmt_insert_id(stmt_wrapper->stmt));
540
+ }
541
+
542
+ /* call-seq:
543
+ * stmt.affected_rows
544
+ *
545
+ * Returns the number of rows changed, deleted, or inserted.
546
+ */
547
+ static VALUE rb_mysql_stmt_affected_rows(VALUE self) {
548
+ my_ulonglong affected;
549
+ GET_STATEMENT(self);
550
+
551
+ affected = mysql_stmt_affected_rows(stmt_wrapper->stmt);
552
+ if (affected == (my_ulonglong)-1) {
553
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
554
+ }
555
+
556
+ return ULL2NUM(affected);
557
+ }
558
+
559
+ /* call-seq:
560
+ * stmt.close
561
+ *
562
+ * Explicitly closing this will free up server resources immediately rather
563
+ * than waiting for the garbage collector. Useful if you're managing your
564
+ * own prepared statement cache.
565
+ */
566
+ static VALUE rb_mysql_stmt_close(VALUE self) {
567
+ GET_STATEMENT(self);
568
+ stmt_wrapper->closed = 1;
569
+ rb_thread_call_without_gvl(nogvl_stmt_close, stmt_wrapper, RUBY_UBF_IO, 0);
570
+ return Qnil;
571
+ }
572
+
573
+ void init_mysql2_statement() {
574
+ cDate = rb_const_get(rb_cObject, rb_intern("Date"));
575
+ cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
576
+ cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal"));
577
+
578
+ cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject);
579
+ rb_define_method(cMysql2Statement, "param_count", rb_mysql_stmt_param_count, 0);
580
+ rb_define_method(cMysql2Statement, "field_count", rb_mysql_stmt_field_count, 0);
581
+ rb_define_method(cMysql2Statement, "_execute", rb_mysql_stmt_execute, -1);
582
+ rb_define_method(cMysql2Statement, "fields", rb_mysql_stmt_fields, 0);
583
+ rb_define_method(cMysql2Statement, "last_id", rb_mysql_stmt_last_id, 0);
584
+ rb_define_method(cMysql2Statement, "affected_rows", rb_mysql_stmt_affected_rows, 0);
585
+ rb_define_method(cMysql2Statement, "close", rb_mysql_stmt_close, 0);
586
+
587
+ sym_stream = ID2SYM(rb_intern("stream"));
588
+
589
+ intern_new_with_args = rb_intern("new_with_args");
590
+ intern_each = rb_intern("each");
591
+
592
+ intern_sec_fraction = rb_intern("sec_fraction");
593
+ intern_usec = rb_intern("usec");
594
+ intern_sec = rb_intern("sec");
595
+ intern_min = rb_intern("min");
596
+ intern_hour = rb_intern("hour");
597
+ intern_day = rb_intern("day");
598
+ intern_month = rb_intern("month");
599
+ intern_year = rb_intern("year");
600
+
601
+ intern_to_s = rb_intern("to_s");
602
+ intern_merge_bang = rb_intern("merge!");
603
+ intern_query_options = rb_intern("@query_options");
604
+ }
@@ -0,0 +1,17 @@
1
+ #ifndef MYSQL2_STATEMENT_H
2
+ #define MYSQL2_STATEMENT_H
3
+
4
+ typedef struct {
5
+ VALUE client;
6
+ MYSQL_STMT *stmt;
7
+ int refcount;
8
+ int closed;
9
+ } mysql_stmt_wrapper;
10
+
11
+ void init_mysql2_statement(void);
12
+ void decr_mysql2_stmt(mysql_stmt_wrapper *stmt_wrapper);
13
+
14
+ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql);
15
+ void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) RB_MYSQL_NORETURN;
16
+
17
+ #endif
@@ -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.