mysql2 0.5.0 → 0.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +150 -44
  3. data/ext/mysql2/client.c +217 -58
  4. data/ext/mysql2/client.h +9 -2
  5. data/ext/mysql2/extconf.rb +60 -7
  6. data/ext/mysql2/mysql2_ext.c +6 -1
  7. data/ext/mysql2/mysql2_ext.h +13 -0
  8. data/ext/mysql2/mysql_enc_name_to_ruby.h +60 -56
  9. data/ext/mysql2/mysql_enc_to_ruby.h +64 -3
  10. data/ext/mysql2/result.c +287 -21
  11. data/ext/mysql2/result.h +1 -0
  12. data/ext/mysql2/statement.c +95 -24
  13. data/lib/mysql2/client.rb +24 -3
  14. data/lib/mysql2/error.rb +4 -3
  15. data/lib/mysql2/statement.rb +1 -3
  16. data/lib/mysql2/version.rb +1 -1
  17. data/lib/mysql2.rb +8 -3
  18. data/support/3A79BD29.asc +49 -0
  19. data/support/5072E1F5.asc +5 -5
  20. data/support/C74CD1D8.asc +104 -0
  21. data/support/mysql_enc_to_ruby.rb +6 -1
  22. data/support/ruby_enc_to_mysql.rb +2 -0
  23. metadata +11 -55
  24. data/examples/eventmachine.rb +0 -19
  25. data/examples/threaded.rb +0 -16
  26. data/spec/configuration.yml.example +0 -11
  27. data/spec/em/em_spec.rb +0 -135
  28. data/spec/my.cnf.example +0 -9
  29. data/spec/mysql2/client_spec.rb +0 -1072
  30. data/spec/mysql2/error_spec.rb +0 -78
  31. data/spec/mysql2/result_spec.rb +0 -485
  32. data/spec/mysql2/statement_spec.rb +0 -708
  33. data/spec/rcov.opts +0 -3
  34. data/spec/spec_helper.rb +0 -112
  35. data/spec/ssl/ca-cert.pem +0 -17
  36. data/spec/ssl/ca-key.pem +0 -27
  37. data/spec/ssl/ca.cnf +0 -22
  38. data/spec/ssl/cert.cnf +0 -22
  39. data/spec/ssl/client-cert.pem +0 -17
  40. data/spec/ssl/client-key.pem +0 -27
  41. data/spec/ssl/client-req.pem +0 -15
  42. data/spec/ssl/gen_certs.sh +0 -48
  43. data/spec/ssl/pkcs8-client-key.pem +0 -28
  44. data/spec/ssl/pkcs8-server-key.pem +0 -28
  45. data/spec/ssl/server-cert.pem +0 -17
  46. data/spec/ssl/server-key.pem +0 -27
  47. data/spec/ssl/server-req.pem +0 -15
  48. data/spec/test_data +0 -1
data/ext/mysql2/result.c CHANGED
@@ -1,6 +1,7 @@
1
1
  #include <mysql2_ext.h>
2
2
 
3
3
  #include "mysql_enc_to_ruby.h"
4
+ #define MYSQL2_CHARSETNR_SIZE (sizeof(mysql2_mysql_enc_to_rb)/sizeof(mysql2_mysql_enc_to_rb[0]))
4
5
 
5
6
  static rb_encoding *binaryEncoding;
6
7
 
@@ -16,9 +17,27 @@ static rb_encoding *binaryEncoding;
16
17
  */
17
18
  #define MYSQL2_MIN_TIME 2678400ULL
18
19
 
20
+ #define MYSQL2_MAX_BYTES_PER_CHAR 3
21
+
22
+ /* From Mysql documentations:
23
+ * To distinguish between binary and nonbinary data for string data types,
24
+ * check whether the charsetnr value is 63. If so, the character set is binary,
25
+ * which indicates binary rather than nonbinary data. This enables you to distinguish BINARY
26
+ * from CHAR, VARBINARY from VARCHAR, and the BLOB types from the TEXT types.
27
+ */
28
+ #define MYSQL2_BINARY_CHARSET 63
29
+
30
+ #ifndef MYSQL_TYPE_JSON
31
+ #define MYSQL_TYPE_JSON 245
32
+ #endif
33
+
34
+ #ifndef NEW_TYPEDDATA_WRAPPER
35
+ #define TypedData_Get_Struct(obj, type, ignore, sval) Data_Get_Struct(obj, type, sval)
36
+ #endif
37
+
19
38
  #define GET_RESULT(self) \
20
39
  mysql2_result_wrapper *wrapper; \
21
- Data_Get_Struct(self, mysql2_result_wrapper, wrapper);
40
+ TypedData_Get_Struct(self, mysql2_result_wrapper, &rb_mysql_result_type, wrapper);
22
41
 
