mysql2 0.3.21 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -0
  3. data/README.md +28 -6
  4. data/examples/eventmachine.rb +1 -1
  5. data/examples/threaded.rb +4 -6
  6. data/ext/mysql2/client.c +93 -84
  7. data/ext/mysql2/client.h +21 -1
  8. data/ext/mysql2/extconf.rb +60 -41
  9. data/ext/mysql2/infile.c +2 -2
  10. data/ext/mysql2/mysql2_ext.c +1 -0
  11. data/ext/mysql2/mysql2_ext.h +5 -6
  12. data/ext/mysql2/mysql_enc_name_to_ruby.h +2 -2
  13. data/ext/mysql2/mysql_enc_to_ruby.h +25 -22
  14. data/ext/mysql2/result.c +464 -97
  15. data/ext/mysql2/result.h +11 -4
  16. data/ext/mysql2/statement.c +491 -0
  17. data/ext/mysql2/statement.h +18 -0
  18. data/lib/mysql2/client.rb +32 -24
  19. data/lib/mysql2/console.rb +1 -1
  20. data/lib/mysql2/em.rb +5 -6
  21. data/lib/mysql2/error.rb +17 -26
  22. data/lib/mysql2/field.rb +3 -0
  23. data/lib/mysql2/statement.rb +17 -0
  24. data/lib/mysql2/version.rb +1 -1
  25. data/lib/mysql2.rb +37 -35
  26. data/spec/em/em_spec.rb +21 -21
  27. data/spec/mysql2/client_spec.rb +322 -290
  28. data/spec/mysql2/error_spec.rb +37 -36
  29. data/spec/mysql2/result_spec.rb +200 -209
  30. data/spec/mysql2/statement_spec.rb +684 -0
  31. data/spec/spec_helper.rb +7 -0
  32. data/spec/ssl/ca-cert.pem +17 -0
  33. data/spec/ssl/ca-key.pem +27 -0
  34. data/spec/ssl/ca.cnf +22 -0
  35. data/spec/ssl/cert.cnf +22 -0
  36. data/spec/ssl/client-cert.pem +17 -0
  37. data/spec/ssl/client-key.pem +27 -0
  38. data/spec/ssl/client-req.pem +15 -0
  39. data/spec/ssl/gen_certs.sh +48 -0
  40. data/spec/ssl/pkcs8-client-key.pem +28 -0
  41. data/spec/ssl/pkcs8-server-key.pem +28 -0
  42. data/spec/ssl/server-cert.pem +17 -0
  43. data/spec/ssl/server-key.pem +27 -0
  44. data/spec/ssl/server-req.pem +15 -0
  45. data/support/mysql_enc_to_ruby.rb +7 -8
  46. data/support/ruby_enc_to_mysql.rb +1 -1
  47. metadata +41 -47
data/ext/mysql2/result.h CHANGED
@@ -1,22 +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(VALUE client, VALUE encoding, VALUE options, 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
10
  VALUE client;
11
11
  VALUE encoding;
12
- unsigned int numberOfFields;
13
- unsigned long numberOfRows;
12
+ VALUE statement;
13
+ my_ulonglong numberOfFields;
14
+ my_ulonglong numberOfRows;
14
15
  unsigned long lastRowProcessed;
15
16
  char is_streaming;
16
17
  char streamingComplete;
17
18
  char resultFreed;
18
19
  MYSQL_RES *result;
20
+ mysql_stmt_wrapper *stmt_wrapper;
19
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;
20
27
  } mysql2_result_wrapper;
21
28
 
22
29
  #endif
