mysql2 0.4.4 → 0.4.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 03a4d85f4672425142825728e8c69d84f4f898af
4
- data.tar.gz: 3c78c54532a405a2e05b85692dd18f219729c46c
3
+ metadata.gz: d82596af22ef47aa19d633dabb82afc9928c3f3f
4
+ data.tar.gz: 09b024bdf203f596f35b4df22ed3149b19af6feb
5
5
  SHA512:
6
- metadata.gz: 94cc9c40b48e34c8831315eff9d80ae043569d5a69708ca157f976b9cba75a7c7672a3f12300a3312a02a24b87a1b81185216792fd1fe9968f92446c6f33eed5
7
- data.tar.gz: 2092fd9e8cb83c053bd2b0b89e09fa1f872a535de01dd2f1927bcd3eaced860764ff3e8793d1d971cc9eeb08a4d504a3d3c4da7cf7ab67fa21f0bcc84bc21a3c
6
+ metadata.gz: 23f6e023618c65e82d5f4289242257449da6138e731f27cc45553aac2ffd5b712350d59e5b5e90c0e8961795394fc437007ba8651e1a2b6863a3d6951b0a6c16
7
+ data.tar.gz: 03bede95a81a4dc20b1dfc288a5f5807eb8d7c243b091e03f6809623d2e30726c5b69dedd9de7b94ca6a56ce13c854927c2241e528c7bb8aa42cc39dc235eba7
data/README.md CHANGED
@@ -164,8 +164,8 @@ by the query like this:
164
164
  ``` ruby
165
165
  headers = results.fields # <= that's an array of field names, in order
166
166
  results.each(:as => :array) do |row|
167
- # Each row is an array, ordered the same as the query results
168
- # An otter's den is called a "holt" or "couch"
167
+ # Each row is an array, ordered the same as the query results
168
+ # An otter's den is called a "holt" or "couch"
169
169
  end
170
170
  ```
171
171
 