23
42
  typedef struct {
24
43
  int symbolizeKeys;
@@ -29,14 +48,15 @@ typedef struct {
29
48
  int streaming;
30
49
  ID db_timezone;
31
50
  ID app_timezone;
32
- VALUE block_given;
51
+ int block_given; /* boolean */
33
52
  } result_each_args;
34
53
 
35
54
  extern VALUE mMysql2, cMysql2Client, cMysql2Error;
36
55
  static VALUE cMysql2Result, cDateTime, cDate;
37
56
  static VALUE opt_decimal_zero, opt_float_zero, opt_time_year, opt_time_month, opt_utc_offset;
38
57
  static ID intern_new, intern_utc, intern_local, intern_localtime, intern_local_offset,
39
- intern_civil, intern_new_offset, intern_merge, intern_BigDecimal;
58
+ intern_civil, intern_new_offset, intern_merge, intern_BigDecimal,
59
+ intern_query_options;
40
60
  static VALUE sym_symbolize_keys, sym_as, sym_array, sym_database_timezone,
41
61
  sym_application_timezone, sym_local, sym_utc, sym_cast_booleans,
42
62
  sym_cache_rows, sym_cast, sym_stream, sym_name;
@@ -45,11 +65,11 @@ static VALUE sym_symbolize_keys, sym_as, sym_array, sym_database_timezone,
45
65
  static void rb_mysql_result_mark(void * wrapper) {
46
66
  mysql2_result_wrapper * w = wrapper;
47
67
  if (w) {
48
- rb_gc_mark(w->fields);
49
- rb_gc_mark(w->rows);
50
- rb_gc_mark(w->encoding);
51
- rb_gc_mark(w->client);
52
- rb_gc_mark(w->statement);
68
+ rb_gc_mark_movable(w->fields);
69
+ rb_gc_mark_movable(w->rows);
70
+ rb_gc_mark_movable(w->encoding);
71
+ rb_gc_mark_movable(w->client);
72
+ rb_gc_mark_movable(w->statement);
53
73
  }
54
74
  }
55
75
 
@@ -111,6 +131,48 @@ static void rb_mysql_result_free(void *ptr) {
111
131
  xfree(wrapper);
112
132
  }
113
133
 
134
+ static size_t rb_mysql_result_memsize(const void * wrapper) {
135
+ const mysql2_result_wrapper * w = wrapper;
136
+ size_t memsize = sizeof(*w);
137
+ if (w->stmt_wrapper) {
138
+ memsize += sizeof(*w->stmt_wrapper);
139
+ }
140
+ if (w->client_wrapper) {
141
+ memsize += sizeof(*w->client_wrapper);
142
+ }
143
+ return memsize;
144
+ }
145
+
146
+ #ifdef HAVE_RB_GC_MARK_MOVABLE
147
+ static void rb_mysql_result_compact(void * wrapper) {
148
+ mysql2_result_wrapper * w = wrapper;
149
+ if (w) {
150
+ rb_mysql2_gc_location(w->fields);
151
+ rb_mysql2_gc_location(w->rows);
152
+ rb_mysql2_gc_location(w->encoding);
153
+ rb_mysql2_gc_location(w->client);
154
+ rb_mysql2_gc_location(w->statement);
155
+ }
156
+ }
157
+ #endif
158
+
159
+ static const rb_data_type_t rb_mysql_result_type = {
160
+ "rb_mysql_result",
161
+ {
162
+ rb_mysql_result_mark,
163
+ rb_mysql_result_free,
164
+ rb_mysql_result_memsize,
165
+ #ifdef HAVE_RB_GC_MARK_MOVABLE
166
+ rb_mysql_result_compact,
167
+ #endif
168
+ },
169
+ 0,
170
+ 0,
171
+ #ifdef RUBY_TYPED_FREE_IMMEDIATELY
172
+ RUBY_TYPED_FREE_IMMEDIATELY,
173
+ #endif
174
+ };
175
+
114
176
  static VALUE rb_mysql_result_free_(VALUE self) {
115
177
  GET_RESULT(self);
116
178
  rb_mysql_result_free_result(wrapper);
@@ -155,11 +217,18 @@ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, int symbo
155
217
  rb_field = rb_intern3(field->name, field->name_length, rb_utf8_encoding());
156
218
  rb_field = ID2SYM(rb_field);
157
219
  } else {
158
- rb_field = rb_str_new(field->name, field->name_length);
159
- rb_enc_associate(rb_field, conn_enc);
160
- if (default_internal_enc) {
220
+ #ifdef HAVE_RB_ENC_INTERNED_STR
221
+ rb_field = rb_enc_interned_str(field->name, field->name_length, conn_enc);
222
+ if (default_internal_enc && default_internal_enc != conn_enc) {
223
+ rb_field = rb_str_to_interned_str(rb_str_export_to_enc(rb_field, default_internal_enc));
224
+ }
225
+ #else
226
+ rb_field = rb_enc_str_new(field->name, field->name_length, conn_enc);
227
+ if (default_internal_enc && default_internal_enc != conn_enc) {
161
228
  rb_field = rb_str_export_to_enc(rb_field, default_internal_enc);
162
229
  }
230
+ rb_obj_freeze(rb_field);
231
+ #endif
163
232
  }
164
233
  rb_ary_store(wrapper->fields, idx, rb_field);
165
234
  }
@@ -167,9 +236,171 @@ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, int symbo
167
236
  return rb_field;
168
237
  }
