mysql2 0.3.1 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1 -151
  3. data/LICENSE +21 -0
  4. data/README.md +634 -0
  5. data/examples/eventmachine.rb +1 -3
  6. data/examples/threaded.rb +5 -9
  7. data/ext/mysql2/client.c +1154 -342
  8. data/ext/mysql2/client.h +20 -33
  9. data/ext/mysql2/extconf.rb +229 -37
  10. data/ext/mysql2/infile.c +122 -0
  11. data/ext/mysql2/infile.h +1 -0
  12. data/ext/mysql2/mysql2_ext.c +3 -1
  13. data/ext/mysql2/mysql2_ext.h +18 -16
  14. data/ext/mysql2/mysql_enc_name_to_ruby.h +168 -0
  15. data/ext/mysql2/mysql_enc_to_ruby.h +259 -0
  16. data/ext/mysql2/result.c +708 -191
  17. data/ext/mysql2/result.h +15 -6
  18. data/ext/mysql2/statement.c +602 -0
  19. data/ext/mysql2/statement.h +17 -0
  20. data/ext/mysql2/wait_for_single_fd.h +37 -0
  21. data/lib/mysql2.rb +69 -7
  22. data/lib/mysql2/client.rb +126 -211
  23. data/lib/mysql2/console.rb +5 -0
  24. data/lib/mysql2/em.rb +24 -8
  25. data/lib/mysql2/error.rb +93 -8
  26. data/lib/mysql2/field.rb +3 -0
  27. data/lib/mysql2/result.rb +2 -0
  28. data/lib/mysql2/statement.rb +11 -0
  29. data/lib/mysql2/version.rb +2 -2
  30. data/spec/configuration.yml.example +11 -0
  31. data/spec/em/em_spec.rb +101 -15
  32. data/spec/my.cnf.example +9 -0
  33. data/spec/mysql2/client_spec.rb +874 -232
  34. data/spec/mysql2/error_spec.rb +55 -46
  35. data/spec/mysql2/result_spec.rb +306 -154
  36. data/spec/mysql2/statement_spec.rb +712 -0
  37. data/spec/spec_helper.rb +103 -57
  38. data/spec/ssl/ca-cert.pem +17 -0
  39. data/spec/ssl/ca-key.pem +27 -0
  40. data/spec/ssl/ca.cnf +22 -0
  41. data/spec/ssl/cert.cnf +22 -0
  42. data/spec/ssl/client-cert.pem +17 -0
  43. data/spec/ssl/client-key.pem +27 -0
  44. data/spec/ssl/client-req.pem +15 -0
  45. data/spec/ssl/gen_certs.sh +48 -0
  46. data/spec/ssl/pkcs8-client-key.pem +28 -0
  47. data/spec/ssl/pkcs8-server-key.pem +28 -0
  48. data/spec/ssl/server-cert.pem +17 -0
  49. data/spec/ssl/server-key.pem +27 -0
  50. data/spec/ssl/server-req.pem +15 -0
  51. data/spec/test_data +1 -0
  52. data/support/5072E1F5.asc +432 -0
  53. data/support/libmysql.def +219 -0
  54. data/support/mysql_enc_to_ruby.rb +81 -0
  55. data/support/ruby_enc_to_mysql.rb +61 -0
  56. metadata +82 -188
  57. data/.gitignore +0 -12
  58. data/.rspec +0 -2
  59. data/.rvmrc +0 -1
  60. data/Gemfile +0 -3
  61. data/MIT-LICENSE +0 -20
  62. data/README.rdoc +0 -257
  63. data/Rakefile +0 -5
  64. data/benchmark/active_record.rb +0 -51
  65. data/benchmark/active_record_threaded.rb +0 -42
  66. data/benchmark/allocations.rb +0 -33
  67. data/benchmark/escape.rb +0 -36
  68. data/benchmark/query_with_mysql_casting.rb +0 -80
  69. data/benchmark/query_without_mysql_casting.rb +0 -47
  70. data/benchmark/sequel.rb +0 -37
  71. data/benchmark/setup_db.rb +0 -119
  72. data/benchmark/threaded.rb +0 -44
  73. data/lib/active_record/connection_adapters/em_mysql2_adapter.rb +0 -64
  74. data/lib/active_record/fiber_patches.rb +0 -104
  75. data/lib/mysql2/em_fiber.rb +0 -31
  76. data/mysql2.gemspec +0 -32
  77. data/spec/em/em_fiber_spec.rb +0 -22
  78. data/tasks/benchmarks.rake +0 -20
  79. data/tasks/compile.rake +0 -71
  80. data/tasks/rspec.rake +0 -16
  81. 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,602 @@
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
+
8
+ #define GET_STATEMENT(self) \
9
+ mysql_stmt_wrapper *stmt_wrapper; \
10
+ Data_Get_Struct(self, mysql_stmt_wrapper, stmt_wrapper); \
11
+ if (!stmt_wrapper->stmt) { rb_raise(cMysql2Error, "Invalid statement handle"); } \
12
+ if (stmt_wrapper->closed) { rb_raise(cMysql2Error, "Statement handle already closed"); }
13
+
14
+ static void rb_mysql_stmt_mark(void * ptr) {
15
+ mysql_stmt_wrapper *stmt_wrapper = ptr;
16
+ if (!stmt_wrapper) return;
17
+
18
+ rb_gc_mark(stmt_wrapper->client);
19
+ }
20
+
21
+ static void *nogvl_stmt_close(void *ptr) {
22
+ mysql_stmt_wrapper *stmt_wrapper = ptr;
23
+ if (stmt_wrapper->stmt) {
24
+ mysql_stmt_close(stmt_wrapper->stmt);
25
+ stmt_wrapper->stmt = NULL;
26
+ }
27
+ return NULL;
28
+ }
29
+
30
+ static void rb_mysql_stmt_free(void *ptr) {
31
+ mysql_stmt_wrapper *stmt_wrapper = ptr;
32
+ decr_mysql2_stmt(stmt_wrapper);
33
+ }
34
+
35
+ void decr_mysql2_stmt(mysql_stmt_wrapper *stmt_wrapper) {
36
+ stmt_wrapper->refcount--;
37
+
38
+ if (stmt_wrapper->refcount == 0) {
39
+ nogvl_stmt_close(stmt_wrapper);
40
+ xfree(stmt_wrapper);
41
+ }
42
+ }
43
+
44
+ void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) {
45
+ VALUE e;
46
+ GET_CLIENT(stmt_wrapper->client);
47
+ VALUE rb_error_msg = rb_str_new2(mysql_stmt_error(stmt_wrapper->stmt));
48
+ VALUE rb_sql_state = rb_tainted_str_new2(mysql_stmt_sqlstate(stmt_wrapper->stmt));
49
+
50
+ rb_encoding *conn_enc;
51
+ conn_enc = rb_to_encoding(wrapper->encoding);
52
+
53
+ rb_encoding *default_internal_enc = rb_default_internal_encoding();
54
+
55
+ rb_enc_associate(rb_error_msg, conn_enc);
56
+ rb_enc_associate(rb_sql_state, conn_enc);
57
+ if (default_internal_enc) {
58
+ rb_error_msg = rb_str_export_to_enc(rb_error_msg, default_internal_enc);
59
+ rb_sql_state = rb_str_export_to_enc(rb_sql_state, default_internal_enc);
60
+ }
61
+
62
+ e = rb_funcall(cMysql2Error, intern_new_with_args, 4,
63
+ rb_error_msg,
64
+ LONG2FIX(wrapper->server_version),
65
+ UINT2NUM(mysql_stmt_errno(stmt_wrapper->stmt)),
66
+ rb_sql_state);
67
+ rb_exc_raise(e);
68
+ }
69
+
70
+ /*
71
+ * used to pass all arguments to mysql_stmt_prepare while inside
72
+ * nogvl_prepare_statement_args
73
+ */
74
+ struct nogvl_prepare_statement_args {
75
+ MYSQL_STMT *stmt;
76
+ VALUE sql;
77
+ const char *sql_ptr;
78
+ unsigned long sql_len;
79
+ };
80
+
81
+ static void *nogvl_prepare_statement(void *ptr) {
82
+ struct nogvl_prepare_statement_args *args = ptr;
83
+
84
+ if (mysql_stmt_prepare(args->stmt, args->sql_ptr, args->sql_len)) {
85
+ return (void*)Qfalse;
86
+ } else {
87
+ return (void*)Qtrue;
88
+ }
89
+ }
90
+
91
+ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
92
+ mysql_stmt_wrapper *stmt_wrapper;
93
+ VALUE rb_stmt;
94
+ rb_encoding *conn_enc;
95
+
96
+ Check_Type(sql, T_STRING);
97
+
98
+ rb_stmt = Data_Make_Struct(cMysql2Statement, mysql_stmt_wrapper, rb_mysql_stmt_mark, rb_mysql_stmt_free, stmt_wrapper);
99
+ {
100
+ stmt_wrapper->client = rb_client;
101
+ stmt_wrapper->refcount = 1;
102
+ stmt_wrapper->closed = 0;
103
+ stmt_wrapper->stmt = NULL;
104
+ }
105
+
106
+ // instantiate stmt
107
+ {
108
+ GET_CLIENT(rb_client);
109
+ stmt_wrapper->stmt = mysql_stmt_init(wrapper->client);
110
+ conn_enc = rb_to_encoding(wrapper->encoding);
111
+ }
112
+ if (stmt_wrapper->stmt == NULL) {
113
+ rb_raise(cMysql2Error, "Unable to initialize prepared statement: out of memory");
114
+ }
115
+
116
+ // set STMT_ATTR_UPDATE_MAX_LENGTH attr
117
+ {
118
+ my_bool truth = 1;
119
+ if (mysql_stmt_attr_set(stmt_wrapper->stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &truth)) {
120
+ rb_raise(cMysql2Error, "Unable to initialize prepared statement: set STMT_ATTR_UPDATE_MAX_LENGTH");
121
+ }
122
+ }
123
+
124
+ // call mysql_stmt_prepare w/o gvl
125
+ {
126
+ struct nogvl_prepare_statement_args args;
127
+ args.stmt = stmt_wrapper->stmt;
128
+ // ensure the string is in the encoding the connection is expecting
129
+ args.sql = rb_str_export_to_enc(sql, conn_enc);
130
+ args.sql_ptr = RSTRING_PTR(sql);
131
+ args.sql_len = RSTRING_LEN(sql);
132
+
133
+ if ((VALUE)rb_thread_call_without_gvl(nogvl_prepare_statement, &args, RUBY_UBF_IO, 0) == Qfalse) {
134
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
135
+ }
136
+ }
137
+
138
+ return rb_stmt;
139
+ }
140
+
141
+ /* call-seq: stmt.param_count # => Numeric
142
+ *
143
+ * Returns the number of parameters the prepared statement expects.
144
+ */
145
+ static VALUE rb_mysql_stmt_param_count(VALUE self) {
146
+ GET_STATEMENT(self);
147
+
148
+ return ULL2NUM(mysql_stmt_param_count(stmt_wrapper->stmt));
149
+ }
150
+
151
+ /* call-seq: stmt.field_count # => Numeric
152
+ *
153
+ * Returns the number of fields the prepared statement returns.
154
+ */
155
+ static VALUE rb_mysql_stmt_field_count(VALUE self) {
156
+ GET_STATEMENT(self);
157
+
158
+ return UINT2NUM(mysql_stmt_field_count(stmt_wrapper->stmt));
159
+ }
160
+
161
+ static void *nogvl_stmt_execute(void *ptr) {
162
+ MYSQL_STMT *stmt = ptr;
163
+
164
+ if (mysql_stmt_execute(stmt)) {
165
+ return (void*)Qfalse;
166
+ } else {
167
+ return (void*)Qtrue;
168
+ }
169
+ }
170
+
171
+ static void set_buffer_for_string(MYSQL_BIND* bind_buffer, unsigned long *length_buffer, VALUE string) {
172
+ unsigned long length;
173
+
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;
181
+ }
182
+
183
+ /* Free each bind_buffer[i].buffer except when params_enc is non-nil, this means
184
+ * the buffer is a Ruby string pointer and not our memory to manage.
185
+ */
186
+ #define FREE_BINDS \
187
+ for (i = 0; i < bind_count; i++) { \
188
+ if (bind_buffers[i].buffer && NIL_P(params_enc[i])) { \
189
+ xfree(bind_buffers[i].buffer); \
190
+ } \
191
+ } \
192
+ if (argc > 0) { \
193
+ xfree(bind_buffers); \
194
+ xfree(length_buffers); \
195
+ }
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
+
242
+ /* call-seq: stmt.execute
243
+ *
244
+ * Executes the current prepared statement, returns +result+.
245
+ */
246
+ static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) {
247
+ MYSQL_BIND *bind_buffers = NULL;
248
+ unsigned long *length_buffers = NULL;
249
+ unsigned long bind_count;
250
+ unsigned long i;
251
+ MYSQL_STMT *stmt;
252
+ MYSQL_RES *metadata;
253
+ VALUE opts;
254
+ VALUE current;
255
+ VALUE resultObj;
256
+ VALUE *params_enc = NULL;
257
+ int is_streaming;
258
+ rb_encoding *conn_enc;
259
+
260
+ GET_STATEMENT(self);
261
+ GET_CLIENT(stmt_wrapper->client);
262
+
263
+ conn_enc = rb_to_encoding(wrapper->encoding);
264
+
265
+ stmt = stmt_wrapper->stmt;
266
+ bind_count = mysql_stmt_param_count(stmt);
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
+ }
275
+ }
276
+
277
+ // setup any bind variables in the query
278
+ if (bind_count > 0) {
279
+ // Scratch space for string encoding exports, allocate on the stack
280
+ params_enc = alloca(sizeof(VALUE) * bind_count);
281
+ bind_buffers = xcalloc(bind_count, sizeof(MYSQL_BIND));
282
+ length_buffers = xcalloc(bind_count, sizeof(unsigned long));
283
+
284
+ for (i = 0; i < bind_count; i++) {
285
+ bind_buffers[i].buffer = NULL;
286
+ params_enc[i] = Qnil;
287
+
288
+ switch (TYPE(argv[i])) {
289
+ case T_NIL:
290
+ bind_buffers[i].buffer_type = MYSQL_TYPE_NULL;
291
+ break;
292
+ case T_FIXNUM:
293
+ #if SIZEOF_INT < SIZEOF_LONG
294
+ bind_buffers[i].buffer_type = MYSQL_TYPE_LONGLONG;
295
+ bind_buffers[i].buffer = xmalloc(sizeof(long long int));
296
+ *(long*)(bind_buffers[i].buffer) = FIX2LONG(argv[i]);
297
+ #else
298
+ bind_buffers[i].buffer_type = MYSQL_TYPE_LONG;
299
+ bind_buffers[i].buffer = xmalloc(sizeof(int));
300
+ *(long*)(bind_buffers[i].buffer) = FIX2INT(argv[i]);
301
+ #endif
302
+ break;
303
+ case T_BIGNUM:
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
+ }
317
+ break;
318
+ case T_FLOAT:
319
+ bind_buffers[i].buffer_type = MYSQL_TYPE_DOUBLE;
320
+ bind_buffers[i].buffer = xmalloc(sizeof(double));
321
+ *(double*)(bind_buffers[i].buffer) = NUM2DBL(argv[i]);
322
+ break;
323
+ case T_STRING:
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;
339
+ break;
340
+ default:
341
+ // TODO: what Ruby type should support MYSQL_TYPE_TIME
342
+ if (CLASS_OF(argv[i]) == rb_cTime || CLASS_OF(argv[i]) == cDateTime) {
343
+ MYSQL_TIME t;
344
+ VALUE rb_time = argv[i];
345
+
346
+ bind_buffers[i].buffer_type = MYSQL_TYPE_DATETIME;
347
+ bind_buffers[i].buffer = xmalloc(sizeof(MYSQL_TIME));
348
+
349
+ memset(&t, 0, sizeof(MYSQL_TIME));
350
+ t.neg = 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
+
358
+ t.second = FIX2INT(rb_funcall(rb_time, intern_sec, 0));
359
+ t.minute = FIX2INT(rb_funcall(rb_time, intern_min, 0));
360
+ t.hour = FIX2INT(rb_funcall(rb_time, intern_hour, 0));
361
+ t.day = FIX2INT(rb_funcall(rb_time, intern_day, 0));
362
+ t.month = FIX2INT(rb_funcall(rb_time, intern_month, 0));
363
+ t.year = FIX2INT(rb_funcall(rb_time, intern_year, 0));
364
+
365
+ *(MYSQL_TIME*)(bind_buffers[i].buffer) = t;
366
+ } else if (CLASS_OF(argv[i]) == cDate) {
367
+ MYSQL_TIME t;
368
+ VALUE rb_time = argv[i];
369
+
370
+ bind_buffers[i].buffer_type = MYSQL_TYPE_DATE;
371
+ bind_buffers[i].buffer = xmalloc(sizeof(MYSQL_TIME));
372
+
373
+ memset(&t, 0, sizeof(MYSQL_TIME));
374
+ t.second_part = 0;
375
+ t.neg = 0;
376
+ t.day = FIX2INT(rb_funcall(rb_time, intern_day, 0));
377
+ t.month = FIX2INT(rb_funcall(rb_time, intern_month, 0));
378
+ t.year = FIX2INT(rb_funcall(rb_time, intern_year, 0));
379
+
380
+ *(MYSQL_TIME*)(bind_buffers[i].buffer) = t;
381
+ } else if (CLASS_OF(argv[i]) == cBigDecimal) {
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]);
394
+ }
395
+ break;
396
+ }
397
+ }
398
+
399
+ // copies bind_buffers into internal storage
400
+ if (mysql_stmt_bind_param(stmt, bind_buffers)) {
401
+ FREE_BINDS;
402
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
403
+ }
404
+ }
405
+
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) {
440
+ FREE_BINDS;
441
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
442
+ }
443
+
444
+ FREE_BINDS;
445
+
446
+ metadata = mysql_stmt_result_metadata(stmt);
447
+ if (metadata == NULL) {
448
+ if (mysql_stmt_errno(stmt) != 0) {
449
+ // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal.
450
+ wrapper->active_thread = Qnil;
451
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
452
+ }
453
+ // no data and no error, so query was not a SELECT
454
+ return Qnil;
455
+ }
456
+
457
+ if (!is_streaming) {
458
+ // recieve the whole result set from the server
459
+ if (mysql_stmt_store_result(stmt)) {
460
+ mysql_free_result(metadata);
461
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
462
+ }
463
+ wrapper->active_thread = Qnil;
464
+ }
465
+
466
+ resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, metadata, self);
467
+
468
+ rb_mysql_set_server_query_flags(wrapper->client, resultObj);
469
+
470
+ if (!is_streaming) {
471
+ // cache all result
472
+ rb_funcall(resultObj, intern_each, 0);
473
+ }
474
+
475
+ return resultObj;
476
+ }
477
+
478
+ /* call-seq: stmt.fields # => array
479
+ *
480
+ * Returns a list of fields that will be returned by this statement.
481
+ */
482
+ static VALUE rb_mysql_stmt_fields(VALUE self) {
483
+ MYSQL_FIELD *fields;
484
+ MYSQL_RES *metadata;
485
+ unsigned int field_count;
486
+ unsigned int i;
487
+ VALUE field_list;
488
+ MYSQL_STMT* stmt;
489
+ rb_encoding *default_internal_enc, *conn_enc;
490
+ GET_STATEMENT(self);
491
+ GET_CLIENT(stmt_wrapper->client);
492
+ stmt = stmt_wrapper->stmt;
493
+
494
+ default_internal_enc = rb_default_internal_encoding();
495
+ {
496
+ GET_CLIENT(stmt_wrapper->client);
497
+ conn_enc = rb_to_encoding(wrapper->encoding);
498
+ }
499
+
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
+
511
+ fields = mysql_fetch_fields(metadata);
512
+ field_count = mysql_stmt_field_count(stmt);
513
+ field_list = rb_ary_new2((long)field_count);
514
+
515
+ for (i = 0; i < field_count; i++) {
516
+ VALUE rb_field;
517
+
518
+ rb_field = rb_str_new(fields[i].name, fields[i].name_length);
519
+ rb_enc_associate(rb_field, conn_enc);
520
+ if (default_internal_enc) {
521
+ rb_field = rb_str_export_to_enc(rb_field, default_internal_enc);
522
+ }
523
+
524
+ rb_ary_store(field_list, (long)i, rb_field);
525
+ }
526
+
527
+ mysql_free_result(metadata);
528
+ return field_list;
529
+ }
530
+
531
+ /* call-seq:
532
+ * stmt.last_id
533
+ *
534
+ * Returns the AUTO_INCREMENT value from the executed INSERT or UPDATE.
535
+ */
536
+ static VALUE rb_mysql_stmt_last_id(VALUE self) {
537
+ GET_STATEMENT(self);
538
+ return ULL2NUM(mysql_stmt_insert_id(stmt_wrapper->stmt));
539
+ }
540
+
541
+ /* call-seq:
542
+ * stmt.affected_rows
543
+ *
544
+ * Returns the number of rows changed, deleted, or inserted.
545
+ */
546
+ static VALUE rb_mysql_stmt_affected_rows(VALUE self) {
547
+ my_ulonglong affected;
548
+ GET_STATEMENT(self);
549
+
550
+ affected = mysql_stmt_affected_rows(stmt_wrapper->stmt);
551
+ if (affected == (my_ulonglong)-1) {
552
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
553
+ }
554
+
555
+ return ULL2NUM(affected);
556
+ }
557
+
558
+ /* call-seq:
559
+ * stmt.close
560
+ *
561
+ * Explicitly closing this will free up server resources immediately rather
562
+ * than waiting for the garbage collector. Useful if you're managing your
563
+ * own prepared statement cache.
564
+ */
565
+ static VALUE rb_mysql_stmt_close(VALUE self) {
566
+ GET_STATEMENT(self);
567
+ stmt_wrapper->closed = 1;
568
+ rb_thread_call_without_gvl(nogvl_stmt_close, stmt_wrapper, RUBY_UBF_IO, 0);
569
+ return Qnil;
570
+ }
571
+
572
+ void init_mysql2_statement() {
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"));
576
+
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);
582
+ rb_define_method(cMysql2Statement, "last_id", rb_mysql_stmt_last_id, 0);
583
+ rb_define_method(cMysql2Statement, "affected_rows", rb_mysql_stmt_affected_rows, 0);
584
+ rb_define_method(cMysql2Statement, "close", rb_mysql_stmt_close, 0);
585
+
586
+ sym_stream = ID2SYM(rb_intern("stream"));
587
+
588
+ intern_new_with_args = rb_intern("new_with_args");
589
+ intern_each = rb_intern("each");
590
+
591
+ intern_sec_fraction = rb_intern("sec_fraction");
592
+ intern_usec = rb_intern("usec");
593
+ intern_sec = rb_intern("sec");
594
+ intern_min = rb_intern("min");
595
+ intern_hour = rb_intern("hour");
596
+ intern_day = rb_intern("day");
597
+ intern_month = rb_intern("month");
598
+ intern_year = rb_intern("year");
599
+
600
+ intern_to_s = rb_intern("to_s");
601
+ intern_merge_bang = rb_intern("merge!");
602
+ }