mysql2 0.5.0 → 0.5.4

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 (45) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +81 -34
  3. data/ext/mysql2/client.c +123 -25
  4. data/ext/mysql2/extconf.rb +24 -6
  5. data/ext/mysql2/mysql2_ext.c +6 -1
  6. data/ext/mysql2/mysql_enc_name_to_ruby.h +60 -56
  7. data/ext/mysql2/mysql_enc_to_ruby.h +64 -3
  8. data/ext/mysql2/result.c +231 -15
  9. data/ext/mysql2/result.h +1 -0
  10. data/ext/mysql2/statement.c +46 -14
  11. data/lib/mysql2/client.rb +20 -3
  12. data/lib/mysql2/error.rb +4 -3
  13. data/lib/mysql2/statement.rb +1 -3
  14. data/lib/mysql2/version.rb +1 -1
  15. data/lib/mysql2.rb +7 -3
  16. data/support/3A79BD29.asc +49 -0
  17. data/support/5072E1F5.asc +5 -5
  18. data/support/mysql_enc_to_ruby.rb +6 -1
  19. data/support/ruby_enc_to_mysql.rb +2 -0
  20. metadata +10 -55
  21. data/examples/eventmachine.rb +0 -19
  22. data/examples/threaded.rb +0 -16
  23. data/spec/configuration.yml.example +0 -11
  24. data/spec/em/em_spec.rb +0 -135
  25. data/spec/my.cnf.example +0 -9
  26. data/spec/mysql2/client_spec.rb +0 -1072
  27. data/spec/mysql2/error_spec.rb +0 -78
  28. data/spec/mysql2/result_spec.rb +0 -485
  29. data/spec/mysql2/statement_spec.rb +0 -708
  30. data/spec/rcov.opts +0 -3
  31. data/spec/spec_helper.rb +0 -112
  32. data/spec/ssl/ca-cert.pem +0 -17
  33. data/spec/ssl/ca-key.pem +0 -27
  34. data/spec/ssl/ca.cnf +0 -22
  35. data/spec/ssl/cert.cnf +0 -22
  36. data/spec/ssl/client-cert.pem +0 -17
  37. data/spec/ssl/client-key.pem +0 -27
  38. data/spec/ssl/client-req.pem +0 -15
  39. data/spec/ssl/gen_certs.sh +0 -48
  40. data/spec/ssl/pkcs8-client-key.pem +0 -28
  41. data/spec/ssl/pkcs8-server-key.pem +0 -28
  42. data/spec/ssl/server-cert.pem +0 -17
  43. data/spec/ssl/server-key.pem +0 -27
  44. data/spec/ssl/server-req.pem +0 -15
  45. 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,6 +17,20 @@ 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
+
19
34
  #define GET_RESULT(self) \
20
35
  mysql2_result_wrapper *wrapper; \
21
36
  Data_Get_Struct(self, mysql2_result_wrapper, wrapper);
@@ -29,14 +44,15 @@ typedef struct {
29
44
  int streaming;
30
45
  ID db_timezone;
31
46
  ID app_timezone;
32
- VALUE block_given;
47
+ int block_given; /* boolean */
33
48
  } result_each_args;
34
49
 
35
50
  extern VALUE mMysql2, cMysql2Client, cMysql2Error;
36
51
  static VALUE cMysql2Result, cDateTime, cDate;
37
52
  static VALUE opt_decimal_zero, opt_float_zero, opt_time_year, opt_time_month, opt_utc_offset;
38
53
  static ID intern_new, intern_utc, intern_local, intern_localtime, intern_local_offset,
39
- intern_civil, intern_new_offset, intern_merge, intern_BigDecimal;
54
+ intern_civil, intern_new_offset, intern_merge, intern_BigDecimal,
55
+ intern_query_options;
40
56
  static VALUE sym_symbolize_keys, sym_as, sym_array, sym_database_timezone,
41
57
  sym_application_timezone, sym_local, sym_utc, sym_cast_booleans,