169
238
 
239
+ static VALUE rb_mysql_result_fetch_field_type(VALUE self, unsigned int idx) {
240
+ VALUE rb_field_type;
241
+ GET_RESULT(self);
242
+
243
+ if (wrapper->fieldTypes == Qnil) {
244
+ wrapper->numberOfFields = mysql_num_fields(wrapper->result);
245
+ wrapper->fieldTypes = rb_ary_new2(wrapper->numberOfFields);
246
+ }
247
+
248
+ rb_field_type = rb_ary_entry(wrapper->fieldTypes, idx);
249
+ if (rb_field_type == Qnil) {
250
+ MYSQL_FIELD *field = NULL;
251
+ rb_encoding *default_internal_enc = rb_default_internal_encoding();
252
+ rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
253
+ int precision;
254
+
255
+ field = mysql_fetch_field_direct(wrapper->result, idx);
256
+
257
+ switch(field->type) {
258
+ case MYSQL_TYPE_NULL: // NULL
259
+ rb_field_type = rb_str_new_cstr("null");
260
+ break;
261
+ case MYSQL_TYPE_TINY: // signed char
262
+ rb_field_type = rb_sprintf("tinyint(%ld)", field->length);
263
+ break;
264
+ case MYSQL_TYPE_SHORT: // short int
265
+ rb_field_type = rb_sprintf("smallint(%ld)", field->length);
266
+ break;
267
+ case MYSQL_TYPE_YEAR: // short int
268
+ rb_field_type = rb_sprintf("year(%ld)", field->length);
269
+ break;
270
+ case MYSQL_TYPE_INT24: // int
271
+ rb_field_type = rb_sprintf("mediumint(%ld)", field->length);
272
+ break;
273
+ case MYSQL_TYPE_LONG: // int
274
+ rb_field_type = rb_sprintf("int(%ld)", field->length);
275
+ break;
276
+ case MYSQL_TYPE_LONGLONG: // long long int
277
+ rb_field_type = rb_sprintf("bigint(%ld)", field->length);
278
+ break;
279
+ case MYSQL_TYPE_FLOAT: // float
280
+ rb_field_type = rb_sprintf("float(%ld,%d)", field->length, field->decimals);
281
+ break;
282
+ case MYSQL_TYPE_DOUBLE: // double
283
+ rb_field_type = rb_sprintf("double(%ld,%d)", field->length, field->decimals);
284
+ break;
285
+ case MYSQL_TYPE_TIME: // MYSQL_TIME
286
+ rb_field_type = rb_str_new_cstr("time");
287
+ break;
288
+ case MYSQL_TYPE_DATE: // MYSQL_TIME
289
+ case MYSQL_TYPE_NEWDATE: // MYSQL_TIME
290
+ rb_field_type = rb_str_new_cstr("date");
291
+ break;
292
+ case MYSQL_TYPE_DATETIME: // MYSQL_TIME
293
+ rb_field_type = rb_str_new_cstr("datetime");
294
+ break;
295
+ case MYSQL_TYPE_TIMESTAMP: // MYSQL_TIME
296
+ rb_field_type = rb_str_new_cstr("timestamp");
297
+ break;
298
+ case MYSQL_TYPE_DECIMAL: // char[]
299
+ case MYSQL_TYPE_NEWDECIMAL: // char[]
300
+ /*
301
+ Handle precision similar to this line from mysql's code:
302
+ https://github.com/mysql/mysql-server/blob/ea7d2e2d16ac03afdd9cb72a972a95981107bf51/sql/field.cc#L2246
303
+ */
304
+ precision = field->length - (field->decimals > 0 ? 2 : 1);
305
+ rb_field_type = rb_sprintf("decimal(%d,%d)", precision, field->decimals);
306
+ break;
307
+ case MYSQL_TYPE_STRING: // char[]
308
+ if (field->flags & ENUM_FLAG) {
309
+ rb_field_type = rb_str_new_cstr("enum");
310
+ } else if (field->flags & SET_FLAG) {
311
+ rb_field_type = rb_str_new_cstr("set");
312
+ } else {
313
+ if (field->charsetnr == MYSQL2_BINARY_CHARSET) {
314
+ rb_field_type = rb_sprintf("binary(%ld)", field->length);
315
+ } else {
316
+ rb_field_type = rb_sprintf("char(%ld)", field->length / MYSQL2_MAX_BYTES_PER_CHAR);
317
+ }
318
+ }
319
+ break;
320
+ case MYSQL_TYPE_VAR_STRING: // char[]
321
+ if (field->charsetnr == MYSQL2_BINARY_CHARSET) {
322
+ rb_field_type = rb_sprintf("varbinary(%ld)", field->length);
323
+ } else {
324
+ rb_field_type = rb_sprintf("varchar(%ld)", field->length / MYSQL2_MAX_BYTES_PER_CHAR);
325
+ }
326
+ break;
327
+ case MYSQL_TYPE_VARCHAR: // char[]
328
+ rb_field_type = rb_sprintf("varchar(%ld)", field->length / MYSQL2_MAX_BYTES_PER_CHAR);
329
+ break;
330
+ case MYSQL_TYPE_TINY_BLOB: // char[]
331
+ rb_field_type = rb_str_new_cstr("tinyblob");
332
+ break;
333
+ case MYSQL_TYPE_BLOB: // char[]
334
+ if (field->charsetnr == MYSQL2_BINARY_CHARSET) {
335
+ switch(field->length) {
336
+ case 255:
337
+ rb_field_type = rb_str_new_cstr("tinyblob");
338
+ break;
339
+ case 65535:
340
+ rb_field_type = rb_str_new_cstr("blob");
341
+ break;
342
+ case 16777215:
343
+ rb_field_type = rb_str_new_cstr("mediumblob");
344
+ break;
345
+ case 4294967295:
346
+ rb_field_type = rb_str_new_cstr("longblob");
347
+ default:
348
+ break;
349
+ }
350
+ } else {
351
+ if (field->length == (255 * MYSQL2_MAX_BYTES_PER_CHAR)) {
352
+ rb_field_type = rb_str_new_cstr("tinytext");
353
+ } else if (field->length == (65535 * MYSQL2_MAX_BYTES_PER_CHAR)) {
354
+ rb_field_type = rb_str_new_cstr("text");
355
+ } else if (field->length == (16777215 * MYSQL2_MAX_BYTES_PER_CHAR)) {
356
+ rb_field_type = rb_str_new_cstr("mediumtext");
357
+ } else if (field->length == 4294967295) {
358
+ rb_field_type = rb_str_new_cstr("longtext");
359
+ } else {
360
+ rb_field_type = rb_sprintf("text(%ld)", field->length);
361
+ }
362
+ }
363
+ break;
364
+ case MYSQL_TYPE_MEDIUM_BLOB: // char[]
365
+ rb_field_type = rb_str_new_cstr("mediumblob");
366
+ break;
367
+ case MYSQL_TYPE_LONG_BLOB: // char[]
368
+ rb_field_type = rb_str_new_cstr("longblob");
369
+ break;
370
+ case MYSQL_TYPE_BIT: // char[]
371
+ rb_field_type = rb_sprintf("bit(%ld)", field->length);
372
+ break;
373
+ case MYSQL_TYPE_SET: // char[]
374
+ rb_field_type = rb_str_new_cstr("set");
375
+ break;
376
+ case MYSQL_TYPE_ENUM: // char[]
377
+ rb_field_type = rb_str_new_cstr("enum");
378
+ break;
379
+ case MYSQL_TYPE_GEOMETRY: // char[]
380
+ rb_field_type = rb_str_new_cstr("geometry");
381
+ break;
382
+ case MYSQL_TYPE_JSON: // json
383
+ rb_field_type = rb_str_new_cstr("json");
384
+ break;
385
+ default:
386
+ rb_field_type = rb_str_new_cstr("unknown");
387
+ break;
388
+ }
389
+
390
+ rb_enc_associate(rb_field_type, conn_enc);
391
+ if (default_internal_enc) {
392
+ rb_field_type = rb_str_export_to_enc(rb_field_type, default_internal_enc);
393
+ }
394
+
395
+ rb_ary_store(wrapper->fieldTypes, idx, rb_field_type);
396
+ }
397
+
398
+ return rb_field_type;
399
+ }
400
+
170
401
  static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_encoding *default_internal_enc, rb_encoding *conn_enc) {
171
402
  /* if binary flag is set, respect its wishes */
172
- if (field.flags & BINARY_FLAG && field.charsetnr == 63) {
403
+ if (field.flags & BINARY_FLAG && field.charsetnr == MYSQL2_BINARY_CHARSET) {
173
404
  rb_enc_associate(val, binaryEncoding);
174
405
  } else if (!field.charsetnr) {
175
406
  /* MySQL 4.x may not provide an encoding, binary will get the bytes through */
@@ -179,7 +410,8 @@ static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_e
179
410
  const char *enc_name;
180
411
  int enc_index;
181
412
 
182
- enc_name = mysql2_mysql_enc_to_rb[field.charsetnr-1];
413
+ enc_name = (field.charsetnr-1 < MYSQL2_CHARSETNR_SIZE) ? mysql2_mysql_enc_to_rb[field.charsetnr-1] : NULL;
414
+
183
415
  if (enc_name != NULL) {
184
416
  /* use the field encoding we were able to match */
185
417
  enc_index = rb_enc_find_index(enc_name);
@@ -693,7 +925,7 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) {
693
925
 
694
926
  GET_RESULT(self);
695
927
 
696
- defaults = rb_iv_get(self, "@query_options");
928
+ defaults = rb_ivar_get(self, intern_query_options);
697
929
  Check_Type(defaults, T_HASH);
698
930
  if (rb_hash_aref(defaults, sym_symbolize_keys) == Qtrue) {
699
931
  symbolizeKeys = 1;
@@ -713,6 +945,25 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) {
713
945
  return wrapper->fields;
714
946
  }
715
947
 
948
+ static VALUE rb_mysql_result_fetch_field_types(VALUE self) {
949
+ unsigned int i = 0;
950
+
951
+ GET_RESULT(self);
952
+
953
+ if (wrapper->fieldTypes == Qnil) {
954
+ wrapper->numberOfFields = mysql_num_fields(wrapper->result);
955
+ wrapper->fieldTypes = rb_ary_new2(wrapper->numberOfFields);
956
+ }
957
+
958
+ if ((my_ulonglong)RARRAY_LEN(wrapper->fieldTypes) != wrapper->numberOfFields) {
959
+ for (i=0; i<wrapper->numberOfFields; i++) {
960
+ rb_mysql_result_fetch_field_type(self, i);
961
+ }
962
+ }
963
+
964
+ return wrapper->fieldTypes;
965
+ }
966
+
716
967
  static VALUE rb_mysql_result_each_(VALUE self,
717
968
  VALUE(*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args),
718
969
  const result_each_args *args)
@@ -738,7 +989,7 @@ static VALUE rb_mysql_result_each_(VALUE self,
738
989
  row = fetch_row_func(self, fields, args);
739
990
  if (row != Qnil) {
740
991
  wrapper->numberOfRows++;
741
- if (args->block_given != Qnil) {
992
+ if (args->block_given) {
742
993
  rb_yield(row);
743
994
  }
744
995
  }
@@ -788,7 +1039,7 @@ static VALUE rb_mysql_result_each_(VALUE self,
788
1039
  return Qnil;
789
1040
  }
790
1041
 
791
- if (args->block_given != Qnil) {
1042
+ if (args->block_given) {
792
1043
  rb_yield(row);
793
1044
  }
794
1045
  }
@@ -806,7 +1057,7 @@ static VALUE rb_mysql_result_each_(VALUE self,
806
1057
 
807
1058
  static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
808
1059
  result_each_args args;
809
- VALUE defaults, opts, block, (*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args);
1060
+ VALUE defaults, opts, (*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args);
810
1061
  ID db_timezone, app_timezone, dbTz, appTz;
811
1062
  int symbolizeKeys, asArray, castBool, cacheRows, cast;
812
1063
 
@@ -816,9 +1067,12 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
816
1067
  rb_raise(cMysql2Error, "Statement handle already closed");
817
1068
  }
818
1069
 
819
- defaults = rb_iv_get(self, "@query_options");
1070
+ defaults = rb_ivar_get(self, intern_query_options);
820
1071
  Check_Type(defaults, T_HASH);
821
- if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1) {
1072
+
1073
+ // A block can be passed to this method, but since we don't call the block directly from C,
1074
+ // we don't need to capture it into a variable here with the "&" scan arg.
1075
+ if (rb_scan_args(argc, argv, "01", &opts) == 1) {
822
1076
  opts = rb_funcall(defaults, intern_merge, 1, opts);
823
1077
  } else {
824
1078
  opts = defaults;
@@ -884,7 +1138,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
884
1138
  args.cast = cast;
885
1139
  args.db_timezone = db_timezone;
886
1140
  args.app_timezone = app_timezone;
887
- args.block_given = block;
1141
+ args.block_given = rb_block_given_p();
888
1142
 
889
1143
  if (wrapper->stmt_wrapper) {
890
1144
  fetch_row_func = rb_mysql_result_fetch_row_stmt;
@@ -921,13 +1175,18 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_
921
1175
  VALUE obj;
922
1176
  mysql2_result_wrapper * wrapper;
923
1177
 
1178
+ #ifdef NEW_TYPEDDATA_WRAPPER
1179
+ obj = TypedData_Make_Struct(cMysql2Result, mysql2_result_wrapper, &rb_mysql_result_type, wrapper);
1180
+ #else
924
1181
  obj = Data_Make_Struct(cMysql2Result, mysql2_result_wrapper, rb_mysql_result_mark, rb_mysql_result_free, wrapper);
1182
+ #endif
925
1183
  wrapper->numberOfFields = 0;
926
1184
  wrapper->numberOfRows = 0;
927
1185
  wrapper->lastRowProcessed = 0;
928
1186
  wrapper->resultFreed = 0;
929
1187
  wrapper->result = r;
930
1188
  wrapper->fields = Qnil;
1189
+ wrapper->fieldTypes = Qnil;
931
1190
  wrapper->rows = Qnil;
932
1191
  wrapper->encoding = encoding;
933
1192
  wrapper->streamingComplete = 0;
@@ -949,7 +1208,7 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_
949
1208
  }
950
1209
 
951
1210
  rb_obj_call_init(obj, 0, NULL);
952
- rb_iv_set(obj, "@query_options", options);
1211
+ rb_ivar_set(obj, intern_query_options, options);
953
1212
 
954
1213
  /* Options that cannot be changed in results.each(...) { |row| }
955
1214
  * should be processed here. */
@@ -960,11 +1219,17 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_
960
1219
 
961
1220
  void init_mysql2_result() {
962
1221
  cDate = rb_const_get(rb_cObject, rb_intern("Date"));
1222
+ rb_global_variable(&cDate);
963
1223
  cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
1224
+ rb_global_variable(&cDateTime);
964
1225
 
965
1226
  cMysql2Result = rb_define_class_under(mMysql2, "Result", rb_cObject);
1227
+ rb_undef_alloc_func(cMysql2Result);
1228
+ rb_global_variable(&cMysql2Result);
1229
+
966
1230
  rb_define_method(cMysql2Result, "each", rb_mysql_result_each, -1);
967
1231
  rb_define_method(cMysql2Result, "fields", rb_mysql_result_fetch_fields, 0);
1232
+ rb_define_method(cMysql2Result, "field_types", rb_mysql_result_fetch_field_types, 0);
968
1233
  rb_define_method(cMysql2Result, "free", rb_mysql_result_free_, 0);
969
1234
  rb_define_method(cMysql2Result, "count", rb_mysql_result_count, 0);
970
1235
  rb_define_alias(cMysql2Result, "size", "count");
@@ -978,6 +1243,7 @@ void init_mysql2_result() {
978
1243
  intern_civil = rb_intern("civil");
979
1244
  intern_new_offset = rb_intern("new_offset");
980
1245
  intern_BigDecimal = rb_intern("BigDecimal");
1246
+ intern_query_options = rb_intern("@query_options");
981
1247
 
982
1248
  sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys"));
983
1249
  sym_as = ID2SYM(rb_intern("as"));
data/ext/mysql2/result.h CHANGED
@@ -6,6 +6,7 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_
6
6
 
7
7
  typedef struct {
8
8
  VALUE fields;
9
+ VALUE fieldTypes;
9
10
  VALUE rows;
10
11
  VALUE client;
11
12
  VALUE encoding;
@@ -3,11 +3,16 @@
3
3
  extern VALUE mMysql2, cMysql2Error;
4
4
  static VALUE cMysql2Statement, cBigDecimal, cDateTime, cDate;
5
5
  static VALUE sym_stream, intern_new_with_args, intern_each, intern_to_s, intern_merge_bang;
6
- static VALUE intern_sec_fraction, intern_usec, intern_sec, intern_min, intern_hour, intern_day, intern_month, intern_year;
6
+ static VALUE intern_sec_fraction, intern_usec, intern_sec, intern_min, intern_hour, intern_day, intern_month, intern_year,
7
+ intern_query_options;
8
+
9
+ #ifndef NEW_TYPEDDATA_WRAPPER
10
+ #define TypedData_Get_Struct(obj, type, ignore, sval) Data_Get_Struct(obj, type, sval)
11
+ #endif
7
12
 
8
13
  #define GET_STATEMENT(self) \
9
14
  mysql_stmt_wrapper *stmt_wrapper; \
10
- Data_Get_Struct(self, mysql_stmt_wrapper, stmt_wrapper); \
15
+ TypedData_Get_Struct(self, mysql_stmt_wrapper, &rb_mysql_statement_type, stmt_wrapper); \
11
16
  if (!stmt_wrapper->stmt) { rb_raise(cMysql2Error, "Invalid statement handle"); } \
12
17
  if (stmt_wrapper->closed) { rb_raise(cMysql2Error, "Statement handle already closed"); }
13
18
 
@@ -15,8 +20,44 @@ static void rb_mysql_stmt_mark(void * ptr) {
15
20
  mysql_stmt_wrapper *stmt_wrapper = ptr;
16
21
  if (!stmt_wrapper) return;
17
22
 
18
- rb_gc_mark(stmt_wrapper->client);
23
+ rb_gc_mark_movable(stmt_wrapper->client);
24
+ }
25
+
26
+ static void rb_mysql_stmt_free(void *ptr) {
27
+ mysql_stmt_wrapper *stmt_wrapper = ptr;
28
+ decr_mysql2_stmt(stmt_wrapper);
29
+ }
30
+
31
+ static size_t rb_mysql_stmt_memsize(const void * ptr) {
32
+ const mysql_stmt_wrapper *stmt_wrapper = ptr;
33
+ return sizeof(*stmt_wrapper);
34
+ }
35
+
36
+ #ifdef HAVE_RB_GC_MARK_MOVABLE
37
+ static void rb_mysql_stmt_compact(void * ptr) {
38
+ mysql_stmt_wrapper *stmt_wrapper = ptr;
39
+ if (!stmt_wrapper) return;
40
+
41
+ rb_mysql2_gc_location(stmt_wrapper->client);
19
42
  }
43
+ #endif
44
+
45
+ static const rb_data_type_t rb_mysql_statement_type = {
46
+ "rb_mysql_statement",
47
+ {
48
+ rb_mysql_stmt_mark,
49
+ rb_mysql_stmt_free,
50
+ rb_mysql_stmt_memsize,
51
+ #ifdef HAVE_RB_GC_MARK_MOVABLE
52
+ rb_mysql_stmt_compact,
53
+ #endif
54
+ },
55
+ 0,
56
+ 0,
57
+ #ifdef RUBY_TYPED_FREE_IMMEDIATELY
58
+ RUBY_TYPED_FREE_IMMEDIATELY,
59
+ #endif
60
+ };
20
61
 
21
62
  static void *nogvl_stmt_close(void *ptr) {
22
63
  mysql_stmt_wrapper *stmt_wrapper = ptr;
@@ -27,11 +68,6 @@ static void *nogvl_stmt_close(void *ptr) {
27
68
  return NULL;
28
69
  }
29
70
 
30
- static void rb_mysql_stmt_free(void *ptr) {
31
- mysql_stmt_wrapper *stmt_wrapper = ptr;
32
- decr_mysql2_stmt(stmt_wrapper);
33
- }
34
-
35
71
  void decr_mysql2_stmt(mysql_stmt_wrapper *stmt_wrapper) {
36
72
  stmt_wrapper->refcount--;
37
73
 
@@ -45,7 +81,7 @@ void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) {
45
81
  VALUE e;
46
82
  GET_CLIENT(stmt_wrapper->client);
47
83
  VALUE rb_error_msg = rb_str_new2(mysql_stmt_error(stmt_wrapper->stmt));
48
- VALUE rb_sql_state = rb_tainted_str_new2(mysql_stmt_sqlstate(stmt_wrapper->stmt));
84
+ VALUE rb_sql_state = rb_str_new2(mysql_stmt_sqlstate(stmt_wrapper->stmt));
49
85
 
50
86
  rb_encoding *conn_enc;
51
87
  conn_enc = rb_to_encoding(wrapper->encoding);
@@ -95,7 +131,11 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
95
131
 
96
132
  Check_Type(sql, T_STRING);
97
133
 
134
+ #ifdef NEW_TYPEDDATA_WRAPPER
135
+ rb_stmt = TypedData_Make_Struct(cMysql2Statement, mysql_stmt_wrapper, &rb_mysql_statement_type, stmt_wrapper);
136
+ #else
98
137
  rb_stmt = Data_Make_Struct(cMysql2Statement, mysql_stmt_wrapper, rb_mysql_stmt_mark, rb_mysql_stmt_free, stmt_wrapper);
138
+ #endif
99
139
  {
100
140
  stmt_wrapper->client = rb_client;
101
141
  stmt_wrapper->refcount = 1;
@@ -403,6 +443,39 @@ static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) {
403
443
  }
404
444
  }
405
445
 
446
+ // Duplicate the options hash, merge! extra opts, put the copy into the Result object
447
+ current = rb_hash_dup(rb_ivar_get(stmt_wrapper->client, intern_query_options));
448
+ (void)RB_GC_GUARD(current);
449
+ Check_Type(current, T_HASH);
450
+
451
+ // Merge in hash opts/keyword arguments
452
+ if (!NIL_P(opts)) {
453
+ rb_funcall(current, intern_merge_bang, 1, opts);
454
+ }
455
+
456
+ is_streaming = (Qtrue == rb_hash_aref(current, sym_stream));
457
+
458
+ // From stmt_execute to mysql_stmt_result_metadata to stmt_store_result, no
459
+ // Ruby API calls are allowed so that GC is not invoked. If the connection is
460
+ // in results-streaming-mode for Statement A, and in the middle Statement B
461
+ // gets garbage collected, a message will be sent to the server notifying it
462
+ // to release Statement B, resulting in the following error:
463
+ // Commands out of sync; you can't run this command now
464
+ //
465
+ // In streaming mode, statement execute must return a cursor because we
466
+ // cannot prevent other Statement objects from being garbage collected
467
+ // between fetches of each row of the result set. The following error
468
+ // occurs if cursor mode is not set:
469
+ // Row retrieval was canceled by mysql_stmt_close
470
+
471
+ if (is_streaming) {
472
+ unsigned long type = CURSOR_TYPE_READ_ONLY;
473
+ if (mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, &type)) {
474
+ FREE_BINDS;
475
+ rb_raise(cMysql2Error, "Unable to stream prepared statement, could not set CURSOR_TYPE_READ_ONLY");
476
+ }
477
+ }
478
+
406
479
  if ((VALUE)rb_thread_call_without_gvl(nogvl_stmt_execute, stmt, RUBY_UBF_IO, 0) == Qfalse) {
407
480
  FREE_BINDS;
408
481
  rb_raise_mysql2_stmt_error(stmt_wrapper);
@@ -414,31 +487,20 @@ static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) {
414
487
  if (metadata == NULL) {
415
488
  if (mysql_stmt_errno(stmt) != 0) {
416
489
  // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal.
417
- wrapper->active_thread = Qnil;
490
+ wrapper->active_fiber = Qnil;
418
491
  rb_raise_mysql2_stmt_error(stmt_wrapper);
419
492
  }
420
493
  // no data and no error, so query was not a SELECT
421
494
  return Qnil;
422
495
  }
423
496
 
424
- // Duplicate the options hash, merge! extra opts, put the copy into the Result object
425
- current = rb_hash_dup(rb_iv_get(stmt_wrapper->client, "@query_options"));
426
- (void)RB_GC_GUARD(current);
427
- Check_Type(current, T_HASH);
428
-
429
- // Merge in hash opts/keyword arguments
430
- if (!NIL_P(opts)) {
431
- rb_funcall(current, intern_merge_bang, 1, opts);
432
- }
433
-
434
- is_streaming = (Qtrue == rb_hash_aref(current, sym_stream));
435
497
  if (!is_streaming) {
436
- // recieve the whole result set from the server
498
+ // receive the whole result set from the server
437
499
  if (mysql_stmt_store_result(stmt)) {
438
500
  mysql_free_result(metadata);
439
501
  rb_raise_mysql2_stmt_error(stmt_wrapper);
440
502
  }
441
- wrapper->active_thread = Qnil;
503
+ wrapper->active_fiber = Qnil;
442
504
  }
443
505
 
444
506
  resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, metadata, self);
@@ -479,7 +541,7 @@ static VALUE rb_mysql_stmt_fields(VALUE self) {
479
541
  if (metadata == NULL) {
480
542
  if (mysql_stmt_errno(stmt) != 0) {
481
543
  // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal.
482
- wrapper->active_thread = Qnil;
544
+ wrapper->active_fiber = Qnil;
483
545
  rb_raise_mysql2_stmt_error(stmt_wrapper);
484
546
  }
485
547
  // no data and no error, so query was not a SELECT
@@ -549,10 +611,18 @@ static VALUE rb_mysql_stmt_close(VALUE self) {
549
611
 
550
612
  void init_mysql2_statement() {
551
613
  cDate = rb_const_get(rb_cObject, rb_intern("Date"));
614
+ rb_global_variable(&cDate);
615
+
552
616
  cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
617
+ rb_global_variable(&cDateTime);
618
+
553
619
  cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal"));
620
+ rb_global_variable(&cBigDecimal);
554
621
 
555
622
  cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject);
623
+ rb_undef_alloc_func(cMysql2Statement);
624
+ rb_global_variable(&cMysql2Statement);
625
+
556
626
  rb_define_method(cMysql2Statement, "param_count", rb_mysql_stmt_param_count, 0);
557
627
  rb_define_method(cMysql2Statement, "field_count", rb_mysql_stmt_field_count, 0);
558
628
  rb_define_method(cMysql2Statement, "_execute", rb_mysql_stmt_execute, -1);
@@ -577,4 +647,5 @@ void init_mysql2_statement() {
577
647
 
578
648
  intern_to_s = rb_intern("to_s");
579
649
  intern_merge_bang = rb_intern("merge!");
650
+ intern_query_options = rb_intern("@query_options");
580
651
  }