mysql2 0.3.18 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -0
  3. data/LICENSE +21 -0
  4. data/README.md +63 -12
  5. data/examples/eventmachine.rb +1 -1
  6. data/examples/threaded.rb +4 -6
  7. data/ext/mysql2/client.c +170 -175
  8. data/ext/mysql2/client.h +21 -1
  9. data/ext/mysql2/extconf.rb +95 -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 +5 -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 +494 -132
  16. data/ext/mysql2/result.h +12 -6
  17. data/ext/mysql2/statement.c +494 -0
  18. data/ext/mysql2/statement.h +19 -0
  19. data/lib/mysql2/client.rb +68 -22
  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/em/em_spec.rb +21 -21
  28. data/spec/mysql2/client_spec.rb +393 -351
  29. data/spec/mysql2/error_spec.rb +37 -36
  30. data/spec/mysql2/result_spec.rb +213 -208
  31. data/spec/mysql2/statement_spec.rb +684 -0
  32. data/spec/spec_helper.rb +7 -0
  33. data/spec/ssl/ca-cert.pem +17 -0
  34. data/spec/ssl/ca-key.pem +27 -0
  35. data/spec/ssl/ca.cnf +22 -0
  36. data/spec/ssl/cert.cnf +22 -0
  37. data/spec/ssl/client-cert.pem +17 -0
  38. data/spec/ssl/client-key.pem +27 -0
  39. data/spec/ssl/client-req.pem +15 -0
  40. data/spec/ssl/gen_certs.sh +48 -0
  41. data/spec/ssl/pkcs8-client-key.pem +28 -0
  42. data/spec/ssl/pkcs8-server-key.pem +28 -0
  43. data/spec/ssl/server-cert.pem +17 -0
  44. data/spec/ssl/server-key.pem +27 -0
  45. data/spec/ssl/server-req.pem +15 -0
  46. data/support/mysql_enc_to_ruby.rb +7 -8
  47. data/support/ruby_enc_to_mysql.rb +1 -1
  48. metadata +41 -46
data/ext/mysql2/result.h CHANGED
@@ -1,23 +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;
16
+ char is_streaming;
15
17
  char streamingComplete;
16
18
  char resultFreed;
17
19
  MYSQL_RES *result;
20
+ mysql_stmt_wrapper *stmt_wrapper;
18
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;
19
27
  } mysql2_result_wrapper;
20
28
 
21
- #define GetMysql2Result(obj, sval) (sval = (mysql2_result_wrapper*)DATA_PTR(obj));
22
-
23
29
  #endif
@@ -0,0 +1,494 @@
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
+ if (stmt_wrapper->closed) { rb_raise(cMysql2Error, "Statement handle already closed"); }
13
+
14
+
15
+ static void rb_mysql_stmt_mark(void * ptr) {
16
+ mysql_stmt_wrapper *stmt_wrapper = ptr;
17
+ if (!stmt_wrapper) return;
18
+
19
+ rb_gc_mark(stmt_wrapper->client);
20
+ }
21
+
22
+ static void *nogvl_stmt_close(void * ptr) {
23
+ mysql_stmt_wrapper *stmt_wrapper = ptr;
24
+ if (stmt_wrapper->stmt) {
25
+ mysql_stmt_close(stmt_wrapper->stmt);
26
+ stmt_wrapper->stmt = NULL;
27
+ }
28
+ return NULL;
29
+ }
30
+
31
+ static void rb_mysql_stmt_free(void * ptr) {
32
+ mysql_stmt_wrapper *stmt_wrapper = ptr;
33
+ decr_mysql2_stmt(stmt_wrapper);
34
+ }
35
+
36
+ void decr_mysql2_stmt(mysql_stmt_wrapper *stmt_wrapper) {
37
+ stmt_wrapper->refcount--;
38
+
39
+ if (stmt_wrapper->refcount == 0) {
40
+ nogvl_stmt_close(stmt_wrapper);
41
+ xfree(stmt_wrapper);
42
+ }
43
+ }
44
+
45
+
46
+ void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) {
47
+ VALUE e;
48
+ GET_CLIENT(stmt_wrapper->client);
49
+ VALUE rb_error_msg = rb_str_new2(mysql_stmt_error(stmt_wrapper->stmt));
50
+ VALUE rb_sql_state = rb_tainted_str_new2(mysql_stmt_sqlstate(stmt_wrapper->stmt));
51
+
52
+ #ifdef HAVE_RUBY_ENCODING_H
53
+ rb_encoding *conn_enc;
54
+ conn_enc = rb_to_encoding(wrapper->encoding);
55
+
56
+ rb_encoding *default_internal_enc = rb_default_internal_encoding();
57
+
58
+ rb_enc_associate(rb_error_msg, conn_enc);
59
+ rb_enc_associate(rb_sql_state, conn_enc);
60
+ if (default_internal_enc) {
61
+ rb_error_msg = rb_str_export_to_enc(rb_error_msg, default_internal_enc);
62
+ rb_sql_state = rb_str_export_to_enc(rb_sql_state, default_internal_enc);
63
+ }
64
+ #endif
65
+
66
+ e = rb_funcall(cMysql2Error, intern_new_with_args, 4,
67
+ rb_error_msg,
68
+ LONG2FIX(wrapper->server_version),
69
+ UINT2NUM(mysql_stmt_errno(stmt_wrapper->stmt)),
70
+ rb_sql_state);
71
+ rb_exc_raise(e);
72
+ }
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
+ my_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 *nogvl_stmt_store_result(void *ptr) {
184
+ MYSQL_STMT *stmt = ptr;
185
+
186
+ if (mysql_stmt_store_result(stmt)) {
187
+ return (void *)Qfalse;
188
+ } else {
189
+ return (void *)Qtrue;
190
+ }
191
+ }
192
+
193
+ /* Free each bind_buffer[i].buffer except when params_enc is non-nil, this means
194
+ * the buffer is a Ruby string pointer and not our memory to manage.
195
+ */
196
+ #define FREE_BINDS \
197
+ for (i = 0; i < argc; i++) { \
198
+ if (bind_buffers[i].buffer && NIL_P(params_enc[i])) { \
199
+ xfree(bind_buffers[i].buffer); \
200
+ } \
201
+ } \
202
+ if (argc > 0) { \
203
+ xfree(bind_buffers); \
204
+ xfree(length_buffers); \
205
+ }
206
+
207
+ /* call-seq: stmt.execute
208
+ *
209
+ * Executes the current prepared statement, returns +result+.
210
+ */
211
+ static VALUE execute(int argc, VALUE *argv, VALUE self) {
212
+ MYSQL_BIND *bind_buffers = NULL;
213
+ unsigned long *length_buffers = NULL;
214
+ unsigned long bind_count;
215
+ long i;
216
+ MYSQL_STMT *stmt;
217
+ MYSQL_RES *metadata;
218
+ VALUE current;
219
+ VALUE resultObj;
220
+ VALUE *params_enc;
221
+ int is_streaming;
222
+ #ifdef HAVE_RUBY_ENCODING_H
223
+ rb_encoding *conn_enc;
224
+ #endif
225
+
226
+ GET_STATEMENT(self);
227
+ GET_CLIENT(stmt_wrapper->client);
228
+
229
+ #ifdef HAVE_RUBY_ENCODING_H
230
+ conn_enc = rb_to_encoding(wrapper->encoding);
231
+ #endif
232
+
233
+ /* Scratch space for string encoding exports, allocate on the stack. */
234
+ params_enc = alloca(sizeof(VALUE) * argc);
235
+
236
+ stmt = stmt_wrapper->stmt;
237
+
238
+ bind_count = mysql_stmt_param_count(stmt);
239
+ if (argc != (long)bind_count) {
240
+ rb_raise(cMysql2Error, "Bind parameter count (%ld) doesn't match number of arguments (%d)", bind_count, argc);
241
+ }
242
+
243
+ // setup any bind variables in the query
244
+ if (bind_count > 0) {
245
+ bind_buffers = xcalloc(bind_count, sizeof(MYSQL_BIND));
246
+ length_buffers = xcalloc(bind_count, sizeof(unsigned long));
247
+
248
+ for (i = 0; i < argc; i++) {
249
+ bind_buffers[i].buffer = NULL;
250
+ params_enc[i] = Qnil;
251
+
252
+ switch (TYPE(argv[i])) {
253
+ case T_NIL:
254
+ bind_buffers[i].buffer_type = MYSQL_TYPE_NULL;
255
+ break;
256
+ case T_FIXNUM:
257
+ #if SIZEOF_INT < SIZEOF_LONG
258
+ bind_buffers[i].buffer_type = MYSQL_TYPE_LONGLONG;
259
+ bind_buffers[i].buffer = xmalloc(sizeof(long long int));
260
+ *(long*)(bind_buffers[i].buffer) = FIX2LONG(argv[i]);
261
+ #else
262
+ bind_buffers[i].buffer_type = MYSQL_TYPE_LONG;
263
+ bind_buffers[i].buffer = xmalloc(sizeof(int));
264
+ *(long*)(bind_buffers[i].buffer) = FIX2INT(argv[i]);
265
+ #endif
266
+ break;
267
+ case T_BIGNUM:
268
+ bind_buffers[i].buffer_type = MYSQL_TYPE_LONGLONG;
269
+ bind_buffers[i].buffer = xmalloc(sizeof(long long int));
270
+ *(LONG_LONG*)(bind_buffers[i].buffer) = rb_big2ll(argv[i]);
271
+ break;
272
+ case T_FLOAT:
273
+ bind_buffers[i].buffer_type = MYSQL_TYPE_DOUBLE;
274
+ bind_buffers[i].buffer = xmalloc(sizeof(double));
275
+ *(double*)(bind_buffers[i].buffer) = NUM2DBL(argv[i]);
276
+ break;
277
+ case T_STRING:
278
+ {
279
+ params_enc[i] = argv[i];
280
+ #ifdef HAVE_RUBY_ENCODING_H
281
+ params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
282
+ #endif
283
+ bind_buffers[i].buffer_type = MYSQL_TYPE_STRING;
284
+ bind_buffers[i].buffer = RSTRING_PTR(params_enc[i]);
285
+ bind_buffers[i].buffer_length = RSTRING_LEN(params_enc[i]);
286
+ length_buffers[i] = bind_buffers[i].buffer_length;
287
+ bind_buffers[i].length = &length_buffers[i];
288
+ }
289
+ break;
290
+ default:
291
+ // TODO: what Ruby type should support MYSQL_TYPE_TIME
292
+ if (CLASS_OF(argv[i]) == rb_cTime || CLASS_OF(argv[i]) == cDateTime) {
293
+ MYSQL_TIME t;
294
+ VALUE rb_time = argv[i];
295
+
296
+ bind_buffers[i].buffer_type = MYSQL_TYPE_DATETIME;
297
+ bind_buffers[i].buffer = xmalloc(sizeof(MYSQL_TIME));
298
+
299
+ memset(&t, 0, sizeof(MYSQL_TIME));
300
+ t.neg = 0;
301
+ t.second_part = FIX2INT(rb_funcall(rb_time, intern_usec, 0));
302
+ t.second = FIX2INT(rb_funcall(rb_time, intern_sec, 0));
303
+ t.minute = FIX2INT(rb_funcall(rb_time, intern_min, 0));
304
+ t.hour = FIX2INT(rb_funcall(rb_time, intern_hour, 0));
305
+ t.day = FIX2INT(rb_funcall(rb_time, intern_day, 0));
306
+ t.month = FIX2INT(rb_funcall(rb_time, intern_month, 0));
307
+ t.year = FIX2INT(rb_funcall(rb_time, intern_year, 0));
308
+
309
+ *(MYSQL_TIME*)(bind_buffers[i].buffer) = t;
310
+ } else if (CLASS_OF(argv[i]) == cDate) {
311
+ MYSQL_TIME t;
312
+ VALUE rb_time = argv[i];
313
+
314
+ bind_buffers[i].buffer_type = MYSQL_TYPE_DATE;
315
+ bind_buffers[i].buffer = xmalloc(sizeof(MYSQL_TIME));
316
+
317
+ memset(&t, 0, sizeof(MYSQL_TIME));
318
+ t.second_part = 0;
319
+ t.neg = 0;
320
+ t.day = FIX2INT(rb_funcall(rb_time, intern_day, 0));
321
+ t.month = FIX2INT(rb_funcall(rb_time, intern_month, 0));
322
+ t.year = FIX2INT(rb_funcall(rb_time, intern_year, 0));
323
+
324
+ *(MYSQL_TIME*)(bind_buffers[i].buffer) = t;
325
+ } else if (CLASS_OF(argv[i]) == cBigDecimal) {
326
+ bind_buffers[i].buffer_type = MYSQL_TYPE_NEWDECIMAL;
327
+ }
328
+ break;
329
+ }
330
+ }
331
+
332
+ // copies bind_buffers into internal storage
333
+ if (mysql_stmt_bind_param(stmt, bind_buffers)) {
334
+ FREE_BINDS;
335
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
336
+ }
337
+ }
338
+
339
+ if ((VALUE)rb_thread_call_without_gvl(nogvl_execute, stmt, RUBY_UBF_IO, 0) == Qfalse) {
340
+ FREE_BINDS;
341
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
342
+ }
343
+
344
+ FREE_BINDS;
345
+
346
+ metadata = mysql_stmt_result_metadata(stmt);
347
+ if (metadata == NULL) {
348
+ if (mysql_stmt_errno(stmt) != 0) {
349
+ // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal.
350
+
351
+ MARK_CONN_INACTIVE(stmt_wrapper->client);
352
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
353
+ }
354
+ // no data and no error, so query was not a SELECT
355
+ return Qnil;
356
+ }
357
+
358
+ current = rb_hash_dup(rb_iv_get(stmt_wrapper->client, "@query_options"));
359
+ (void)RB_GC_GUARD(current);
360
+ Check_Type(current, T_HASH);
361
+
362
+ is_streaming = (Qtrue == rb_hash_aref(current, sym_stream));
363
+ if (!is_streaming) {
364
+ // recieve the whole result set from the server
365
+ if (rb_thread_call_without_gvl(nogvl_stmt_store_result, stmt, RUBY_UBF_IO, 0) == Qfalse) {
366
+ mysql_free_result(metadata);
367
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
368
+ }
369
+ MARK_CONN_INACTIVE(stmt_wrapper->client);
370
+ }
371
+
372
+ resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, metadata, self);
373
+
374
+ if (!is_streaming) {
375
+ // cache all result
376
+ rb_funcall(resultObj, intern_each, 0);
377
+ }
378
+
379
+ return resultObj;
380
+ }
381
+
382
+ /* call-seq: stmt.fields # => array
383
+ *
384
+ * Returns a list of fields that will be returned by this statement.
385
+ */
386
+ static VALUE fields(VALUE self) {
387
+ MYSQL_FIELD *fields;
388
+ MYSQL_RES *metadata;
389
+ unsigned int field_count;
390
+ unsigned int i;
391
+ VALUE field_list;
392
+ MYSQL_STMT* stmt;
393
+ #ifdef HAVE_RUBY_ENCODING_H
394
+ rb_encoding *default_internal_enc, *conn_enc;
395
+ #endif
396
+ GET_STATEMENT(self);
397
+ stmt = stmt_wrapper->stmt;
398
+
399
+ #ifdef HAVE_RUBY_ENCODING_H
400
+ default_internal_enc = rb_default_internal_encoding();
401
+ {
402
+ GET_CLIENT(stmt_wrapper->client);
403
+ conn_enc = rb_to_encoding(wrapper->encoding);
404
+ }
405
+ #endif
406
+
407
+ metadata = mysql_stmt_result_metadata(stmt);
408
+ fields = mysql_fetch_fields(metadata);
409
+ field_count = mysql_stmt_field_count(stmt);
410
+ field_list = rb_ary_new2((long)field_count);
411
+
412
+ for(i = 0; i < field_count; i++) {
413
+ VALUE rb_field;
414
+
415
+ rb_field = rb_str_new(fields[i].name, fields[i].name_length);
416
+ #ifdef HAVE_RUBY_ENCODING_H
417
+ rb_enc_associate(rb_field, conn_enc);
418
+ if (default_internal_enc) {
419
+ rb_field = rb_str_export_to_enc(rb_field, default_internal_enc);
420
+ }
421
+ #endif
422
+
423
+ rb_ary_store(field_list, (long)i, rb_field);
424
+ }
425
+
426
+ mysql_free_result(metadata);
427
+ return field_list;
428
+ }
429
+
430
+ /* call-seq:
431
+ * stmt.last_id
432
+ *
433
+ * Returns the AUTO_INCREMENT value from the executed INSERT or UPDATE.
434
+ */
435
+ static VALUE rb_mysql_stmt_last_id(VALUE self) {
436
+ GET_STATEMENT(self);
437
+ return ULL2NUM(mysql_stmt_insert_id(stmt_wrapper->stmt));
438
+ }
439
+
440
+ /* call-seq:
441
+ * stmt.affected_rows
442
+ *
443
+ * Returns the number of rows changed, deleted, or inserted.
444
+ */
445
+ static VALUE rb_mysql_stmt_affected_rows(VALUE self) {
446
+ my_ulonglong affected;
447
+ GET_STATEMENT(self);
448
+
449
+ affected = mysql_stmt_affected_rows(stmt_wrapper->stmt);
450
+ if (affected == (my_ulonglong)-1) {
451
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
452
+ }
453
+
454
+ return ULL2NUM(affected);
455
+ }
456
+
457
+ /* call-seq:
458
+ * stmt.close
459
+ *
460
+ * Explicitly closing this will free up server resources immediately rather
461
+ * than waiting for the garbage collector. Useful if you're managing your
462
+ * own prepared statement cache.
463
+ */
464
+ static VALUE rb_mysql_stmt_close(VALUE self) {
465
+ GET_STATEMENT(self);
466
+ stmt_wrapper->closed = 1;
467
+ rb_thread_call_without_gvl(nogvl_stmt_close, stmt_wrapper, RUBY_UBF_IO, 0);
468
+ return Qnil;
469
+ }
470
+
471
+ void init_mysql2_statement() {
472
+ cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject);
473
+
474
+ rb_define_method(cMysql2Statement, "param_count", param_count, 0);
475
+ rb_define_method(cMysql2Statement, "field_count", field_count, 0);
476
+ rb_define_method(cMysql2Statement, "_execute", execute, -1);
477
+ rb_define_method(cMysql2Statement, "fields", fields, 0);
478
+ rb_define_method(cMysql2Statement, "last_id", rb_mysql_stmt_last_id, 0);
479
+ rb_define_method(cMysql2Statement, "affected_rows", rb_mysql_stmt_affected_rows, 0);
480
+ rb_define_method(cMysql2Statement, "close", rb_mysql_stmt_close, 0);
481
+
482
+ sym_stream = ID2SYM(rb_intern("stream"));
483
+
484
+ intern_new_with_args = rb_intern("new_with_args");
485
+ intern_each = rb_intern("each");
486
+
487
+ intern_usec = rb_intern("usec");
488
+ intern_sec = rb_intern("sec");
489
+ intern_min = rb_intern("min");
490
+ intern_hour = rb_intern("hour");
491
+ intern_day = rb_intern("day");
492
+ intern_month = rb_intern("month");
493
+ intern_year = rb_intern("year");
494
+ }
@@ -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
data/lib/mysql2/client.rb CHANGED
@@ -1,24 +1,27 @@
1
1
  module Mysql2