@@ -203,6 +203,7 @@ Mysql2::Client.new(
203
203
  :reconnect = true/false,
204
204
  :local_infile = true/false,
205
205
  :secure_auth = true/false,
206
+ :ssl_mode = :disabled / :preferred / :required / :verify_ca / :verify_identity,
206
207
  :default_file = '/path/to/my.cfg',
207
208
  :default_group = 'my.cfg section',
208
209
  :init_command => sql
@@ -398,6 +399,15 @@ client = Mysql2::Client.new
398
399
  result = client.query("SELECT * FROM table_with_boolean_field", :cast_booleans => true)
399
400
  ```
400
401
 
402
+ Keep in mind that this works only with fields and not with computed values, e.g. this result will contain `1`, not `true`:
403
+
404
+ ``` ruby
405
+ client = Mysql2::Client.new
406
+ result = client.query("SELECT true", :cast_booleans => true)
407
+ ```
408
+
409
+ CAST function wouldn't help here as there's no way to cast to TINYINT(1). Apparently the only way to solve this is to use a stored procedure with return type set to TINYINT(1).
410
+
401
411
  ### Skipping casting
402
412
 
403
413
  Mysql2 casting is fast, but not as fast as not casting data. In rare cases where typecasting is not needed, it will be faster to disable it by providing :cast => false. (Note that :cast => false overrides :cast_booleans => true.)
@@ -484,13 +494,13 @@ As for field values themselves, I'm workin on it - but expect that soon.
484
494
 
485
495
  This gem is tested with the following Ruby versions on Linux and Mac OS X:
486
496
 
487
- * Ruby MRI 1.8.7, 1.9.3, 2.0.0, 2.1.x, 2.2.x, 2.3.x
497
+ * Ruby MRI 1.8.7, 1.9.3, 2.0.0, 2.1.x, 2.2.x, 2.3.x, 2.4.x
488
498
  * Ruby Enterprise Edition (based on MRI 1.8.7)
489
- * Rubinius 2.x, 3.x
499
+ * Rubinius 2.x and 3.x do work but may fail under some workloads
490
500
 
491
501
  This gem is tested with the following MySQL and MariaDB versions:
492
502
 
493
- * MySQL 5.5, 5.6, 5.7
503
+ * MySQL 5.5, 5.6, 5.7, 8.0
494
504
  * MySQL Connector/C 6.0 and 6.1 (primarily on Windows)
495
505
  * MariaDB 5.5, 10.0, 10.1
496
506
 
@@ -30,6 +30,12 @@ VALUE rb_hash_dup(VALUE other) {
30
30
  rb_raise(cMysql2Error, "MySQL client is not initialized"); \
31
31
  }
32
32
 
33
+ #define REQUIRE_CONNECTED(wrapper) \
34
+ REQUIRE_INITIALIZED(wrapper) \
35
+ if (!wrapper->connected && !wrapper->reconnect_enabled) { \
36
+ rb_raise(cMysql2Error, "MySQL client is not connected"); \
37
+ }
38
+
33
39
  #define REQUIRE_NOT_CONNECTED(wrapper) \
34
40
  REQUIRE_INITIALIZED(wrapper) \
35
41
  if (wrapper->connected) { \
@@ -47,6 +53,16 @@ VALUE rb_hash_dup(VALUE other) {
47
53
  #define MYSQL_LINK_VERSION MYSQL_SERVER_VERSION
48
54
  #endif
49
55
 
56
+ /*
57
+ * compatibility with mysql-connector-c 6.1.x, and with MySQL 5.7.3 - 5.7.10.
58
+ */
59
+ #ifdef HAVE_CONST_MYSQL_OPT_SSL_ENFORCE
60
+ #define SSL_MODE_DISABLED 1
61
+ #define SSL_MODE_REQUIRED 3
62
+ #define HAVE_CONST_SSL_MODE_DISABLED
63
+ #define HAVE_CONST_SSL_MODE_REQUIRED
64
+ #endif
65
+
50
66
  /*
51
67
  * used to pass all arguments to mysql_real_connect while inside
52
68
  * rb_thread_call_without_gvl
@@ -83,6 +99,43 @@ struct nogvl_select_db_args {
83
99
  char *db;
84
100
  };
85
101
 
102
+ static VALUE rb_set_ssl_mode_option(VALUE self, VALUE setting) {
103
+ unsigned long version = mysql_get_client_version();
104
+
105
+ if (version < 50703) {
106
+ rb_warn( "Your mysql client library does not support setting ssl_mode; full support comes with 5.7.11." );
107
+ return Qnil;
108
+ }
109
+ #ifdef HAVE_CONST_MYSQL_OPT_SSL_ENFORCE
110
+ GET_CLIENT(self);
111
+ int val = NUM2INT( setting );
112
+ if (version >= 50703 && version < 50711) {
113
+ if (val == SSL_MODE_DISABLED || val == SSL_MODE_REQUIRED) {
114
+ my_bool b = ( val == SSL_MODE_REQUIRED );
115
+ int result = mysql_options( wrapper->client, MYSQL_OPT_SSL_ENFORCE, &b );
116
+ return INT2NUM(result);
117
+
118
+ } else {
119
+ rb_warn( "MySQL client libraries between 5.7.3 and 5.7.10 only support SSL_MODE_DISABLED and SSL_MODE_REQUIRED" );
120
+ return Qnil;
121
+ }
122
+ }
123
+ #endif
124
+ #ifdef FULL_SSL_MODE_SUPPORT
125
+ GET_CLIENT(self);
126
+ int val = NUM2INT( setting );
127
+
128
+ if (val != SSL_MODE_DISABLED && val != SSL_MODE_PREFERRED && val != SSL_MODE_REQUIRED && val != SSL_MODE_VERIFY_CA && val != SSL_MODE_VERIFY_IDENTITY) {
129
+ rb_raise(cMysql2Error, "ssl_mode= takes DISABLED, PREFERRED, REQUIRED, VERIFY_CA, VERIFY_IDENTITY, you passed: %d", val );
130
+ }
131
+ int result = mysql_options( wrapper->client, MYSQL_OPT_SSL_MODE, &val );
132
+
133
+ return INT2NUM(result);
134
+ #endif
135
+ #ifdef NO_SSL_MODE_SUPPORT
136
+ return Qnil;
137
+ #endif
138
+ }
86
139
  /*
87
140
  * non-blocking mysql_*() functions that we won't be wrapping since
88
141
  * they do not appear to hit the network nor issue any interruptible
@@ -1192,6 +1245,7 @@ static VALUE set_charset_name(VALUE self, VALUE value) {
1192
1245
  #endif
1193
1246
  GET_CLIENT(self);
1194
1247
 
1248
+ Check_Type(value, T_STRING);
1195
1249
  charset_name = RSTRING_PTR(value);
1196
1250
 
1197
1251
  #ifdef HAVE_RUBY_ENCODING_H
@@ -1337,6 +1391,7 @@ void init_mysql2_client() {
1337
1391
  rb_define_private_method(cMysql2Client, "default_group=", set_read_default_group, 1);
1338
1392
  rb_define_private_method(cMysql2Client, "init_command=", set_init_command, 1);
1339
1393
  rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5);
1394
+ rb_define_private_method(cMysql2Client, "ssl_mode=", rb_set_ssl_mode_option, 1);
1340
1395
  rb_define_private_method(cMysql2Client, "initialize_ext", initialize_ext, 0);
1341
1396
  rb_define_private_method(cMysql2Client, "connect", rb_connect, 7);
1342
1397
  rb_define_private_method(cMysql2Client, "_query", rb_query, 2);
@@ -1464,4 +1519,31 @@ void init_mysql2_client() {
1464
1519
  rb_const_set(cMysql2Client, rb_intern("BASIC_FLAGS"),
1465
1520
  LONG2NUM(CLIENT_BASIC_FLAGS));
1466
1521
  #endif
1522
+
1523
+ #if defined(FULL_SSL_MODE_SUPPORT) // MySQL 5.7.11 and above
1524
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(SSL_MODE_DISABLED));
1525
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_PREFERRED"), INT2NUM(SSL_MODE_PREFERRED));
1526
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_REQUIRED"), INT2NUM(SSL_MODE_REQUIRED));
1527
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_CA"), INT2NUM(SSL_MODE_VERIFY_CA));
1528
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_IDENTITY"), INT2NUM(SSL_MODE_VERIFY_IDENTITY));
1529
+ #elif defined(HAVE_CONST_MYSQL_OPT_SSL_ENFORCE) // MySQL 5.7.3 - 5.7.10
1530
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(SSL_MODE_DISABLED));
1531
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_REQUIRED"), INT2NUM(SSL_MODE_REQUIRED));
1532
+ #endif
1533
+
1534
+ #ifndef HAVE_CONST_SSL_MODE_DISABLED
1535
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(0));
1536
+ #endif
1537
+ #ifndef HAVE_CONST_SSL_MODE_PREFERRED
1538
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_PREFERRED"), INT2NUM(0));
1539
+ #endif
1540
+ #ifndef HAVE_CONST_SSL_MODE_REQUIRED
1541
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_REQUIRED"), INT2NUM(0));
1542
+ #endif
1543
+ #ifndef HAVE_CONST_SSL_MODE_VERIFY_CA
1544
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_CA"), INT2NUM(0));
1545
+ #endif
1546
+ #ifndef HAVE_CONST_SSL_MODE_VERIFY_IDENTITY
1547
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_IDENTITY"), INT2NUM(0));
1548
+ #endif
1467
1549
  }
@@ -51,12 +51,6 @@ typedef struct {
51
51
  MYSQL *client;
52
52
  } mysql_client_wrapper;
53
53
 
54
- #define REQUIRE_CONNECTED(wrapper) \
55
- REQUIRE_INITIALIZED(wrapper) \
56
- if (!wrapper->connected && !wrapper->reconnect_enabled) { \
57
- rb_raise(cMysql2Error, "closed MySQL connection"); \
58
- }
59
-
60
54
  void rb_mysql_client_set_active_thread(VALUE self);
61
55
 
62
56
  #define GET_CLIENT(self) \
@@ -12,6 +12,20 @@ def asplode(lib)
12
12
  end
13
13
  end
14
14
 
15
+ def add_ssl_defines(header)
16
+ all_modes_found = %w(SSL_MODE_DISABLED SSL_MODE_PREFERRED SSL_MODE_REQUIRED SSL_MODE_VERIFY_CA SSL_MODE_VERIFY_IDENTITY).inject(true) do |m, ssl_mode|
17
+ m && have_const(ssl_mode, header)
18
+ end
19
+ $CFLAGS << ' -DFULL_SSL_MODE_SUPPORT' if all_modes_found
20
+ # if we only have ssl toggle (--ssl,--disable-ssl) from 5.7.3 to 5.7.10
21
+ has_no_support = all_modes_found ? false : !have_const('MYSQL_OPT_SSL_ENFORCE', header)
22
+ $CFLAGS << ' -DNO_SSL_MODE_SUPPORT' if has_no_support
23
+ end
24
+
25
+ # 2.1+
26
+ have_func('rb_absint_size')
27
+ have_func('rb_absint_singlebit_p')
28
+
15
29
  # 2.0-only
16
30
  have_header('ruby/thread.h') && have_func('rb_thread_call_without_gvl', 'ruby/thread.h')
17
31
 
@@ -20,6 +34,7 @@ have_func('rb_thread_blocking_region')
20
34
  have_func('rb_wait_for_single_fd')
21
35
  have_func('rb_hash_dup')
22
36
  have_func('rb_intern3')
37
+ have_func('rb_big_cmp')
23
38
 
24
39
  # borrowed from mysqlplus
25
40
  # http://github.com/oldmoe/mysqlplus/blob/master/ext/extconf.rb
@@ -37,6 +52,9 @@ dirs = ENV.fetch('PATH').split(File::PATH_SEPARATOR) + %w(
37
52
  /usr/local/opt/mysql5*
38
53
  ).map { |dir| dir << '/bin' }
39
54
 
55
+ # For those without HOMEBREW_ROOT in PATH
56
+ dirs << "#{ENV['HOMEBREW_ROOT']}/bin" if ENV['HOMEBREW_ROOT']
57
+
40
58
  GLOB = "{#{dirs.join(',')}}/{mysql_config,mysql_config5,mariadb_config}"
41
59
 
42
60
  # If the user has provided a --with-mysql-dir argument, we must respect it or fail.
@@ -87,6 +105,8 @@ else
87
105
  asplode 'mysql.h'
88
106
  end
89
107
 
108
+ add_ssl_defines([prefix, 'mysql.h'].compact.join('/'))
109
+
90
110
  %w(errmsg.h mysqld_error.h).each do |h|
91
111
  header = [prefix, h].compact.join '/'
92
112
  asplode h unless have_header header
@@ -920,6 +920,9 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
920
920
  wrapper->numberOfRows = wrapper->stmt_wrapper ? mysql_stmt_num_rows(wrapper->stmt_wrapper->stmt) : mysql_num_rows(wrapper->result);
921
921
  wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
922
922
  } else if (wrapper->rows && !cacheRows) {
923
+ if (wrapper->resultFreed) {
924
+ rb_raise(cMysql2Error, "Result set has already been freed");
925
+ }
923
926
  mysql_data_seek(wrapper->result, 0);
924
927
  wrapper->lastRowProcessed = 0;
925
928
  wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
@@ -2,8 +2,11 @@
2
2
 
3
3
  VALUE cMysql2Statement;
4
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, intern_to_s;
5
+ static VALUE sym_stream, intern_new_with_args, intern_each, intern_to_s;
6
+ static VALUE intern_sec_fraction, intern_usec, intern_sec, intern_min, intern_hour, intern_day, intern_month, intern_year;
7
+ #ifndef HAVE_RB_BIG_CMP
8
+ static ID id_cmp;
9
+ #endif
7
10
 
8
11
  #define GET_STATEMENT(self) \
9
12
  mysql_stmt_wrapper *stmt_wrapper; \
@@ -180,7 +183,6 @@ static void *nogvl_execute(void *ptr) {
180
183
  static void set_buffer_for_string(MYSQL_BIND* bind_buffer, unsigned long *length_buffer, VALUE string) {
181
184
  unsigned long length;
182
185
 
183
- bind_buffer->buffer_type = MYSQL_TYPE_STRING;
184
186
  bind_buffer->buffer = RSTRING_PTR(string);
185
187
 
186
188
  length = RSTRING_LEN(string);
@@ -204,6 +206,50 @@ static void set_buffer_for_string(MYSQL_BIND* bind_buffer, unsigned long *length
204
206
  xfree(length_buffers); \
205
207
  }
206
208
 
209
+ /* return 0 if the given bignum can cast as LONG_LONG, otherwise 1 */
210
+ static int my_big2ll(VALUE bignum, LONG_LONG *ptr)
211
+ {
212
+ unsigned LONG_LONG num;
213
+ size_t len;
214
+ #ifdef HAVE_RB_ABSINT_SIZE
215
+ int nlz_bits = 0;
216
+ len = rb_absint_size(bignum, &nlz_bits);
217
+ #else
218
+ len = RBIGNUM_LEN(bignum) * SIZEOF_BDIGITS;
219
+ #endif
220
+ if (len > sizeof(LONG_LONG)) goto overflow;
221
+ if (RBIGNUM_POSITIVE_P(bignum)) {
222
+ num = rb_big2ull(bignum);
223
+ if (num > LLONG_MAX)
224
+ goto overflow;
225
+ *ptr = num;
226
+ }
227
+ else {
228
+ if (len == 8 &&
229
+ #ifdef HAVE_RB_ABSINT_SIZE
230
+ nlz_bits == 0 &&
231
+ #endif
232
+ #if defined(HAVE_RB_ABSINT_SIZE) && defined(HAVE_RB_ABSINT_SINGLEBIT_P)
233
+ /* Optimized to avoid object allocation for Ruby 2.1+
234
+ * only -0x8000000000000000 is safe if `len == 8 && nlz_bits == 0`
235
+ */
236
+ !rb_absint_singlebit_p(bignum)
237
+ #elif defined(HAVE_RB_BIG_CMP)
238
+ rb_big_cmp(bignum, LL2NUM(LLONG_MIN)) == INT2FIX(-1)
239
+ #else
240
+ /* Ruby 1.8.7 and REE doesn't have rb_big_cmp */
241
+ rb_funcall(bignum, id_cmp, 1, LL2NUM(LLONG_MIN)) == INT2FIX(-1)
242
+ #endif
243
+ ) {
244
+ goto overflow;
245
+ }
246
+ *ptr = rb_big2ll(bignum);
247
+ }
248
+ return 0;
249
+ overflow:
250
+ return 1;
251
+ }
252
+
207
253
  /* call-seq: stmt.execute
208
254
  *
209
255
  * Executes the current prepared statement, returns +result+.
@@ -265,9 +311,23 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
265
311
  #endif
266
312
  break;
267
313
  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]);
314
+ {
315
+ LONG_LONG num;
316
+ if (my_big2ll(argv[i], &num) == 0) {
317
+ bind_buffers[i].buffer_type = MYSQL_TYPE_LONGLONG;
318
+ bind_buffers[i].buffer = xmalloc(sizeof(long long int));
319
+ *(LONG_LONG*)(bind_buffers[i].buffer) = num;
320
+ } else {
321
+ /* The bignum was larger than we can fit in LONG_LONG, send it as a string */
322
+ VALUE rb_val_as_string = rb_big2str(argv[i], 10);
323
+ bind_buffers[i].buffer_type = MYSQL_TYPE_NEWDECIMAL;
324
+ params_enc[i] = rb_val_as_string;
325
+ #ifdef HAVE_RUBY_ENCODING_H
326
+ params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
327
+ #endif
328
+ set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
329
+ }
330
+ }
271
331
  break;
272
332
  case T_FLOAT:
273
333
  bind_buffers[i].buffer_type = MYSQL_TYPE_DOUBLE;
@@ -275,13 +335,13 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
275
335
  *(double*)(bind_buffers[i].buffer) = NUM2DBL(argv[i]);
276
336
  break;
277
337
  case T_STRING:
278
- {
279
- params_enc[i] = argv[i];
338
+ bind_buffers[i].buffer_type = MYSQL_TYPE_STRING;
339
+
340
+ params_enc[i] = argv[i];
280
341
  #ifdef HAVE_RUBY_ENCODING_H
281
- params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
342
+ params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
282
343
  #endif
283
- set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
284
- }
344
+ set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
285
345
  break;
286
346
  default:
287
347
  // TODO: what Ruby type should support MYSQL_TYPE_TIME
@@ -294,7 +354,13 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
294
354
 
295
355
  memset(&t, 0, sizeof(MYSQL_TIME));
296
356
  t.neg = 0;
297
- t.second_part = FIX2INT(rb_funcall(rb_time, intern_usec, 0));
357
+
358
+ if (CLASS_OF(argv[i]) == rb_cTime) {
359
+ t.second_part = FIX2INT(rb_funcall(rb_time, intern_usec, 0));
360
+ } else if (CLASS_OF(argv[i]) == cDateTime) {
361
+ t.second_part = NUM2DBL(rb_funcall(rb_time, intern_sec_fraction, 0)) * 1000000;
362
+ }
363
+
298
364
  t.second = FIX2INT(rb_funcall(rb_time, intern_sec, 0));
299
365
  t.minute = FIX2INT(rb_funcall(rb_time, intern_min, 0));
300
366
  t.hour = FIX2INT(rb_funcall(rb_time, intern_hour, 0));
@@ -492,6 +558,7 @@ void init_mysql2_statement() {
492
558
  intern_new_with_args = rb_intern("new_with_args");
493
559
  intern_each = rb_intern("each");
494
560
 
561
+ intern_sec_fraction = rb_intern("sec_fraction");
495
562
  intern_usec = rb_intern("usec");
496
563
  intern_sec = rb_intern("sec");
497
564
  intern_min = rb_intern("min");
@@ -501,4 +568,7 @@ void init_mysql2_statement() {
501
568
  intern_year = rb_intern("year");
502
569
 
503
570
  intern_to_s = rb_intern("to_s");
571
+ #ifndef HAVE_RB_BIG_CMP
572
+ id_cmp = rb_intern("<=>");
573
+ #endif
504
574
  }
@@ -19,6 +19,7 @@ module Mysql2
19
19
  end
20
20
 
21
21
  def initialize(opts = {})
22
+ fail Mysql2::Error, "Options parameter must be a Hash" unless opts.is_a? Hash
22
23
  opts = Mysql2::Util.key_hash_as_symbols(opts)
23
24
  @read_timeout = nil
24
25
  @query_options = self.class.default_query_options.dup
@@ -36,7 +37,7 @@ module Mysql2
36
37
  when :reconnect, :local_infile, :secure_auth, :automatic_close
37
38
  send(:"#{key}=", !!opts[key]) # rubocop:disable Style/DoubleNegation
38
39
  when :connect_timeout, :read_timeout, :write_timeout
39
- send(:"#{key}=", opts[key]) unless opts[key].nil?
40
+ send(:"#{key}=", opts[key].to_i) unless opts[key].nil?
40
41
  else
41
42
  send(:"#{key}=", opts[key])
42
43
  end
@@ -47,6 +48,7 @@ module Mysql2
47
48
 
48
49
  ssl_options = opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslcipher)
49
50
  ssl_set(*ssl_options) if ssl_options.any?
51
+ self.ssl_mode = parse_ssl_mode(opts[:ssl_mode]) if opts[:ssl_mode]
50
52
 
51
53
  case opts[:flags]
52
54
  when Array
@@ -87,6 +89,17 @@ module Mysql2
87
89
  connect user, pass, host, port, database, socket, flags
88
90
  end
89
91
 
92
+ def parse_ssl_mode(mode)
93
+ m = mode.to_s.upcase
94
+ if m.start_with?('SSL_MODE_')
95
+ return Mysql2::Client.const_get(m) if Mysql2::Client.const_defined?(m)
96
+ else
97
+ x = 'SSL_MODE_' + m
98
+ return Mysql2::Client.const_get(x) if Mysql2::Client.const_defined?(x)
99
+ end
100
+ warn "Unknown MySQL ssl_mode flag: #{mode}"
101
+ end
102
+
90
103
  def parse_flags_array(flags, initial = 0)
91
104
  flags.reduce(initial) do |memo, f|
92
105
  fneg = f.start_with?('-') ? f[1..-1] : nil
@@ -1,3 +1,3 @@
1
1
  module Mysql2
2
- VERSION = "0.4.4"
2
+ VERSION = "0.4.5"
3
3
  end
@@ -9,9 +9,3 @@ user:
9
9
  username: LOCALUSERNAME
10
10
  password:
11
11
  database: mysql2_test
12
-
13
- numericuser:
14
- host: localhost
15
- username: LOCALUSERNAME
16
- password:
17
- database: 12345
@@ -1,6 +1,5 @@
1
1
  # encoding: UTF-8
2
2
  require 'spec_helper'
3
- require 'stringio'
4
3
 
5
4
  RSpec.describe Mysql2::Client do
6
5
  context "using defaults file" do
@@ -34,6 +33,12 @@ RSpec.describe Mysql2::Client do
34
33
  }.to raise_error(Mysql2::Error)
35
34
  end
36
35
 
36
+ it "should raise an exception on non-string encodings" do
37
+ expect {
38
+ Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => :fake))
39
+ }.to raise_error(TypeError)
40
+ end
41
+
37
42
  it "should not raise an exception on create for a valid encoding" do
38
43
  expect {
39
44
  Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8"))
@@ -200,22 +205,21 @@ RSpec.describe Mysql2::Client do
200
205
 
201
206
  if RUBY_PLATFORM =~ /mingw|mswin/
202
207
  it "cannot be disabled" do
203
- stderr, $stderr = $stderr, StringIO.new
204
-
205
- begin
206
- Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => false))
207
- expect($stderr.string).to include('always closed by garbage collector')
208
- $stderr.reopen
209
-
210
- client = Mysql2::Client.new(DatabaseCredentials['root'])
208
+ expect do
209
+ client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => false))
210
+ expect(client.automatic_close?).to be(true)
211
+ end.to output(/always closed by garbage collector/).to_stderr
212
+
213
+ expect do
214
+ client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => true))
215
+ expect(client.automatic_close?).to be(true)
216
+ end.to_not output(/always closed by garbage collector/).to_stderr
217
+
218
+ expect do
219
+ client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => true))
211
220
  client.automatic_close = false
212
- expect($stderr.string).to include('always closed by garbage collector')
213
- $stderr.reopen
214
-
215
- expect { client.automatic_close = true }.to_not change { $stderr.string }
216
- ensure
217
- $stderr = stderr
218
- end
221
+ expect(client.automatic_close?).to be(true)
222
+ end.to output(/always closed by garbage collector/).to_stderr
219
223
  end
220
224
  else
221
225
  it "can be configured" do
@@ -263,13 +267,14 @@ RSpec.describe Mysql2::Client do
263
267
  end
264
268
 
265
269
  it "should be able to connect to database with numeric-only name" do
266
- creds = DatabaseCredentials['numericuser']
267
- @client.query "CREATE DATABASE IF NOT EXISTS `#{creds['database']}`"
268
- @client.query "GRANT ALL ON `#{creds['database']}`.* TO #{creds['username']}@`#{creds['host']}`"
270
+ database = 1235
271
+ @client.query "CREATE DATABASE IF NOT EXISTS `#{database}`"
269
272
 
270
- expect { Mysql2::Client.new(creds) }.not_to raise_error
273
+ expect {
274
+ Mysql2::Client.new(DatabaseCredentials['root'].merge('database' => database))
275
+ }.not_to raise_error
271
276
 
272
- @client.query "DROP DATABASE IF EXISTS `#{creds['database']}`"
277
+ @client.query "DROP DATABASE IF EXISTS `#{database}`"
273
278
  end
274
279
 
275
280
  it "should respond to #close" do
@@ -559,7 +564,7 @@ RSpec.describe Mysql2::Client do
559
564
  context 'when a non-standard exception class is raised' do
560
565
  it "should close the connection when an exception is raised" do
561
566
  expect { Timeout.timeout(0.1, ArgumentError) { @client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError)
562
- expect { @client.query('SELECT 1') }.to raise_error(Mysql2::Error, 'closed MySQL connection')
567
+ expect { @client.query('SELECT 1') }.to raise_error(Mysql2::Error, 'MySQL client is not connected')
563
568
  end
564
569
 
565
570
  it "should handle Timeouts without leaving the connection hanging if reconnect is true" do
@@ -34,7 +34,7 @@ RSpec.describe Mysql2::Error do
34
34
  end
35
35
  end
36
36
 
37
- let(:invalid_utf8) { "\xE5\xC6\x7D\x1F" }
37
+ let(:invalid_utf8) { ["e5c67d1f"].pack('H*').force_encoding(Encoding::UTF_8) }
38
38
  let(:bad_err) do
39
39
  begin
40
40
  client.query(invalid_utf8)
@@ -6,11 +6,13 @@ RSpec.describe Mysql2::Statement do
6
6
  @client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8"))
7
7
  end
8
8
 
9
+ def stmt_count
10
+ @client.query("SHOW STATUS LIKE 'Prepared_stmt_count'").first['Value'].to_i
11
+ end
12
+
9
13
  it "should create a statement" do
10
14
  statement = nil
11
- expect { statement = @client.prepare 'SELECT 1' }.to change {
12
- @client.query("SHOW STATUS LIKE 'Prepared_stmt_count'").first['Value'].to_i
13
- }.by(1)
15
+ expect { statement = @client.prepare 'SELECT 1' }.to change(&method(:stmt_count)).by(1)
14
16
  expect(statement).to be_an_instance_of(Mysql2::Statement)
15
17
  end
16
18
 
@@ -59,6 +61,26 @@ RSpec.describe Mysql2::Statement do
59
61
  expect(rows).to eq([{ "1" => 1 }])
60
62
  end
61
63
 
64
+ it "should handle bignum but in int64_t" do
65
+ stmt = @client.prepare('SELECT ? AS max, ? AS min')
66
+ int64_max = (1 << 63) - 1
67
+ int64_min = -(1 << 63)
68
+ result = stmt.execute(int64_max, int64_min)
69
+ expect(result.to_a).to eq(['max' => int64_max, 'min' => int64_min])
70
+ end
71
+
72
+ it "should handle bignum but beyond int64_t" do
73
+ stmt = @client.prepare('SELECT ? AS max1, ? AS max2, ? AS max3, ? AS min1, ? AS min2, ? AS min3')
74
+ int64_max1 = (1 << 63)
75
+ int64_max2 = (1 << 64) - 1
76
+ int64_max3 = 1 << 64
77
+ int64_min1 = -(1 << 63) - 1
78
+ int64_min2 = -(1 << 64) + 1
79
+ int64_min3 = -0xC000000000000000
80
+ result = stmt.execute(int64_max1, int64_max2, int64_max3, int64_min1, int64_min2, int64_min3)
81
+ expect(result.to_a).to eq(['max1' => int64_max1, 'max2' => int64_max2, 'max3' => int64_max3, 'min1' => int64_min1, 'min2' => int64_min2, 'min3' => int64_min3])
82
+ end
83
+
62
84
  it "should keep its result after other query" do
63
85
  @client.query 'USE test'
64
86
  @client.query 'CREATE TABLE IF NOT EXISTS mysql2_stmt_q(a int)'
@@ -108,6 +130,35 @@ RSpec.describe Mysql2::Statement do
108
130
  expect(result.first.first[1]).to be_an_instance_of(Time)
109
131
  end
110
132
 
133
+ it "should prepare Date values" do
134
+ now = Date.today
135
+ statement = @client.prepare('SELECT ? AS a')
136
+ result = statement.execute(now)
137
+ expect(result.first['a'].to_s).to eql(now.strftime('%F'))
138
+ end
139
+
140
+ it "should prepare Time values with microseconds" do
141
+ now = Time.now
142
+ statement = @client.prepare('SELECT ? AS a')
143
+ result = statement.execute(now)
144
+ if RUBY_VERSION =~ /1.8/
145
+ expect(result.first['a'].strftime('%F %T %z')).to eql(now.strftime('%F %T %z'))
146
+ else
147
+ expect(result.first['a'].strftime('%F %T.%6N %z')).to eql(now.strftime('%F %T.%6N %z'))
148
+ end
149
+ end
150
+
151
+ it "should prepare DateTime values with microseconds" do
152
+ now = DateTime.now
153
+ statement = @client.prepare('SELECT ? AS a')
154
+ result = statement.execute(now)
155
+ if RUBY_VERSION =~ /1.8/
156
+ expect(result.first['a'].strftime('%F %T %z')).to eql(now.strftime('%F %T %z'))
157
+ else
158
+ expect(result.first['a'].strftime('%F %T.%6N %z')).to eql(now.strftime('%F %T.%6N %z'))
159
+ end
160
+ end
161
+
111
162
  it "should tell us about the fields" do
112
163
  statement = @client.prepare 'SELECT 1 as foo, 2'
113
164
  statement.execute
@@ -117,6 +168,13 @@ RSpec.describe Mysql2::Statement do
117
168
  expect(list[1]).to eq('2')
118
169
  end
119
170
 
171
+ it "should handle as a decimal binding a BigDecimal" do
172
+ stmt = @client.prepare('SELECT ? AS decimal_test')
173
+ test_result = stmt.execute(BigDecimal.new("123.45")).first
174
+ expect(test_result['decimal_test']).to be_an_instance_of(BigDecimal)
175
+ expect(test_result['decimal_test']).to eql(123.45)
176
+ end
177
+
120
178
  it "should update a DECIMAL value passing a BigDecimal" do
121
179
  @client.query 'USE test'
122
180
  @client.query 'DROP TABLE IF EXISTS mysql2_stmt_decimal_test'
@@ -689,9 +747,7 @@ RSpec.describe Mysql2::Statement do
689
747
  context 'close' do
690
748
  it 'should free server resources' do
691
749
  stmt = @client.prepare 'SELECT 1'
692
- expect { stmt.close }.to change {
693
- @client.query("SHOW STATUS LIKE 'Prepared_stmt_count'").first['Value'].to_i
694
- }.by(-1)
750
+ expect { stmt.close }.to change(&method(:stmt_count)).by(-1)
695
751
  end
696
752
 
697
753
  it 'should raise an error on subsequent execution' do
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bash
2
2
 
3
- set -eu
3
+ set -eux
4
4
 
5
5
  echo "
6
6
  [ ca ]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mysql2
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.4
4
+ version: 0.4.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Lopez
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-04-19 00:00:00.000000000 Z
12
+ date: 2016-10-22 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description:
15
15
  email: