mysql2 0.3.18 → 0.4.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -0
  3. data/LICENSE +21 -0
  4. data/README.md +135 -55
  5. data/examples/eventmachine.rb +1 -1
  6. data/examples/threaded.rb +4 -6
  7. data/ext/mysql2/client.c +368 -197
  8. data/ext/mysql2/client.h +13 -3
  9. data/ext/mysql2/extconf.rb +118 -35
  10. data/ext/mysql2/infile.c +2 -2
  11. data/ext/mysql2/mysql2_ext.c +1 -0
  12. data/ext/mysql2/mysql2_ext.h +7 -6
  13. data/ext/mysql2/mysql_enc_name_to_ruby.h +2 -2
  14. data/ext/mysql2/mysql_enc_to_ruby.h +25 -22
  15. data/ext/mysql2/result.c +512 -138
  16. data/ext/mysql2/result.h +13 -6
  17. data/ext/mysql2/statement.c +585 -0
  18. data/ext/mysql2/statement.h +19 -0
  19. data/lib/mysql2/client.rb +85 -26
  20. data/lib/mysql2/console.rb +1 -1
  21. data/lib/mysql2/em.rb +5 -6
  22. data/lib/mysql2/error.rb +18 -27
  23. data/lib/mysql2/field.rb +3 -0
  24. data/lib/mysql2/statement.rb +17 -0
  25. data/lib/mysql2/version.rb +1 -1
  26. data/lib/mysql2.rb +38 -18
  27. data/spec/configuration.yml.example +0 -6
  28. data/spec/em/em_spec.rb +22 -21
  29. data/spec/mysql2/client_spec.rb +525 -388
  30. data/spec/mysql2/error_spec.rb +38 -39
  31. data/spec/mysql2/result_spec.rb +223 -214
  32. data/spec/mysql2/statement_spec.rb +757 -0
  33. data/spec/spec_helper.rb +80 -59
  34. data/spec/ssl/ca-cert.pem +17 -0
  35. data/spec/ssl/ca-key.pem +27 -0
  36. data/spec/ssl/ca.cnf +22 -0
  37. data/spec/ssl/cert.cnf +22 -0
  38. data/spec/ssl/client-cert.pem +17 -0
  39. data/spec/ssl/client-key.pem +27 -0
  40. data/spec/ssl/client-req.pem +15 -0
  41. data/spec/ssl/gen_certs.sh +48 -0
  42. data/spec/ssl/pkcs8-client-key.pem +28 -0
  43. data/spec/ssl/pkcs8-server-key.pem +28 -0
  44. data/spec/ssl/server-cert.pem +17 -0
  45. data/spec/ssl/server-key.pem +27 -0
  46. data/spec/ssl/server-req.pem +15 -0
  47. data/support/mysql_enc_to_ruby.rb +7 -8
  48. data/support/ruby_enc_to_mysql.rb +1 -1
  49. metadata +42 -47
data/ext/mysql2/result.h CHANGED
@@ -1,23 +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(VALUE client, VALUE encoding, VALUE options, 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;
10
11
  VALUE client;
11
12
  VALUE encoding;
12
- unsigned int numberOfFields;
13
- unsigned long numberOfRows;
13
+ VALUE statement;
14
+ my_ulonglong numberOfFields;
15
+ my_ulonglong numberOfRows;
14
16
  unsigned long lastRowProcessed;
17
+ char is_streaming;
15
18
  char streamingComplete;
16
19
  char resultFreed;
17
20
  MYSQL_RES *result;
21
+ mysql_stmt_wrapper *stmt_wrapper;
18
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;
19
28
  } mysql2_result_wrapper;
20
29
 
21
- #define GetMysql2Result(obj, sval) (sval = (mysql2_result_wrapper*)DATA_PTR(obj));
22
-
23
30
  #endif
@@ -0,0 +1,585 @@
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
+ default:
347
+ // TODO: what Ruby type should support MYSQL_TYPE_TIME
348
+ if (CLASS_OF(argv[i]) == rb_cTime || CLASS_OF(argv[i]) == cDateTime) {
349
+ MYSQL_TIME t;
350
+ VALUE rb_time = argv[i];
351
+
352
+ bind_buffers[i].buffer_type = MYSQL_TYPE_DATETIME;
353
+ bind_buffers[i].buffer = xmalloc(sizeof(MYSQL_TIME));
354
+
355
+ memset(&t, 0, sizeof(MYSQL_TIME));
356
+ t.neg = 0;
357
+
358
+ if (CLASS_OF(argv[i]) == rb_cTime) {
359
+ t.second_part = FIX2INT(rb_funcall(rb_time, intern_usec, 0));
360
+ } else if (CLASS_OF(argv[i]) == cDateTime) {
361
+ t.second_part = NUM2DBL(rb_funcall(rb_time, intern_sec_fraction, 0)) * 1000000;
362
+ }
363
+
364
+ t.second = FIX2INT(rb_funcall(rb_time, intern_sec, 0));
365
+ t.minute = FIX2INT(rb_funcall(rb_time, intern_min, 0));
366
+ t.hour = FIX2INT(rb_funcall(rb_time, intern_hour, 0));
367
+ t.day = FIX2INT(rb_funcall(rb_time, intern_day, 0));
368
+ t.month = FIX2INT(rb_funcall(rb_time, intern_month, 0));
369
+ t.year = FIX2INT(rb_funcall(rb_time, intern_year, 0));
370
+
371
+ *(MYSQL_TIME*)(bind_buffers[i].buffer) = t;
372
+ } else if (CLASS_OF(argv[i]) == cDate) {
373
+ MYSQL_TIME t;
374
+ VALUE rb_time = argv[i];
375
+
376
+ bind_buffers[i].buffer_type = MYSQL_TYPE_DATE;
377
+ bind_buffers[i].buffer = xmalloc(sizeof(MYSQL_TIME));
378
+
379
+ memset(&t, 0, sizeof(MYSQL_TIME));
380
+ t.second_part = 0;
381
+ t.neg = 0;
382
+ t.day = FIX2INT(rb_funcall(rb_time, intern_day, 0));
383
+ t.month = FIX2INT(rb_funcall(rb_time, intern_month, 0));
384
+ t.year = FIX2INT(rb_funcall(rb_time, intern_year, 0));
385
+
386
+ *(MYSQL_TIME*)(bind_buffers[i].buffer) = t;
387
+ } else if (CLASS_OF(argv[i]) == cBigDecimal) {
388
+ bind_buffers[i].buffer_type = MYSQL_TYPE_NEWDECIMAL;
389
+
390
+ // DECIMAL are represented with the "string representation of the
391
+ // original server-side value", see
392
+ // https://dev.mysql.com/doc/refman/5.7/en/c-api-prepared-statement-type-conversions.html
393
+ // This should be independent of the locale used both on the server
394
+ // and the client side.
395
+ VALUE rb_val_as_string = rb_funcall(argv[i], intern_to_s, 0);
396
+
397
+ params_enc[i] = rb_val_as_string;
398
+ #ifdef HAVE_RUBY_ENCODING_H
399
+ params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
400
+ #endif
401
+ set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
402
+ }
403
+ break;
404
+ }
405
+ }
406
+
407
+ // copies bind_buffers into internal storage
408
+ if (mysql_stmt_bind_param(stmt, bind_buffers)) {
409
+ FREE_BINDS;
410
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
411
+ }
412
+ }
413
+
414
+ if ((VALUE)rb_thread_call_without_gvl(nogvl_execute, stmt, RUBY_UBF_IO, 0) == Qfalse) {
415
+ FREE_BINDS;
416
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
417
+ }
418
+
419
+ FREE_BINDS;
420
+
421
+ metadata = mysql_stmt_result_metadata(stmt);
422
+ if (metadata == NULL) {
423
+ if (mysql_stmt_errno(stmt) != 0) {
424
+ // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal.
425
+ wrapper->active_thread = Qnil;
426
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
427
+ }
428
+ // no data and no error, so query was not a SELECT
429
+ return Qnil;
430
+ }
431
+
432
+ current = rb_hash_dup(rb_iv_get(stmt_wrapper->client, "@query_options"));
433
+ (void)RB_GC_GUARD(current);
434
+ Check_Type(current, T_HASH);
435
+
436
+ is_streaming = (Qtrue == rb_hash_aref(current, sym_stream));
437
+ if (!is_streaming) {
438
+ // recieve the whole result set from the server
439
+ if (mysql_stmt_store_result(stmt)) {
440
+ mysql_free_result(metadata);
441
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
442
+ }
443
+ wrapper->active_thread = Qnil;
444
+ }
445
+
446
+ resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, metadata, self);
447
+
448
+ if (!is_streaming) {
449
+ // cache all result
450
+ rb_funcall(resultObj, intern_each, 0);
451
+ }
452
+
453
+ return resultObj;
454
+ }
455
+
456
+ /* call-seq: stmt.fields # => array
457
+ *
458
+ * Returns a list of fields that will be returned by this statement.
459
+ */
460
+ static VALUE fields(VALUE self) {
461
+ MYSQL_FIELD *fields;
462
+ MYSQL_RES *metadata;
463
+ unsigned int field_count;
464
+ unsigned int i;
465
+ VALUE field_list;
466
+ MYSQL_STMT* stmt;
467
+ #ifdef HAVE_RUBY_ENCODING_H
468
+ rb_encoding *default_internal_enc, *conn_enc;
469
+ #endif
470
+ GET_STATEMENT(self);
471
+ GET_CLIENT(stmt_wrapper->client);
472
+ stmt = stmt_wrapper->stmt;
473
+
474
+ #ifdef HAVE_RUBY_ENCODING_H
475
+ default_internal_enc = rb_default_internal_encoding();
476
+ {
477
+ GET_CLIENT(stmt_wrapper->client);
478
+ conn_enc = rb_to_encoding(wrapper->encoding);
479
+ }
480
+ #endif
481
+
482
+ metadata = mysql_stmt_result_metadata(stmt);
483
+ if (metadata == NULL) {
484
+ if (mysql_stmt_errno(stmt) != 0) {
485
+ // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal.
486
+ wrapper->active_thread = Qnil;
487
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
488
+ }
489
+ // no data and no error, so query was not a SELECT
490
+ return Qnil;
491
+ }
492
+
493
+ fields = mysql_fetch_fields(metadata);
494
+ field_count = mysql_stmt_field_count(stmt);
495
+ field_list = rb_ary_new2((long)field_count);
496
+
497
+ for (i = 0; i < field_count; i++) {
498
+ VALUE rb_field;
499
+
500
+ rb_field = rb_str_new(fields[i].name, fields[i].name_length);
501
+ #ifdef HAVE_RUBY_ENCODING_H
502
+ rb_enc_associate(rb_field, conn_enc);
503
+ if (default_internal_enc) {
504
+ rb_field = rb_str_export_to_enc(rb_field, default_internal_enc);
505
+ }
506
+ #endif
507
+
508
+ rb_ary_store(field_list, (long)i, rb_field);
509
+ }
510
+
511
+ mysql_free_result(metadata);
512
+ return field_list;
513
+ }
514
+
515
+ /* call-seq:
516
+ * stmt.last_id
517
+ *
518
+ * Returns the AUTO_INCREMENT value from the executed INSERT or UPDATE.
519
+ */
520
+ static VALUE rb_mysql_stmt_last_id(VALUE self) {
521
+ GET_STATEMENT(self);
522
+ return ULL2NUM(mysql_stmt_insert_id(stmt_wrapper->stmt));
523
+ }
524
+
525
+ /* call-seq:
526
+ * stmt.affected_rows
527
+ *
528
+ * Returns the number of rows changed, deleted, or inserted.
529
+ */
530
+ static VALUE rb_mysql_stmt_affected_rows(VALUE self) {
531
+ my_ulonglong affected;
532
+ GET_STATEMENT(self);
533
+
534
+ affected = mysql_stmt_affected_rows(stmt_wrapper->stmt);
535
+ if (affected == (my_ulonglong)-1) {
536
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
537
+ }
538
+
539
+ return ULL2NUM(affected);
540
+ }
541
+
542
+ /* call-seq:
543
+ * stmt.close
544
+ *
545
+ * Explicitly closing this will free up server resources immediately rather
546
+ * than waiting for the garbage collector. Useful if you're managing your
547
+ * own prepared statement cache.
548
+ */
549
+ static VALUE rb_mysql_stmt_close(VALUE self) {
550
+ GET_STATEMENT(self);
551
+ stmt_wrapper->closed = 1;
552
+ rb_thread_call_without_gvl(nogvl_stmt_close, stmt_wrapper, RUBY_UBF_IO, 0);
553
+ return Qnil;
554
+ }
555
+
556
+ void init_mysql2_statement() {
557
+ cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject);
558
+
559
+ rb_define_method(cMysql2Statement, "param_count", param_count, 0);
560
+ rb_define_method(cMysql2Statement, "field_count", field_count, 0);
561
+ rb_define_method(cMysql2Statement, "_execute", execute, -1);
562
+ rb_define_method(cMysql2Statement, "fields", fields, 0);
563
+ rb_define_method(cMysql2Statement, "last_id", rb_mysql_stmt_last_id, 0);
564
+ rb_define_method(cMysql2Statement, "affected_rows", rb_mysql_stmt_affected_rows, 0);
565
+ rb_define_method(cMysql2Statement, "close", rb_mysql_stmt_close, 0);
566
+
567
+ sym_stream = ID2SYM(rb_intern("stream"));
568
+
569
+ intern_new_with_args = rb_intern("new_with_args");
570
+ intern_each = rb_intern("each");
571
+
572
+ intern_sec_fraction = rb_intern("sec_fraction");
573
+ intern_usec = rb_intern("usec");
574
+ intern_sec = rb_intern("sec");
575
+ intern_min = rb_intern("min");
576
+ intern_hour = rb_intern("hour");
577
+ intern_day = rb_intern("day");
578
+ intern_month = rb_intern("month");
579
+ intern_year = rb_intern("year");
580
+
581
+ intern_to_s = rb_intern("to_s");
582
+ #ifndef HAVE_RB_BIG_CMP
583
+ id_cmp = rb_intern("<=>");
584
+ #endif
585
+ }
@@ -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