2
2
  class Client
3
3
  attr_reader :query_options, :read_timeout
4
- @@default_query_options = {
5
- :as => :hash, # the type of object you want each row back as; also supports :array (an array of values)
6
- :async => false, # don't wait for a result after sending the query, you'll have to monitor the socket yourself then eventually call Mysql2::Client#async_result
7
- :cast_booleans => false, # cast tinyint(1) fields as true/false in ruby
8
- :symbolize_keys => false, # return field names as symbols instead of strings
9
- :database_timezone => :local, # timezone Mysql2 will assume datetime objects are stored in
10
- :application_timezone => nil, # timezone Mysql2 will convert to before handing the object back to the caller
11
- :cache_rows => true, # tells Mysql2 to use it's internal row cache for results
12
- :connect_flags => REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION,
13
- :cast => true,
14
- :default_file => nil,
15
- :default_group => nil
16
- }
4
+
5
+ def self.default_query_options
6
+ @default_query_options ||= {
7
+ :as => :hash, # the type of object you want each row back as; also supports :array (an array of values)
8
+ :async => false, # don't wait for a result after sending the query, you'll have to monitor the socket yourself then eventually call Mysql2::Client#async_result
9
+ :cast_booleans => false, # cast tinyint(1) fields as true/false in ruby
10
+ :symbolize_keys => false, # return field names as symbols instead of strings
11
+ :database_timezone => :local, # timezone Mysql2 will assume datetime objects are stored in
12
+ :application_timezone => nil, # timezone Mysql2 will convert to before handing the object back to the caller
13
+ :cache_rows => true, # tells Mysql2 to use its internal row cache for results
14
+ :connect_flags => REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION,
15
+ :cast => true,
16
+ :default_file => nil,
17
+ :default_group => nil,
18
+ }
19
+ end
17
20
 
