mysql2 0.3.8 → 0.4.10

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