42
58
  sym_cache_rows, sym_cast, sym_stream, sym_name;
@@ -155,11 +171,18 @@ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, int symbo
155
171
  rb_field = rb_intern3(field->name, field->name_length, rb_utf8_encoding());
156
172
  rb_field = ID2SYM(rb_field);
157
173
  } 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) {
174
+ #ifdef HAVE_RB_ENC_INTERNED_STR
175
+ rb_field = rb_enc_interned_str(field->name, field->name_length, conn_enc);
176
+ if (default_internal_enc && default_internal_enc != conn_enc) {
177
+ rb_field = rb_str_to_interned_str(rb_str_export_to_enc(rb_field, default_internal_enc));
178
+ }
179
+ #else
180
+ rb_field = rb_enc_str_new(field->name, field->name_length, conn_enc);
181
+ if (default_internal_enc && default_internal_enc != conn_enc) {
161
182
  rb_field = rb_str_export_to_enc(rb_field, default_internal_enc);
162
183
  }
184
+ rb_obj_freeze(rb_field);
185
+ #endif
163
186
  }
164
187
  rb_ary_store(wrapper->fields, idx, rb_field);
165
188
  }
@@ -167,9 +190,171 @@ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, int symbo
167
190
  return rb_field;
168
191
  }
169
192
 
193
+ static VALUE rb_mysql_result_fetch_field_type(VALUE self, unsigned int idx) {
194
+ VALUE rb_field_type;
195
+ GET_RESULT(self);
196
+
197
+ if (wrapper->fieldTypes == Qnil) {
198
+ wrapper->numberOfFields = mysql_num_fields(wrapper->result);
199
+ wrapper->fieldTypes = rb_ary_new2(wrapper->numberOfFields);
200
+ }
201
+
202
+ rb_field_type = rb_ary_entry(wrapper->fieldTypes, idx);
203
+ if (rb_field_type == Qnil) {
204
+ MYSQL_FIELD *field = NULL;
205
+ rb_encoding *default_internal_enc = rb_default_internal_encoding();
206
+ rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
207
+ int precision;
208
+
209
+ field = mysql_fetch_field_direct(wrapper->result, idx);
210
+
211
+ switch(field->type) {
212
+ case MYSQL_TYPE_NULL: // NULL
213
+ rb_field_type = rb_str_new_cstr("null");
214
+ break;
215
+ case MYSQL_TYPE_TINY: // signed char
216
+ rb_field_type = rb_sprintf("tinyint(%ld)", field->length);
217
+ break;
218
+ case MYSQL_TYPE_SHORT: // short int
219
+ rb_field_type = rb_sprintf("smallint(%ld)", field->length);
220
+ break;
221
+ case MYSQL_TYPE_YEAR: // short int
222
+ rb_field_type = rb_sprintf("year(%ld)", field->length);
223
+ break;
224
+ case MYSQL_TYPE_INT24: // int
225
+ rb_field_type = rb_sprintf("mediumint(%ld)", field->length);
226
+ break;
227
+ case MYSQL_TYPE_LONG: // int
228
+ rb_field_type = rb_sprintf("int(%ld)", field->length);
229
+ break;
230
+ case MYSQL_TYPE_LONGLONG: // long long int
231
+ rb_field_type = rb_sprintf("bigint(%ld)", field->length);
232
+ break;
233
+ case MYSQL_TYPE_FLOAT: // float
234
+ rb_field_type = rb_sprintf("float(%ld,%d)", field->length, field->decimals);
235
+ break;
236
+ case MYSQL_TYPE_DOUBLE: // double
237
+ rb_field_type = rb_sprintf("double(%ld,%d)", field->length, field->decimals);
238
+ break;
239
+ case MYSQL_TYPE_TIME: // MYSQL_TIME
240
+ rb_field_type = rb_str_new_cstr("time");
241
+ break;
242
+ case MYSQL_TYPE_DATE: // MYSQL_TIME
243
+ case MYSQL_TYPE_NEWDATE: // MYSQL_TIME
244
+ rb_field_type = rb_str_new_cstr("date");
245
+ break;
246
+ case MYSQL_TYPE_DATETIME: // MYSQL_TIME
247
+ rb_field_type = rb_str_new_cstr("datetime");
248
+ break;
249
+ case MYSQL_TYPE_TIMESTAMP: // MYSQL_TIME
250
+ rb_field_type = rb_str_new_cstr("timestamp");
251
+ break;
252
+ case MYSQL_TYPE_DECIMAL: // char[]
253
+ case MYSQL_TYPE_NEWDECIMAL: // char[]
254
+ /*
255
+ Handle precision similar to this line from mysql's code:
256
+ https://github.com/mysql/mysql-server/blob/ea7d2e2d16ac03afdd9cb72a972a95981107bf51/sql/field.cc#L2246
257
+ */
258
+ precision = field->length - (field->decimals > 0 ? 2 : 1);
259
+ rb_field_type = rb_sprintf("decimal(%d,%d)", precision, field->decimals);
260
+ break;
261
+ case MYSQL_TYPE_STRING: // char[]
262
+ if (field->flags & ENUM_FLAG) {
263
+ rb_field_type = rb_str_new_cstr("enum");
264
+ } else if (field->flags & SET_FLAG) {
265
+ rb_field_type = rb_str_new_cstr("set");
266
+ } else {
267
+ if (field->charsetnr == MYSQL2_BINARY_CHARSET) {
268
+ rb_field_type = rb_sprintf("binary(%ld)", field->length);
269
+ } else {
270
+ rb_field_type = rb_sprintf("char(%ld)", field->length / MYSQL2_MAX_BYTES_PER_CHAR);
271
+ }
272
+ }
273
+ break;
274
+ case MYSQL_TYPE_VAR_STRING: // char[]
275
+ if (field->charsetnr == MYSQL2_BINARY_CHARSET) {
276
+ rb_field_type = rb_sprintf("varbinary(%ld)", field->length);
277
+ } else {
278
+ rb_field_type = rb_sprintf("varchar(%ld)", field->length / MYSQL2_MAX_BYTES_PER_CHAR);
279
+ }
280
+ break;
281
+ case MYSQL_TYPE_VARCHAR: // char[]
282
+ rb_field_type = rb_sprintf("varchar(%ld)", field->length / MYSQL2_MAX_BYTES_PER_CHAR);
283
+ break;
284
+ case MYSQL_TYPE_TINY_BLOB: // char[]
285
+ rb_field_type = rb_str_new_cstr("tinyblob");
286
+ break;
287
+ case MYSQL_TYPE_BLOB: // char[]
288
+ if (field->charsetnr == MYSQL2_BINARY_CHARSET) {
289
+ switch(field->length) {
290
+ case 255:
291
+ rb_field_type = rb_str_new_cstr("tinyblob");
292
+ break;
293
+ case 65535:
294
+ rb_field_type = rb_str_new_cstr("blob");
295
+ break;
296
+ case 16777215:
297
+ rb_field_type = rb_str_new_cstr("mediumblob");
298
+ break;
299
+ case 4294967295:
300
+ rb_field_type = rb_str_new_cstr("longblob");
301
+ default:
302
+ break;
303
+ }
304
+ } else {
305
+ if (field->length == (255 * MYSQL2_MAX_BYTES_PER_CHAR)) {
306
+ rb_field_type = rb_str_new_cstr("tinytext");
307
+ } else if (field->length == (65535 * MYSQL2_MAX_BYTES_PER_CHAR)) {
308
+ rb_field_type = rb_str_new_cstr("text");
309
+ } else if (field->length == (16777215 * MYSQL2_MAX_BYTES_PER_CHAR)) {
310
+ rb_field_type = rb_str_new_cstr("mediumtext");
311
+ } else if (field->length == 4294967295) {
312
+ rb_field_type = rb_str_new_cstr("longtext");
313
+ } else {
314
+ rb_field_type = rb_sprintf("text(%ld)", field->length);
315
+ }
316
+ }
317
+ break;
318
+ case MYSQL_TYPE_MEDIUM_BLOB: // char[]
319
+ rb_field_type = rb_str_new_cstr("mediumblob");
320
+ break;
321
+ case MYSQL_TYPE_LONG_BLOB: // char[]
322
+ rb_field_type = rb_str_new_cstr("longblob");
323
+ break;
324
+ case MYSQL_TYPE_BIT: // char[]
325
+ rb_field_type = rb_sprintf("bit(%ld)", field->length);
326
+ break;
327
+ case MYSQL_TYPE_SET: // char[]
328
+ rb_field_type = rb_str_new_cstr("set");
329
+ break;
330
+ case MYSQL_TYPE_ENUM: // char[]
331
+ rb_field_type = rb_str_new_cstr("enum");
332
+ break;
333
+ case MYSQL_TYPE_GEOMETRY: // char[]
334
+ rb_field_type = rb_str_new_cstr("geometry");
335
+ break;
336
+ case MYSQL_TYPE_JSON: // json
337
+ rb_field_type = rb_str_new_cstr("json");
338
+ break;
339
+ default:
340
+ rb_field_type = rb_str_new_cstr("unknown");
341
+ break;
342
+ }
343
+
344
+ rb_enc_associate(rb_field_type, conn_enc);
345
+ if (default_internal_enc) {
346
+ rb_field_type = rb_str_export_to_enc(rb_field_type, default_internal_enc);
347
+ }
348
+
349
+ rb_ary_store(wrapper->fieldTypes, idx, rb_field_type);
350
+ }
351
+
352
+ return rb_field_type;
353
+ }
354
+
170
355
  static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_encoding *default_internal_enc, rb_encoding *conn_enc) {
171
356
  /* if binary flag is set, respect its wishes */
172
- if (field.flags & BINARY_FLAG && field.charsetnr == 63) {
357
+ if (field.flags & BINARY_FLAG && field.charsetnr == MYSQL2_BINARY_CHARSET) {
173
358
  rb_enc_associate(val, binaryEncoding);
174
359
  } else if (!field.charsetnr) {
175
360
  /* MySQL 4.x may not provide an encoding, binary will get the bytes through */
@@ -179,7 +364,8 @@ static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_e
179
364
  const char *enc_name;
180
365
  int enc_index;
181
366
 
182
- enc_name = mysql2_mysql_enc_to_rb[field.charsetnr-1];
367
+ enc_name = (field.charsetnr-1 < MYSQL2_CHARSETNR_SIZE) ? mysql2_mysql_enc_to_rb[field.charsetnr-1] : NULL;
368
+
183
369
  if (enc_name != NULL) {
184
370
  /* use the field encoding we were able to match */
185
371
  enc_index = rb_enc_find_index(enc_name);
@@ -693,7 +879,7 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) {
693
879
 
694
880
  GET_RESULT(self);
695
881
 
696
- defaults = rb_iv_get(self, "@query_options");
882
+ defaults = rb_ivar_get(self, intern_query_options);
697
883
  Check_Type(defaults, T_HASH);
698
884
  if (rb_hash_aref(defaults, sym_symbolize_keys) == Qtrue) {
699
885
  symbolizeKeys = 1;
@@ -713,6 +899,25 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) {
713
899
  return wrapper->fields;
714
900
  }
715
901
 
902
+ static VALUE rb_mysql_result_fetch_field_types(VALUE self) {
903
+ unsigned int i = 0;
904
+
905
+ GET_RESULT(self);
906
+
907
+ if (wrapper->fieldTypes == Qnil) {
908
+ wrapper->numberOfFields = mysql_num_fields(wrapper->result);
909
+ wrapper->fieldTypes = rb_ary_new2(wrapper->numberOfFields);
910
+ }
911
+
912
+ if ((my_ulonglong)RARRAY_LEN(wrapper->fieldTypes) != wrapper->numberOfFields) {
913
+ for (i=0; i<wrapper->numberOfFields; i++) {
914
+ rb_mysql_result_fetch_field_type(self, i);
915
+ }
916
+ }
917
+
918
+ return wrapper->fieldTypes;
919
+ }
920
+
716
921
  static VALUE rb_mysql_result_each_(VALUE self,
717
922
  VALUE(*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args),
718
923
  const result_each_args *args)
@@ -738,7 +943,7 @@ static VALUE rb_mysql_result_each_(VALUE self,
738
943
  row = fetch_row_func(self, fields, args);
739
944
  if (row != Qnil) {
740
945
  wrapper->numberOfRows++;
741
- if (args->block_given != Qnil) {
946
+ if (args->block_given) {
742
947
  rb_yield(row);
743
948
  }
744
949
  }
@@ -788,7 +993,7 @@ static VALUE rb_mysql_result_each_(VALUE self,
788
993
  return Qnil;
789
994
  }
790
995
 
791
- if (args->block_given != Qnil) {
996
+ if (args->block_given) {
792
997
  rb_yield(row);
793
998
  }
794
999
  }
@@ -806,7 +1011,7 @@ static VALUE rb_mysql_result_each_(VALUE self,
806
1011
 
807
1012
  static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
808
1013
  result_each_args args;
809
- VALUE defaults, opts, block, (*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args);
1014
+ VALUE defaults, opts, (*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args);
810
1015
  ID db_timezone, app_timezone, dbTz, appTz;
811
1016
  int symbolizeKeys, asArray, castBool, cacheRows, cast;
812
1017
 
@@ -816,9 +1021,12 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
816
1021
  rb_raise(cMysql2Error, "Statement handle already closed");
817
1022
  }
818
1023
 
819
- defaults = rb_iv_get(self, "@query_options");
1024
+ defaults = rb_ivar_get(self, intern_query_options);
820
1025
  Check_Type(defaults, T_HASH);
821
- if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1) {
1026
+
1027
+ // A block can be passed to this method, but since we don't call the block directly from C,
1028
+ // we don't need to capture it into a variable here with the "&" scan arg.
1029
+ if (rb_scan_args(argc, argv, "01", &opts) == 1) {
822
1030
  opts = rb_funcall(defaults, intern_merge, 1, opts);
823
1031
  } else {
824
1032
  opts = defaults;
@@ -884,7 +1092,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
884
1092
  args.cast = cast;
885
1093
  args.db_timezone = db_timezone;
886
1094
  args.app_timezone = app_timezone;
887
- args.block_given = block;
1095
+ args.block_given = rb_block_given_p();
888
1096
 
889
1097
  if (wrapper->stmt_wrapper) {
890
1098
  fetch_row_func = rb_mysql_result_fetch_row_stmt;
@@ -928,6 +1136,7 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_
928
1136
  wrapper->resultFreed = 0;
929
1137
  wrapper->result = r;
930
1138
  wrapper->fields = Qnil;
1139
+ wrapper->fieldTypes = Qnil;
931
1140
  wrapper->rows = Qnil;
932
1141
  wrapper->encoding = encoding;
933
1142
  wrapper->streamingComplete = 0;
@@ -949,7 +1158,7 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_
949
1158
  }
950
1159
 
951
1160
  rb_obj_call_init(obj, 0, NULL);
952
- rb_iv_set(obj, "@query_options", options);
1161
+ rb_ivar_set(obj, intern_query_options, options);
953
1162
 
954
1163
  /* Options that cannot be changed in results.each(...) { |row| }
955
1164
  * should be processed here. */
@@ -960,11 +1169,17 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_
960
1169
 
961
1170
  void init_mysql2_result() {
962
1171
  cDate = rb_const_get(rb_cObject, rb_intern("Date"));
1172
+ rb_global_variable(&cDate);
963
1173
  cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
1174
+ rb_global_variable(&cDateTime);
964
1175
 
965
1176
  cMysql2Result = rb_define_class_under(mMysql2, "Result", rb_cObject);
1177
+ rb_undef_alloc_func(cMysql2Result);
1178
+ rb_global_variable(&cMysql2Result);
1179
+
966
1180
  rb_define_method(cMysql2Result, "each", rb_mysql_result_each, -1);
967
1181
  rb_define_method(cMysql2Result, "fields", rb_mysql_result_fetch_fields, 0);
1182
+ rb_define_method(cMysql2Result, "field_types", rb_mysql_result_fetch_field_types, 0);
968
1183
  rb_define_method(cMysql2Result, "free", rb_mysql_result_free_, 0);
969
1184
  rb_define_method(cMysql2Result, "count", rb_mysql_result_count, 0);
970
1185
  rb_define_alias(cMysql2Result, "size", "count");
@@ -978,6 +1193,7 @@ void init_mysql2_result() {
978
1193
  intern_civil = rb_intern("civil");
979
1194
  intern_new_offset = rb_intern("new_offset");
980
1195
  intern_BigDecimal = rb_intern("BigDecimal");
1196
+ intern_query_options = rb_intern("@query_options");
981
1197
 
982
1198
  sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys"));
983
1199
  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,7 +3,8 @@
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;
7
8
 
8
9
  #define GET_STATEMENT(self) \
9
10
  mysql_stmt_wrapper *stmt_wrapper; \
@@ -45,7 +46,7 @@ void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) {
45
46
  VALUE e;
46
47
  GET_CLIENT(stmt_wrapper->client);
47
48
  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));
49
+ VALUE rb_sql_state = rb_str_new2(mysql_stmt_sqlstate(stmt_wrapper->stmt));
49
50
 
50
51
  rb_encoding *conn_enc;
51
52
  conn_enc = rb_to_encoding(wrapper->encoding);
@@ -403,6 +404,39 @@ static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) {
403
404
  }
404
405
  }
405
406
 
407
+ // Duplicate the options hash, merge! extra opts, put the copy into the Result object
408
+ current = rb_hash_dup(rb_ivar_get(stmt_wrapper->client, intern_query_options));
409
+ (void)RB_GC_GUARD(current);
410
+ Check_Type(current, T_HASH);
411
+
412
+ // Merge in hash opts/keyword arguments
413
+ if (!NIL_P(opts)) {
414
+ rb_funcall(current, intern_merge_bang, 1, opts);
415
+ }
416
+
417
+ is_streaming = (Qtrue == rb_hash_aref(current, sym_stream));
418
+
419
+ // From stmt_execute to mysql_stmt_result_metadata to stmt_store_result, no
420
+ // Ruby API calls are allowed so that GC is not invoked. If the connection is
421
+ // in results-streaming-mode for Statement A, and in the middle Statement B
422
+ // gets garbage collected, a message will be sent to the server notifying it
423
+ // to release Statement B, resulting in the following error:
424
+ // Commands out of sync; you can't run this command now
425
+ //
426
+ // In streaming mode, statement execute must return a cursor because we
427
+ // cannot prevent other Statement objects from being garbage collected
428
+ // between fetches of each row of the result set. The following error
429
+ // occurs if cursor mode is not set:
430
+ // Row retrieval was canceled by mysql_stmt_close
431
+
432
+ if (is_streaming) {
433
+ unsigned long type = CURSOR_TYPE_READ_ONLY;
434
+ if (mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, &type)) {
435
+ FREE_BINDS;
436
+ rb_raise(cMysql2Error, "Unable to stream prepared statement, could not set CURSOR_TYPE_READ_ONLY");
437
+ }
438
+ }
439
+
406
440
  if ((VALUE)rb_thread_call_without_gvl(nogvl_stmt_execute, stmt, RUBY_UBF_IO, 0) == Qfalse) {
407
441
  FREE_BINDS;
408
442
  rb_raise_mysql2_stmt_error(stmt_wrapper);
@@ -421,19 +455,8 @@ static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) {
421
455
  return Qnil;
422
456
  }
423
457
 
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
458
  if (!is_streaming) {
436
- // recieve the whole result set from the server
459
+ // receive the whole result set from the server
437
460
  if (mysql_stmt_store_result(stmt)) {
438
461
  mysql_free_result(metadata);
439
462
  rb_raise_mysql2_stmt_error(stmt_wrapper);
@@ -549,10 +572,18 @@ static VALUE rb_mysql_stmt_close(VALUE self) {
549
572
 
550
573
  void init_mysql2_statement() {
551
574
  cDate = rb_const_get(rb_cObject, rb_intern("Date"));
575
+ rb_global_variable(&cDate);
576
+
552
577
  cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
578
+ rb_global_variable(&cDateTime);
579
+
553
580
  cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal"));
581
+ rb_global_variable(&cBigDecimal);
554
582
 
555
583
  cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject);
584
+ rb_undef_alloc_func(cMysql2Statement);
585
+ rb_global_variable(&cMysql2Statement);
586
+
556
587
  rb_define_method(cMysql2Statement, "param_count", rb_mysql_stmt_param_count, 0);
557
588
  rb_define_method(cMysql2Statement, "field_count", rb_mysql_stmt_field_count, 0);
558
589
  rb_define_method(cMysql2Statement, "_execute", rb_mysql_stmt_execute, -1);
@@ -577,4 +608,5 @@ void init_mysql2_statement() {
577
608
 
578
609
  intern_to_s = rb_intern("to_s");
579
610
  intern_merge_bang = rb_intern("merge!");
611
+ intern_query_options = rb_intern("@query_options");
580
612
  }
data/lib/mysql2/client.rb CHANGED
@@ -31,7 +31,7 @@ module Mysql2
31
31
  opts[:connect_timeout] = 120 unless opts.key?(:connect_timeout)
32
32
 
33
33
  # TODO: stricter validation rather than silent massaging
34
- %i[reconnect connect_timeout local_infile read_timeout write_timeout default_file default_group secure_auth init_command automatic_close enable_cleartext_plugin].each do |key|
34
+ %i[reconnect connect_timeout local_infile read_timeout write_timeout default_file default_group secure_auth init_command automatic_close enable_cleartext_plugin default_auth].each do |key|
35
35
  next unless opts.key?(key)
36
36
  case key
37
37
  when :reconnect, :local_infile, :secure_auth, :automatic_close, :enable_cleartext_plugin
@@ -46,9 +46,14 @@ module Mysql2
46
46
  # force the encoding to utf8
47
47
  self.charset_name = opts[:encoding] || 'utf8'
48
48
 
49
+ mode = parse_ssl_mode(opts[:ssl_mode]) if opts[:ssl_mode]
50
+ if (mode == SSL_MODE_VERIFY_CA || mode == SSL_MODE_VERIFY_IDENTITY) && !opts[:sslca]
51
+ opts[:sslca] = find_default_ca_path
52
+ end
53
+
49
54
  ssl_options = opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslcipher)
50
55
  ssl_set(*ssl_options) if ssl_options.any? || opts.key?(:sslverify)
51
- self.ssl_mode = parse_ssl_mode(opts[:ssl_mode]) if opts[:ssl_mode]
56
+ self.ssl_mode = mode if mode
52
57
 
53
58
  flags = case opts[:flags]
54
59
  when Array
@@ -115,6 +120,18 @@ module Mysql2
115
120
  end
116
121
  end
117
122
 
123
+ # Find any default system CA paths to handle system roots
124
+ # by default if stricter validation is requested and no
125
+ # path is provide.
126
+ def find_default_ca_path
127
+ [
128
+ "/etc/ssl/certs/ca-certificates.crt",
129
+ "/etc/pki/tls/certs/ca-bundle.crt",
130
+ "/etc/ssl/ca-bundle.pem",
131
+ "/etc/ssl/cert.pem",
132
+ ].find { |f| File.exist?(f) }
133
+ end
134
+
118
135
  # Set default program_name in performance_schema.session_connect_attrs
119
136
  # and performance_schema.session_account_connect_attrs
120
137
  def parse_connect_attrs(conn_attrs)
@@ -127,7 +144,7 @@ module Mysql2
127
144
  end
128
145
 
129
146
  def query(sql, options = {})
130
- Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_CLASS => :never) do
147
+ Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_NEVER) do
131
148
  _query(sql, @query_options.merge(options))
132
149
  end
133
150
  end
data/lib/mysql2/error.rb CHANGED
@@ -24,6 +24,7 @@ module Mysql2
24
24
  1159 => ConnectionError, # ER_NET_READ_INTERRUPTED
25
25
  1160 => ConnectionError, # ER_NET_ERROR_ON_WRITE
26
26
  1161 => ConnectionError, # ER_NET_WRITE_INTERRUPTED
27
+ 1927 => ConnectionError, # ER_CONNECTION_KILLED
27
28
 
28
29
  2001 => ConnectionError, # CR_SOCKET_CREATE_ERROR
29
30
  2002 => ConnectionError, # CR_CONNECTION_ERROR
@@ -52,7 +53,7 @@ module Mysql2
52
53
  def initialize(msg, server_version = nil, error_number = nil, sql_state = nil)
53
54
  @server_version = server_version
54
55
  @error_number = error_number
55
- @sql_state = sql_state ? sql_state.encode(ENCODE_OPTS) : nil
56
+ @sql_state = sql_state ? sql_state.encode(**ENCODE_OPTS) : nil
56
57
 
57
58
  super(clean_message(msg))
58
59
  end
@@ -91,9 +92,9 @@ module Mysql2
91
92
  # Returns a valid UTF-8 string.
92
93
  def clean_message(message)
93
94
  if @server_version && @server_version > 50500
94
- message.encode(ENCODE_OPTS)
95
+ message.encode(**ENCODE_OPTS)
95
96
  else
96
- message.encode(Encoding::UTF_8, ENCODE_OPTS)
97
+ message.encode(Encoding::UTF_8, **ENCODE_OPTS)
97
98
  end
98
99
  end
99
100
  end
@@ -1,9 +1,7 @@
1
1
  module Mysql2
2
2
  class Statement
3
- include Enumerable
4
-
5
3
  def execute(*args, **kwargs)
6
- Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_CLASS => :never) do
4
+ Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_NEVER) do
7
5
  _execute(*args, **kwargs)
8
6
  end
9
7
  end
@@ -1,3 +1,3 @@
1
1
  module Mysql2
2
- VERSION = "0.5.0".freeze
2
+ VERSION = "0.5.4".freeze
3
3
  end
data/lib/mysql2.rb CHANGED
@@ -20,9 +20,12 @@ if RUBY_PLATFORM =~ /mswin|mingw/
20
20
  end
21
21
 
22
22
  if dll_path
23
- require 'Win32API'
24
- LoadLibrary = Win32API.new('Kernel32', 'LoadLibrary', ['P'], 'I')
25
- if LoadLibrary.call(dll_path).zero?
23
+ require 'fiddle'
24
+ kernel32 = Fiddle.dlopen 'kernel32'
25
+ load_library = Fiddle::Function.new(
26
+ kernel32['LoadLibraryW'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT,
27
+ )
28
+ if load_library.call(dll_path.encode('utf-16le')).zero?
26
29
  abort "Failed to load libmysql.dll from #{dll_path}"
27
30
  end
28
31
  end
@@ -79,5 +82,6 @@ module Mysql2
79
82
  else
80
83
  ::Timeout::Error
81
84
  end
85
+ TIMEOUT_ERROR_NEVER = { TIMEOUT_ERROR_CLASS => :never }.freeze
82
86
  end
83
87
  end
@@ -0,0 +1,49 @@
1
+ -----BEGIN PGP PUBLIC KEY BLOCK-----
2
+ Version: SKS 1.1.6
3
+ Comment: Hostname: pgp.mit.edu
4
+
5
+ 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
47
+ sTSKHe+QnnnoFmu4gnmDU31i
48
+ =Xqbo
49
+ -----END PGP PUBLIC KEY BLOCK-----