@@ -0,0 +1,491 @@
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;
6
+ static VALUE 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
+
13
+
14
+ static void rb_mysql_stmt_mark(void * ptr) {
15
+ mysql_stmt_wrapper* stmt_wrapper = (mysql_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 = (mysql_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 = (mysql_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
+
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
+ #ifdef HAVE_RUBY_ENCODING_H
52
+ rb_encoding *conn_enc;
53
+ conn_enc = rb_to_encoding(wrapper->encoding);
54
+
55
+ rb_encoding *default_internal_enc = rb_default_internal_encoding();
56
+
57
+ rb_enc_associate(rb_error_msg, conn_enc);
58
+ rb_enc_associate(rb_sql_state, conn_enc);
59
+ if (default_internal_enc) {
60
+ rb_error_msg = rb_str_export_to_enc(rb_error_msg, default_internal_enc);
61
+ rb_sql_state = rb_str_export_to_enc(rb_sql_state, default_internal_enc);
62
+ }
63
+ #endif
64
+
65
+ e = rb_funcall(cMysql2Error, intern_new_with_args, 4,
66
+ rb_error_msg,
67
+ LONG2FIX(wrapper->server_version),
68
+ UINT2NUM(mysql_stmt_errno(stmt_wrapper->stmt)),
69
+ rb_sql_state);
70
+ rb_exc_raise(e);
71
+ }
72
+
73
+
74
+ /*
75
+ * used to pass all arguments to mysql_stmt_prepare while inside
76
+ * nogvl_prepare_statement_args
77
+ */
78
+ struct nogvl_prepare_statement_args {
79
+ MYSQL_STMT *stmt;
80
+ VALUE sql;
81
+ const char *sql_ptr;
82
+ unsigned long sql_len;
83
+ };
84
+
85
+ static void *nogvl_prepare_statement(void *ptr) {
86
+ struct nogvl_prepare_statement_args *args = ptr;
87
+
88
+ if (mysql_stmt_prepare(args->stmt, args->sql_ptr, args->sql_len)) {
89
+ return (void*)Qfalse;
90
+ } else {
91
+ return (void*)Qtrue;
92
+ }
93
+ }
94
+
95
+ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
96
+ mysql_stmt_wrapper* stmt_wrapper;
97
+ VALUE rb_stmt;
98
+ #ifdef HAVE_RUBY_ENCODING_H
99
+ rb_encoding *conn_enc;
100
+ #endif
101
+
102
+ Check_Type(sql, T_STRING);
103
+
104
+ rb_stmt = Data_Make_Struct(cMysql2Statement, mysql_stmt_wrapper, rb_mysql_stmt_mark, rb_mysql_stmt_free, stmt_wrapper);
105
+ {
106
+ stmt_wrapper->client = rb_client;
107
+ stmt_wrapper->refcount = 1;
108
+ stmt_wrapper->stmt = NULL;
109
+ }
110
+
111
+ // instantiate stmt
112
+ {
113
+ GET_CLIENT(rb_client);
114
+ stmt_wrapper->stmt = mysql_stmt_init(wrapper->client);
115
+ #ifdef HAVE_RUBY_ENCODING_H
116
+ conn_enc = rb_to_encoding(wrapper->encoding);
117
+ #endif
118
+ }
119
+ if (stmt_wrapper->stmt == NULL) {
120
+ rb_raise(cMysql2Error, "Unable to initialize prepared statement: out of memory");
121
+ }
122
+
123
+ // set STMT_ATTR_UPDATE_MAX_LENGTH attr
124
+ {
125
+ my_bool truth = 1;
126
+ if (mysql_stmt_attr_set(stmt_wrapper->stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &truth)) {
127
+ rb_raise(cMysql2Error, "Unable to initialize prepared statement: set STMT_ATTR_UPDATE_MAX_LENGTH");
128
+ }
129
+ }
130
+
131
+ // call mysql_stmt_prepare w/o gvl
132
+ {
133
+ struct nogvl_prepare_statement_args args;
134
+ args.stmt = stmt_wrapper->stmt;
135
+ args.sql = sql;
136
+ #ifdef HAVE_RUBY_ENCODING_H
137
+ // ensure the string is in the encoding the connection is expecting
138
+ args.sql = rb_str_export_to_enc(args.sql, conn_enc);
139
+ #endif
140
+ args.sql_ptr = RSTRING_PTR(sql);
141
+ args.sql_len = RSTRING_LEN(sql);
142
+
143
+ if ((VALUE)rb_thread_call_without_gvl(nogvl_prepare_statement, &args, RUBY_UBF_IO, 0) == Qfalse) {
144
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
145
+ }
146
+ }
147
+
148
+ return rb_stmt;
149
+ }
150
+
151
+ /* call-seq: stmt.param_count # => Numeric
152
+ *
153
+ * Returns the number of parameters the prepared statement expects.
154
+ */
155
+ static VALUE param_count(VALUE self) {
156
+ GET_STATEMENT(self);
157
+
158
+ return ULL2NUM(mysql_stmt_param_count(stmt_wrapper->stmt));
159
+ }
160
+
161
+ /* call-seq: stmt.field_count # => Numeric
162
+ *
163
+ * Returns the number of fields the prepared statement returns.
164
+ */
165
+ static VALUE field_count(VALUE self) {
166
+ GET_STATEMENT(self);
167
+
168
+ return UINT2NUM(mysql_stmt_field_count(stmt_wrapper->stmt));
169
+ }
170
+
171
+ static void *nogvl_execute(void *ptr) {
172
+ MYSQL_STMT *stmt = ptr;
173
+
174
+ if (mysql_stmt_execute(stmt)) {
175
+ return (void*)Qfalse;
176
+ } else {
177
+ return (void*)Qtrue;
178
+ }
179
+ }
180
+
181
+ static void *nogvl_stmt_store_result(void *ptr) {
182
+ MYSQL_STMT *stmt = ptr;
183
+
184
+ if (mysql_stmt_store_result(stmt)) {
185
+ return (void *)Qfalse;
186
+ } else {
187
+ return (void *)Qtrue;
188
+ }
189
+ }
190
+
191
+ /* Free each bind_buffer[i].buffer except when params_enc is non-nil, this means
192
+ * the buffer is a Ruby string pointer and not our memory to manage.
193
+ */
194
+ #define FREE_BINDS \
195
+ for (i = 0; i < argc; i++) { \
196
+ if (bind_buffers[i].buffer && NIL_P(params_enc[i])) { \
197
+ xfree(bind_buffers[i].buffer); \
198
+ } \
199
+ } \
200
+ if (argc > 0) { \
201
+ xfree(bind_buffers); \
202
+ xfree(length_buffers); \
203
+ }
204
+
205
+ /* call-seq: stmt.execute
206
+ *
207
+ * Executes the current prepared statement, returns +result+.
208
+ */
209
+ static VALUE execute(int argc, VALUE *argv, VALUE self) {
210
+ MYSQL_BIND *bind_buffers = NULL;
211
+ unsigned long *length_buffers = NULL;
212
+ unsigned long bind_count;
213
+ long i;
214
+ MYSQL_STMT *stmt;
215
+ MYSQL_RES *metadata;
216
+ VALUE current;
217
+ VALUE resultObj;
218
+ VALUE *params_enc;
219
+ int is_streaming;
220
+ #ifdef HAVE_RUBY_ENCODING_H
221
+ rb_encoding *conn_enc;
222
+ #endif
223
+
224
+ GET_STATEMENT(self);
225
+ GET_CLIENT(stmt_wrapper->client);
226
+
227
+ #ifdef HAVE_RUBY_ENCODING_H
228
+ conn_enc = rb_to_encoding(wrapper->encoding);
229
+ #endif
230
+
231
+ /* Scratch space for string encoding exports, allocate on the stack. */
232
+ params_enc = alloca(sizeof(VALUE) * argc);
233
+
234
+ stmt = stmt_wrapper->stmt;
235
+
236
+ bind_count = mysql_stmt_param_count(stmt);
237
+ if (argc != (long)bind_count) {
238
+ rb_raise(cMysql2Error, "Bind parameter count (%ld) doesn't match number of arguments (%d)", bind_count, argc);
239
+ }
240
+
241
+ // setup any bind variables in the query
242
+ if (bind_count > 0) {
243
+ bind_buffers = xcalloc(bind_count, sizeof(MYSQL_BIND));
244
+ length_buffers = xcalloc(bind_count, sizeof(unsigned long));
245
+
246
+ for (i = 0; i < argc; i++) {
247
+ bind_buffers[i].buffer = NULL;
248
+ params_enc[i] = Qnil;
249
+
250
+ switch (TYPE(argv[i])) {
251
+ case T_NIL:
252
+ bind_buffers[i].buffer_type = MYSQL_TYPE_NULL;
253
+ break;
254
+ case T_FIXNUM:
255
+ #if SIZEOF_INT < SIZEOF_LONG
256
+ bind_buffers[i].buffer_type = MYSQL_TYPE_LONGLONG;
257
+ bind_buffers[i].buffer = xmalloc(sizeof(long long int));
258
+ *(long*)(bind_buffers[i].buffer) = FIX2LONG(argv[i]);
259
+ #else
260
+ bind_buffers[i].buffer_type = MYSQL_TYPE_LONG;
261
+ bind_buffers[i].buffer = xmalloc(sizeof(int));
262
+ *(long*)(bind_buffers[i].buffer) = FIX2INT(argv[i]);
263
+ #endif
264
+ break;
265
+ case T_BIGNUM:
266
+ bind_buffers[i].buffer_type = MYSQL_TYPE_LONGLONG;
267
+ bind_buffers[i].buffer = xmalloc(sizeof(long long int));
268
+ *(LONG_LONG*)(bind_buffers[i].buffer) = rb_big2ll(argv[i]);
269
+ break;
270
+ case T_FLOAT:
271
+ bind_buffers[i].buffer_type = MYSQL_TYPE_DOUBLE;
272
+ bind_buffers[i].buffer = xmalloc(sizeof(double));
273
+ *(double*)(bind_buffers[i].buffer) = NUM2DBL(argv[i]);
274
+ break;
275
+ case T_STRING:
276
+ {
277
+ params_enc[i] = argv[i];
278
+ #ifdef HAVE_RUBY_ENCODING_H
279
+ params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
280
+ #endif
281
+ bind_buffers[i].buffer_type = MYSQL_TYPE_STRING;
282
+ bind_buffers[i].buffer = RSTRING_PTR(params_enc[i]);
283
+ bind_buffers[i].buffer_length = RSTRING_LEN(params_enc[i]);
284
+ length_buffers[i] = bind_buffers[i].buffer_length;
285
+ bind_buffers[i].length = &length_buffers[i];
286
+ }
287
+ break;
288
+ default:
289
+ // TODO: what Ruby type should support MYSQL_TYPE_TIME
290
+ if (CLASS_OF(argv[i]) == rb_cTime || CLASS_OF(argv[i]) == cDateTime) {
291
+ MYSQL_TIME t;
292
+ VALUE rb_time = argv[i];
293
+
294
+ bind_buffers[i].buffer_type = MYSQL_TYPE_DATETIME;
295
+ bind_buffers[i].buffer = xmalloc(sizeof(MYSQL_TIME));
296
+
297
+ memset(&t, 0, sizeof(MYSQL_TIME));
298
+ t.neg = 0;
299
+ t.second_part = FIX2INT(rb_funcall(rb_time, intern_usec, 0));
300
+ t.second = FIX2INT(rb_funcall(rb_time, intern_sec, 0));
301
+ t.minute = FIX2INT(rb_funcall(rb_time, intern_min, 0));
302
+ t.hour = FIX2INT(rb_funcall(rb_time, intern_hour, 0));
303
+ t.day = FIX2INT(rb_funcall(rb_time, intern_day, 0));
304
+ t.month = FIX2INT(rb_funcall(rb_time, intern_month, 0));
305
+ t.year = FIX2INT(rb_funcall(rb_time, intern_year, 0));
306
+
307
+ *(MYSQL_TIME*)(bind_buffers[i].buffer) = t;
308
+ } else if (CLASS_OF(argv[i]) == cDate) {
309
+ MYSQL_TIME t;
310
+ VALUE rb_time = argv[i];
311
+
312
+ bind_buffers[i].buffer_type = MYSQL_TYPE_DATE;
313
+ bind_buffers[i].buffer = xmalloc(sizeof(MYSQL_TIME));
314
+
315
+ memset(&t, 0, sizeof(MYSQL_TIME));
316
+ t.second_part = 0;
317
+ t.neg = 0;
318
+ t.day = FIX2INT(rb_funcall(rb_time, intern_day, 0));
319
+ t.month = FIX2INT(rb_funcall(rb_time, intern_month, 0));
320
+ t.year = FIX2INT(rb_funcall(rb_time, intern_year, 0));
321
+
322
+ *(MYSQL_TIME*)(bind_buffers[i].buffer) = t;
323
+ } else if (CLASS_OF(argv[i]) == cBigDecimal) {
324
+ bind_buffers[i].buffer_type = MYSQL_TYPE_NEWDECIMAL;
325
+ }
326
+ break;
327
+ }
328
+ }
329
+
330
+ // copies bind_buffers into internal storage
331
+ if (mysql_stmt_bind_param(stmt, bind_buffers)) {
332
+ FREE_BINDS;
333
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
334
+ }
335
+ }
336
+
337
+ if ((VALUE)rb_thread_call_without_gvl(nogvl_execute, stmt, RUBY_UBF_IO, 0) == Qfalse) {
338
+ FREE_BINDS;
339
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
340
+ }
341
+
342
+ FREE_BINDS;
343
+
344
+ metadata = mysql_stmt_result_metadata(stmt);
345
+ if (metadata == NULL) {
346
+ if (mysql_stmt_errno(stmt) != 0) {
347
+ // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal.
348
+
349
+ MARK_CONN_INACTIVE(stmt_wrapper->client);
350
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
351
+ }
352
+ // no data and no error, so query was not a SELECT
353
+ return Qnil;
354
+ }
355
+
356
+ current = rb_hash_dup(rb_iv_get(stmt_wrapper->client, "@query_options"));
357
+ (void)RB_GC_GUARD(current);
358
+ Check_Type(current, T_HASH);
359
+
360
+ is_streaming = (Qtrue == rb_hash_aref(current, sym_stream));
361
+ if (!is_streaming) {
362
+ // recieve the whole result set from the server
363
+ if (rb_thread_call_without_gvl(nogvl_stmt_store_result, stmt, RUBY_UBF_IO, 0) == Qfalse) {
364
+ mysql_free_result(metadata);
365
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
366
+ }
367
+ MARK_CONN_INACTIVE(stmt_wrapper->client);
368
+ }
369
+
370
+ resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, metadata, self);
371
+
372
+ if (!is_streaming) {
373
+ // cache all result
374
+ rb_funcall(resultObj, intern_each, 0);
375
+ }
376
+
377
+ return resultObj;
378
+ }
379
+
380
+ /* call-seq: stmt.fields # => array
381
+ *
382
+ * Returns a list of fields that will be returned by this statement.
383
+ */
384
+ static VALUE fields(VALUE self) {
385
+ MYSQL_FIELD *fields;
386
+ MYSQL_RES *metadata;
387
+ unsigned int field_count;
388
+ unsigned int i;
389
+ VALUE field_list;
390
+ MYSQL_STMT* stmt;
391
+ #ifdef HAVE_RUBY_ENCODING_H
392
+ rb_encoding *default_internal_enc, *conn_enc;
393
+ #endif
394
+ GET_STATEMENT(self);
395
+ stmt = stmt_wrapper->stmt;
396
+
397
+ #ifdef HAVE_RUBY_ENCODING_H
398
+ default_internal_enc = rb_default_internal_encoding();
399
+ {
400
+ GET_CLIENT(stmt_wrapper->client);
401
+ conn_enc = rb_to_encoding(wrapper->encoding);
402
+ }
403
+ #endif
404
+
405
+ metadata = mysql_stmt_result_metadata(stmt);
406
+ fields = mysql_fetch_fields(metadata);
407
+ field_count = mysql_stmt_field_count(stmt);
408
+ field_list = rb_ary_new2((long)field_count);
409
+
410
+ for(i = 0; i < field_count; i++) {
411
+ VALUE rb_field;
412
+
413
+ rb_field = rb_str_new(fields[i].name, fields[i].name_length);
414
+ #ifdef HAVE_RUBY_ENCODING_H
415
+ rb_enc_associate(rb_field, conn_enc);
416
+ if (default_internal_enc) {
417
+ rb_field = rb_str_export_to_enc(rb_field, default_internal_enc);
418
+ }
419
+ #endif
420
+
421
+ rb_ary_store(field_list, (long)i, rb_field);
422
+ }
423
+
424
+ mysql_free_result(metadata);
425
+ return field_list;
426
+ }
427
+
428
+ /* call-seq:
429
+ * stmt.last_id
430
+ *
431
+ * Returns the AUTO_INCREMENT value from the executed INSERT or UPDATE.
432
+ */
433
+ static VALUE rb_mysql_stmt_last_id(VALUE self) {
434
+ GET_STATEMENT(self);
435
+ return ULL2NUM(mysql_stmt_insert_id(stmt_wrapper->stmt));
436
+ }
437
+
438
+ /* call-seq:
439
+ * stmt.affected_rows
440
+ *
441
+ * Returns the number of rows changed, deleted, or inserted.
442
+ */
443
+ static VALUE rb_mysql_stmt_affected_rows(VALUE self) {
444
+ my_ulonglong affected;
445
+ GET_STATEMENT(self);
446
+
447
+ affected = mysql_stmt_affected_rows(stmt_wrapper->stmt);
448
+ if (affected == (my_ulonglong)-1) {
449
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
450
+ }
451
+
452
+ return ULL2NUM(affected);
453
+ }
454
+
455
+ /* call-seq:
456
+ * stmt.close
457
+ *
458
+ * Explicitly closing this will free up server resources immediately rather
459
+ * than waiting for the garbage collector. Useful if you're managing your
460
+ * own prepared statement cache.
461
+ */
462
+ static VALUE rb_mysql_stmt_close(VALUE self) {
463
+ GET_STATEMENT(self);
464
+ rb_thread_call_without_gvl(nogvl_stmt_close, stmt_wrapper, RUBY_UBF_IO, 0);
465
+ return Qnil;
466
+ }
467
+
468
+ void init_mysql2_statement() {
469
+ cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject);
470
+
471
+ rb_define_method(cMysql2Statement, "param_count", param_count, 0);
472
+ rb_define_method(cMysql2Statement, "field_count", field_count, 0);
473
+ rb_define_method(cMysql2Statement, "_execute", execute, -1);
474
+ rb_define_method(cMysql2Statement, "fields", fields, 0);
475
+ rb_define_method(cMysql2Statement, "last_id", rb_mysql_stmt_last_id, 0);
476
+ rb_define_method(cMysql2Statement, "affected_rows", rb_mysql_stmt_affected_rows, 0);
477
+ rb_define_method(cMysql2Statement, "close", rb_mysql_stmt_close, 0);
478
+
479
+ sym_stream = ID2SYM(rb_intern("stream"));
480
+
481
+ intern_new_with_args = rb_intern("new_with_args");
482
+ intern_each = rb_intern("each");
483
+
484
+ intern_usec = rb_intern("usec");
485
+ intern_sec = rb_intern("sec");
486
+ intern_min = rb_intern("min");
487
+ intern_hour = rb_intern("hour");
488
+ intern_day = rb_intern("day");
489
+ intern_month = rb_intern("month");
490
+ intern_year = rb_intern("year");
491
+ }