18
21
  def initialize(opts = {})
19
- opts = Mysql2::Util.key_hash_as_symbols( opts )
22
+ opts = Mysql2::Util.key_hash_as_symbols(opts)
20
23
  @read_timeout = nil
21
- @query_options = @@default_query_options.dup
24
+ @query_options = self.class.default_query_options.dup
22
25
  @query_options.merge! opts
23
26
 
24
27
  initialize_ext
@@ -26,11 +29,12 @@ module Mysql2
26
29
  # Set default connect_timeout to avoid unlimited retries from signal interruption
27
30
  opts[:connect_timeout] = 120 unless opts.key?(:connect_timeout)
28
31
 
32
+ # TODO: stricter validation rather than silent massaging
29
33
  [:reconnect, :connect_timeout, :local_infile, :read_timeout, :write_timeout, :default_file, :default_group, :secure_auth, :init_command].each do |key|
30
34
  next unless opts.key?(key)
31
35
  case key
32
36
  when :reconnect, :local_infile, :secure_auth
33
- send(:"#{key}=", !!opts[key])
37
+ send(:"#{key}=", !!opts[key]) # rubocop:disable Style/DoubleNegation
34
38
  when :connect_timeout, :read_timeout, :write_timeout
35
39
  send(:"#{key}=", opts[key].to_i)
36
40
  else
@@ -44,7 +48,21 @@ module Mysql2
44
48
  ssl_options = opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslcipher)
45
49
  ssl_set(*ssl_options) if ssl_options.any?
46
50
 
47
- if [:user,:pass,:hostname,:dbname,:db,:sock].any?{|k| @query_options.has_key?(k) }
51
+ case opts[:flags]
52
+ when Array
53
+ flags = parse_flags_array(opts[:flags], @query_options[:connect_flags])
54
+ when String
55
+ flags = parse_flags_array(opts[:flags].split(' '), @query_options[:connect_flags])
56
+ when Integer
57
+ flags = @query_options[:connect_flags] | opts[:flags]
58
+ else
59
+ flags = @query_options[:connect_flags]
60
+ end
61
+
62
+ # SSL verify is a connection flag rather than a mysql_ssl_set option
63
+ flags |= SSL_VERIFY_SERVER_CERT if opts[:sslverify] && ssl_options.any?
64
+
65
+ if [:user, :pass, :hostname, :dbname, :db, :sock].any? { |k| @query_options.key?(k) }
48
66
  warn "============= WARNING FROM mysql2 ============="
49
67
  warn "The options :user, :pass, :hostname, :dbname, :db, and :sock will be deprecated at some point in the future."
50
68
  warn "Instead, please use :username, :password, :host, :port, :database, :socket, :flags for the options."
@@ -57,7 +75,6 @@ module Mysql2
57
75
  port = opts[:port]
58
76
  database = opts[:database] || opts[:dbname] || opts[:db]
59
77
  socket = opts[:socket] || opts[:sock]
60
- flags = opts[:flags] ? opts[:flags] | @query_options[:connect_flags] : @query_options[:connect_flags]
61
78
 
62
79
  # Correct the data types before passing these values down to the C level
63
80
  user = user.to_s unless user.nil?
@@ -70,8 +87,30 @@ module Mysql2
70
87
  connect user, pass, host, port, database, socket, flags
71
88
  end
72
89
 
73
- def self.default_query_options
74
- @@default_query_options
90
+ def parse_flags_array(flags, initial = 0)
91
+ flags.reduce(initial) do |memo, f|
92
+ fneg = f.start_with?('-') ? f[1..-1] : nil
93
+ if fneg && fneg =~ /^\w+$/ && Mysql2::Client.const_defined?(fneg)
94
+ memo & ~ Mysql2::Client.const_get(fneg)
95
+ elsif f && f =~ /^\w+$/ && Mysql2::Client.const_defined?(f)
96
+ memo | Mysql2::Client.const_get(f)
97
+ else
98
+ warn "Unknown MySQL connection flag: '#{f}'"
99
+ memo
100
+ end
101
+ end
102
+ end
103
+
104
+ if Thread.respond_to?(:handle_interrupt)
105
+ def query(sql, options = {})
106
+ Thread.handle_interrupt(::Mysql2::Util::TimeoutError => :never) do
107
+ _query(sql, @query_options.merge(options))
108
+ end
109
+ end
110
+ else
111
+ def query(sql, options = {})
112
+ _query(sql, @query_options.merge(options))
113
+ end
75
114
  end
76
115
 
77
116
  def query_info
@@ -82,9 +121,16 @@ module Mysql2
82
121
  info_hash
83
122
  end
84
123
 
85
- private
86
- def self.local_offset
124
+ def info
125
+ self.class.info
126
+ end
127
+
128
+ class << self
129
+ private
130
+
131
+ def local_offset
87
132
  ::Time.local(2010).utc_offset.to_r / 86400
88
133
  end
134
+ end
89
135
  end
90
136
  end
@@ -1,5 +1,5 @@
1
1
  # Loaded by script/console. Land helpers here.
2
2
 
3
- Pry.config.prompt = lambda do |context, nesting, pry|
3
+ Pry.config.prompt = lambda do |context, *|
4
4
  "[mysql2] #{context}> "
5
5
  end