mysql2 0.4.6 → 0.5.2

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.
@@ -1,12 +1,9 @@
1
1
  #include <mysql2_ext.h>
2
2
 
3
- VALUE cMysql2Statement;
4
- extern VALUE mMysql2, cMysql2Error, cBigDecimal, cDateTime, cDate;
5
- static VALUE sym_stream, intern_new_with_args, intern_each, intern_to_s;
3
+ extern VALUE mMysql2, cMysql2Error;
4
+ static VALUE cMysql2Statement, cBigDecimal, cDateTime, cDate;
5
+ static VALUE sym_stream, intern_new_with_args, intern_each, intern_to_s, intern_merge_bang;
6
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
10
7
 
11
8
  #define GET_STATEMENT(self) \
12
9
  mysql_stmt_wrapper *stmt_wrapper; \
@@ -21,7 +18,7 @@ static void rb_mysql_stmt_mark(void * ptr) {
21
18
  rb_gc_mark(stmt_wrapper->client);
22
19
  }
23
20
 
24
- static void *nogvl_stmt_close(void * ptr) {
21
+ static void *nogvl_stmt_close(void *ptr) {
25
22
  mysql_stmt_wrapper *stmt_wrapper = ptr;
26
23
  if (stmt_wrapper->stmt) {
27
24
  mysql_stmt_close(stmt_wrapper->stmt);
@@ -30,7 +27,7 @@ static void *nogvl_stmt_close(void * ptr) {
30
27
  return NULL;
31
28
  }
32
29
 
33
- static void rb_mysql_stmt_free(void * ptr) {
30
+ static void rb_mysql_stmt_free(void *ptr) {
34
31
  mysql_stmt_wrapper *stmt_wrapper = ptr;
35
32
  decr_mysql2_stmt(stmt_wrapper);
36
33
  }
@@ -50,7 +47,6 @@ void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) {
50
47
  VALUE rb_error_msg = rb_str_new2(mysql_stmt_error(stmt_wrapper->stmt));
51
48
  VALUE rb_sql_state = rb_tainted_str_new2(mysql_stmt_sqlstate(stmt_wrapper->stmt));
52
49
 
53
- #ifdef HAVE_RUBY_ENCODING_H
54
50
  rb_encoding *conn_enc;
55
51
  conn_enc = rb_to_encoding(wrapper->encoding);
56
52
 
@@ -62,7 +58,6 @@ void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) {
62
58
  rb_error_msg = rb_str_export_to_enc(rb_error_msg, default_internal_enc);
63
59
  rb_sql_state = rb_str_export_to_enc(rb_sql_state, default_internal_enc);
64
60
  }
65
- #endif
66
61
 
67
62
  e = rb_funcall(cMysql2Error, intern_new_with_args, 4,
68
63
  rb_error_msg,
@@ -96,9 +91,7 @@ static void *nogvl_prepare_statement(void *ptr) {
96
91
  VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
97
92
  mysql_stmt_wrapper *stmt_wrapper;
98
93
  VALUE rb_stmt;
99
- #ifdef HAVE_RUBY_ENCODING_H
100
94
  rb_encoding *conn_enc;
101
- #endif
102
95
 
103
96
  Check_Type(sql, T_STRING);
104
97
 
@@ -114,9 +107,7 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
114
107
  {
115
108
  GET_CLIENT(rb_client);
116
109
  stmt_wrapper->stmt = mysql_stmt_init(wrapper->client);
117
- #ifdef HAVE_RUBY_ENCODING_H
118
110
  conn_enc = rb_to_encoding(wrapper->encoding);
119
- #endif
120
111
  }
121
112
  if (stmt_wrapper->stmt == NULL) {
122
113
  rb_raise(cMysql2Error, "Unable to initialize prepared statement: out of memory");
@@ -124,7 +115,7 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
124
115
 
125
116
  // set STMT_ATTR_UPDATE_MAX_LENGTH attr
126
117
  {
127
- bool truth = 1;
118
+ my_bool truth = 1;
128
119
  if (mysql_stmt_attr_set(stmt_wrapper->stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &truth)) {
129
120
  rb_raise(cMysql2Error, "Unable to initialize prepared statement: set STMT_ATTR_UPDATE_MAX_LENGTH");
130
121
  }
@@ -134,11 +125,8 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
134
125
  {
135
126
  struct nogvl_prepare_statement_args args;
136
127
  args.stmt = stmt_wrapper->stmt;
137
- args.sql = sql;
138
- #ifdef HAVE_RUBY_ENCODING_H
139
128
  // 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
129
+ args.sql = rb_str_export_to_enc(sql, conn_enc);
142
130
  args.sql_ptr = RSTRING_PTR(sql);
143
131
  args.sql_len = RSTRING_LEN(sql);
144
132
 
@@ -154,7 +142,7 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
154
142
  *
155
143
  * Returns the number of parameters the prepared statement expects.
156
144
  */
157
- static VALUE param_count(VALUE self) {
145
+ static VALUE rb_mysql_stmt_param_count(VALUE self) {
158
146
  GET_STATEMENT(self);
159
147
 
160
148
  return ULL2NUM(mysql_stmt_param_count(stmt_wrapper->stmt));
@@ -164,13 +152,13 @@ static VALUE param_count(VALUE self) {
164
152
  *
165
153
  * Returns the number of fields the prepared statement returns.
166
154
  */
167
- static VALUE field_count(VALUE self) {
155
+ static VALUE rb_mysql_stmt_field_count(VALUE self) {
168
156
  GET_STATEMENT(self);
169
157
 
170
158
  return UINT2NUM(mysql_stmt_field_count(stmt_wrapper->stmt));
171
159
  }
172
160
 
173
- static void *nogvl_execute(void *ptr) {
161
+ static void *nogvl_stmt_execute(void *ptr) {
174
162
  MYSQL_STMT *stmt = ptr;
175
163
 
176
164
  if (mysql_stmt_execute(stmt)) {
@@ -196,7 +184,7 @@ static void set_buffer_for_string(MYSQL_BIND* bind_buffer, unsigned long *length
196
184
  * the buffer is a Ruby string pointer and not our memory to manage.
197
185
  */
198
186
  #define FREE_BINDS \
199
- for (i = 0; i < argc; i++) { \
187
+ for (i = 0; i < bind_count; i++) { \
200
188
  if (bind_buffers[i].buffer && NIL_P(params_enc[i])) { \
201
189
  xfree(bind_buffers[i].buffer); \
202
190
  } \
@@ -211,6 +199,8 @@ static int my_big2ll(VALUE bignum, LONG_LONG *ptr)
211
199
  {
212
200
  unsigned LONG_LONG num;
213
201
  size_t len;
202
+ // rb_absint_size was added in 2.1.0. See:
203
+ // https://github.com/ruby/ruby/commit/9fea875
214
204
  #ifdef HAVE_RB_ABSINT_SIZE
215
205
  int nlz_bits = 0;
216
206
  len = rb_absint_size(bignum, &nlz_bits);
@@ -229,16 +219,15 @@ static int my_big2ll(VALUE bignum, LONG_LONG *ptr)
229
219
  #ifdef HAVE_RB_ABSINT_SIZE
230
220
  nlz_bits == 0 &&
231
221
  #endif
222
+ // rb_absint_singlebit_p was added in 2.1.0. See:
223
+ // https://github.com/ruby/ruby/commit/e5ff9d5
232
224
  #if defined(HAVE_RB_ABSINT_SIZE) && defined(HAVE_RB_ABSINT_SINGLEBIT_P)
233
225
  /* Optimized to avoid object allocation for Ruby 2.1+
234
226
  * only -0x8000000000000000 is safe if `len == 8 && nlz_bits == 0`
235
227
  */
236
228
  !rb_absint_singlebit_p(bignum)
237
- #elif defined(HAVE_RB_BIG_CMP)
238
- rb_big_cmp(bignum, LL2NUM(LLONG_MIN)) == INT2FIX(-1)
239
229
  #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)
230
+ rb_big_cmp(bignum, LL2NUM(LLONG_MIN)) == INT2FIX(-1)
242
231
  #endif
243
232
  ) {
244
233
  goto overflow;
@@ -254,44 +243,45 @@ overflow:
254
243
  *
255
244
  * Executes the current prepared statement, returns +result+.
256
245
  */
257
- static VALUE execute(int argc, VALUE *argv, VALUE self) {
246
+ static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) {
258
247
  MYSQL_BIND *bind_buffers = NULL;
259
248
  unsigned long *length_buffers = NULL;
260
249
  unsigned long bind_count;
261
- long i;
250
+ unsigned long i;
262
251
  MYSQL_STMT *stmt;
263
252
  MYSQL_RES *metadata;
253
+ VALUE opts;
264
254
  VALUE current;
265
255
  VALUE resultObj;
266
- VALUE *params_enc;
256
+ VALUE *params_enc = NULL;
267
257
  int is_streaming;
268
- #ifdef HAVE_RUBY_ENCODING_H
269
258
  rb_encoding *conn_enc;
270
- #endif
271
259
 
272
260
  GET_STATEMENT(self);
273
261
  GET_CLIENT(stmt_wrapper->client);
274
262
 
275
- #ifdef HAVE_RUBY_ENCODING_H
276
263
  conn_enc = rb_to_encoding(wrapper->encoding);
277
- #endif
278
-
279
- /* Scratch space for string encoding exports, allocate on the stack. */
280
- params_enc = alloca(sizeof(VALUE) * argc);
281
264
 
282
265
  stmt = stmt_wrapper->stmt;
283
-
284
266
  bind_count = mysql_stmt_param_count(stmt);
285
- if (argc != (long)bind_count) {
286
- rb_raise(cMysql2Error, "Bind parameter count (%ld) doesn't match number of arguments (%d)", bind_count, argc);
267
+
268
+ // Get count of ordinary arguments, and extract hash opts/keyword arguments
269
+ // Use a local scope to avoid leaking the temporary count variable
270
+ {
271
+ int c = rb_scan_args(argc, argv, "*:", NULL, &opts);
272
+ if (c != (long)bind_count) {
273
+ rb_raise(cMysql2Error, "Bind parameter count (%ld) doesn't match number of arguments (%d)", bind_count, c);
274
+ }
287
275
  }
288
276
 
289
277
  // setup any bind variables in the query
290
278
  if (bind_count > 0) {
279
+ // Scratch space for string encoding exports, allocate on the stack
280
+ params_enc = alloca(sizeof(VALUE) * bind_count);
291
281
  bind_buffers = xcalloc(bind_count, sizeof(MYSQL_BIND));
292
282
  length_buffers = xcalloc(bind_count, sizeof(unsigned long));
293
283
 
294
- for (i = 0; i < argc; i++) {
284
+ for (i = 0; i < bind_count; i++) {
295
285
  bind_buffers[i].buffer = NULL;
296
286
  params_enc[i] = Qnil;
297
287
 
@@ -319,12 +309,8 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
319
309
  *(LONG_LONG*)(bind_buffers[i].buffer) = num;
320
310
  } else {
321
311
  /* 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
312
  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
313
+ params_enc[i] = rb_str_export_to_enc(rb_big2str(argv[i], 10), conn_enc);
328
314
  set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
329
315
  }
330
316
  }
@@ -338,11 +324,19 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
338
324
  bind_buffers[i].buffer_type = MYSQL_TYPE_STRING;
339
325
 
340
326
  params_enc[i] = argv[i];
341
- #ifdef HAVE_RUBY_ENCODING_H
342
327
  params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
343
- #endif
344
328
  set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
345
329
  break;
330
+ case T_TRUE:
331
+ bind_buffers[i].buffer_type = MYSQL_TYPE_TINY;
332
+ bind_buffers[i].buffer = xmalloc(sizeof(signed char));
333
+ *(signed char*)(bind_buffers[i].buffer) = 1;
334
+ break;
335
+ case T_FALSE:
336
+ bind_buffers[i].buffer_type = MYSQL_TYPE_TINY;
337
+ bind_buffers[i].buffer = xmalloc(sizeof(signed char));
338
+ *(signed char*)(bind_buffers[i].buffer) = 0;
339
+ break;
346
340
  default:
347
341
  // TODO: what Ruby type should support MYSQL_TYPE_TIME
348
342
  if (CLASS_OF(argv[i]) == rb_cTime || CLASS_OF(argv[i]) == cDateTime) {
@@ -395,9 +389,7 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
395
389
  VALUE rb_val_as_string = rb_funcall(argv[i], intern_to_s, 0);
396
390
 
397
391
  params_enc[i] = rb_val_as_string;
398
- #ifdef HAVE_RUBY_ENCODING_H
399
392
  params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
400
- #endif
401
393
  set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
402
394
  }
403
395
  break;
@@ -411,7 +403,40 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
411
403
  }
412
404
  }
413
405
 
414
- if ((VALUE)rb_thread_call_without_gvl(nogvl_execute, stmt, RUBY_UBF_IO, 0) == Qfalse) {
406
+ // Duplicate the options hash, merge! extra opts, put the copy into the Result object
407
+ current = rb_hash_dup(rb_iv_get(stmt_wrapper->client, "@query_options"));
408
+ (void)RB_GC_GUARD(current);
409
+ Check_Type(current, T_HASH);
410
+
411
+ // Merge in hash opts/keyword arguments
412
+ if (!NIL_P(opts)) {
413
+ rb_funcall(current, intern_merge_bang, 1, opts);
414
+ }
415
+
416
+ is_streaming = (Qtrue == rb_hash_aref(current, sym_stream));
417
+
418
+ // From stmt_execute to mysql_stmt_result_metadata to stmt_store_result, no
419
+ // Ruby API calls are allowed so that GC is not invoked. If the connection is
420
+ // in results-streaming-mode for Statement A, and in the middle Statement B
421
+ // gets garbage collected, a message will be sent to the server notifying it
422
+ // to release Statement B, resulting in the following error:
423
+ // Commands out of sync; you can't run this command now
424
+ //
425
+ // In streaming mode, statement execute must return a cursor because we
426
+ // cannot prevent other Statement objects from being garbage collected
427
+ // between fetches of each row of the result set. The following error
428
+ // occurs if cursor mode is not set:
429
+ // Row retrieval was canceled by mysql_stmt_close
430
+
431
+ if (is_streaming) {
432
+ unsigned long type = CURSOR_TYPE_READ_ONLY;
433
+ if (mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, &type)) {
434
+ FREE_BINDS;
435
+ rb_raise(cMysql2Error, "Unable to stream prepared statement, could not set CURSOR_TYPE_READ_ONLY");
436
+ }
437
+ }
438
+
439
+ if ((VALUE)rb_thread_call_without_gvl(nogvl_stmt_execute, stmt, RUBY_UBF_IO, 0) == Qfalse) {
415
440
  FREE_BINDS;
416
441
  rb_raise_mysql2_stmt_error(stmt_wrapper);
417
442
  }
@@ -429,11 +454,6 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
429
454
  return Qnil;
430
455
  }
431
456
 
432
- current = rb_hash_dup(rb_iv_get(stmt_wrapper->client, "@query_options"));
433
- (void)RB_GC_GUARD(current);
434
- Check_Type(current, T_HASH);
435
-
436
- is_streaming = (Qtrue == rb_hash_aref(current, sym_stream));
437
457
  if (!is_streaming) {
438
458
  // recieve the whole result set from the server
439
459
  if (mysql_stmt_store_result(stmt)) {
@@ -445,6 +465,8 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
445
465
 
446
466
  resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, metadata, self);
447
467
 
468
+ rb_mysql_set_server_query_flags(wrapper->client, resultObj);
469
+
448
470
  if (!is_streaming) {
449
471
  // cache all result
450
472
  rb_funcall(resultObj, intern_each, 0);
@@ -457,42 +479,47 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
457
479
  *
458
480
  * Returns a list of fields that will be returned by this statement.
459
481
  */
460
- static VALUE fields(VALUE self) {
482
+ static VALUE rb_mysql_stmt_fields(VALUE self) {
461
483
  MYSQL_FIELD *fields;
462
484
  MYSQL_RES *metadata;
463
485
  unsigned int field_count;
464
486
  unsigned int i;
465
487
  VALUE field_list;
466
488
  MYSQL_STMT* stmt;
467
- #ifdef HAVE_RUBY_ENCODING_H
468
489
  rb_encoding *default_internal_enc, *conn_enc;
469
- #endif
470
490
  GET_STATEMENT(self);
491
+ GET_CLIENT(stmt_wrapper->client);
471
492
  stmt = stmt_wrapper->stmt;
472
493
 
473
- #ifdef HAVE_RUBY_ENCODING_H
474
494
  default_internal_enc = rb_default_internal_encoding();
475
495
  {
476
496
  GET_CLIENT(stmt_wrapper->client);
477
497
  conn_enc = rb_to_encoding(wrapper->encoding);
478
498
  }
479
- #endif
480
499
 
481
- metadata = mysql_stmt_result_metadata(stmt);
500
+ metadata = mysql_stmt_result_metadata(stmt);
501
+ if (metadata == NULL) {
502
+ if (mysql_stmt_errno(stmt) != 0) {
503
+ // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal.
504
+ wrapper->active_thread = Qnil;
505
+ rb_raise_mysql2_stmt_error(stmt_wrapper);
506
+ }
507
+ // no data and no error, so query was not a SELECT
508
+ return Qnil;
509
+ }
510
+
482
511
  fields = mysql_fetch_fields(metadata);
483
512
  field_count = mysql_stmt_field_count(stmt);
484
513
  field_list = rb_ary_new2((long)field_count);
485
514
 
486
- for(i = 0; i < field_count; i++) {
515
+ for (i = 0; i < field_count; i++) {
487
516
  VALUE rb_field;
488
517
 
489
518
  rb_field = rb_str_new(fields[i].name, fields[i].name_length);
490
- #ifdef HAVE_RUBY_ENCODING_H
491
519
  rb_enc_associate(rb_field, conn_enc);
492
520
  if (default_internal_enc) {
493
521
  rb_field = rb_str_export_to_enc(rb_field, default_internal_enc);
494
522
  }
495
- #endif
496
523
 
497
524
  rb_ary_store(field_list, (long)i, rb_field);
498
525
  }
@@ -543,12 +570,15 @@ static VALUE rb_mysql_stmt_close(VALUE self) {
543
570
  }
544
571
 
545
572
  void init_mysql2_statement() {
546
- cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject);
573
+ cDate = rb_const_get(rb_cObject, rb_intern("Date"));
574
+ cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
575
+ cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal"));
547
576
 
548
- rb_define_method(cMysql2Statement, "param_count", param_count, 0);
549
- rb_define_method(cMysql2Statement, "field_count", field_count, 0);
550
- rb_define_method(cMysql2Statement, "_execute", execute, -1);
551
- rb_define_method(cMysql2Statement, "fields", fields, 0);
577
+ cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject);
578
+ rb_define_method(cMysql2Statement, "param_count", rb_mysql_stmt_param_count, 0);
579
+ rb_define_method(cMysql2Statement, "field_count", rb_mysql_stmt_field_count, 0);
580
+ rb_define_method(cMysql2Statement, "_execute", rb_mysql_stmt_execute, -1);
581
+ rb_define_method(cMysql2Statement, "fields", rb_mysql_stmt_fields, 0);
552
582
  rb_define_method(cMysql2Statement, "last_id", rb_mysql_stmt_last_id, 0);
553
583
  rb_define_method(cMysql2Statement, "affected_rows", rb_mysql_stmt_affected_rows, 0);
554
584
  rb_define_method(cMysql2Statement, "close", rb_mysql_stmt_close, 0);
@@ -568,7 +598,5 @@ void init_mysql2_statement() {
568
598
  intern_year = rb_intern("year");
569
599
 
570
600
  intern_to_s = rb_intern("to_s");
571
- #ifndef HAVE_RB_BIG_CMP
572
- id_cmp = rb_intern("<=>");
573
- #endif
601
+ intern_merge_bang = rb_intern("merge!");
574
602
  }
@@ -1,8 +1,6 @@
1
1
  #ifndef MYSQL2_STATEMENT_H
2
2
  #define MYSQL2_STATEMENT_H
3
3
 
4
- extern VALUE cMysql2Statement;
5
-
6
4
  typedef struct {
7
5
  VALUE client;
8
6
  MYSQL_STMT *stmt;
@@ -1,5 +1,6 @@
1
1
  /*
2
- * backwards compatibility for pre-1.9.3 C API
2
+ * backwards compatibility for Rubinius. See
3
+ * https://github.com/rubinius/rubinius/issues/3771.
3
4
  *
4
5
  * Ruby 1.9.3 provides this API which allows the use of ppoll() on Linux
5
6
  * to minimize select() and malloc() overhead on high-numbered FDs.
data/lib/mysql2/client.rb CHANGED
@@ -4,22 +4,22 @@ module Mysql2
4
4
 
5
5
  def self.default_query_options
6
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,
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 | CONNECT_ATTRS,
15
+ cast: true,
16
+ default_file: nil,
17
+ default_group: nil,
18
18
  }
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
+ raise Mysql2::Error, "Options parameter must be a Hash" unless opts.is_a? Hash
23
23
  opts = Mysql2::Util.key_hash_as_symbols(opts)
24
24
  @read_timeout = nil
25
25
  @query_options = self.class.default_query_options.dup
@@ -31,10 +31,10 @@ 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
- [:reconnect, :connect_timeout, :local_infile, :read_timeout, :write_timeout, :default_file, :default_group, :secure_auth, :init_command, :automatic_close].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].each do |key|
35
35
  next unless opts.key?(key)
36
36
  case key
37
- when :reconnect, :local_infile, :secure_auth, :automatic_close
37
+ when :reconnect, :local_infile, :secure_auth, :automatic_close, :enable_cleartext_plugin
38
38
  send(:"#{key}=", !!opts[key]) # rubocop:disable Style/DoubleNegation
39
39
  when :connect_timeout, :read_timeout, :write_timeout
40
40
  send(:"#{key}=", Integer(opts[key])) unless opts[key].nil?
@@ -47,26 +47,26 @@ module Mysql2
47
47
  self.charset_name = opts[:encoding] || 'utf8'
48
48
 
49
49
  ssl_options = opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslcipher)
50
- ssl_set(*ssl_options) if ssl_options.any?
50
+ ssl_set(*ssl_options) if ssl_options.any? || opts.key?(:sslverify)
51
51
  self.ssl_mode = parse_ssl_mode(opts[:ssl_mode]) if opts[:ssl_mode]
52
52
 
53
- case opts[:flags]
53
+ flags = case opts[:flags]
54
54
  when Array
55
- flags = parse_flags_array(opts[:flags], @query_options[:connect_flags])
55
+ parse_flags_array(opts[:flags], @query_options[:connect_flags])
56
56
  when String
57
- flags = parse_flags_array(opts[:flags].split(' '), @query_options[:connect_flags])
57
+ parse_flags_array(opts[:flags].split(' '), @query_options[:connect_flags])
58
58
  when Integer
59
- flags = @query_options[:connect_flags] | opts[:flags]
59
+ @query_options[:connect_flags] | opts[:flags]
60
60
  else
61
- flags = @query_options[:connect_flags]
61
+ @query_options[:connect_flags]
62
62
  end
63
63
 
64
64
  # SSL verify is a connection flag rather than a mysql_ssl_set option
65
- flags |= SSL_VERIFY_SERVER_CERT if opts[:sslverify] && ssl_options.any?
65
+ flags |= SSL_VERIFY_SERVER_CERT if opts[:sslverify]
66
66
 
67
- if [:user, :pass, :hostname, :dbname, :db, :sock].any? { |k| @query_options.key?(k) }
67
+ if %i[user pass hostname dbname db sock].any? { |k| @query_options.key?(k) }
68
68
  warn "============= WARNING FROM mysql2 ============="
69
- warn "The options :user, :pass, :hostname, :dbname, :db, and :sock will be deprecated at some point in the future."
69
+ warn "The options :user, :pass, :hostname, :dbname, :db, and :sock are deprecated and will be removed at some point in the future."
70
70
  warn "Instead, please use :username, :password, :host, :port, :database, :socket, :flags for the options."
71
71
  warn "============= END WARNING FROM mysql2 ========="
72
72
  end
@@ -85,8 +85,9 @@ module Mysql2
85
85
  port = port.to_i unless port.nil?
86
86
  database = database.to_s unless database.nil?
87
87
  socket = socket.to_s unless socket.nil?
88
+ conn_attrs = parse_connect_attrs(opts[:connect_attrs])
88
89
 
89
- connect user, pass, host, port, database, socket, flags
90
+ connect user, pass, host, port, database, socket, flags, conn_attrs
90
91
  end
91
92
 
92
93
  def parse_ssl_mode(mode)
@@ -114,14 +115,19 @@ module Mysql2
114
115
  end
115
116
  end
116
117
 
117
- if Thread.respond_to?(:handle_interrupt)
118
- def query(sql, options = {})
119
- Thread.handle_interrupt(::Mysql2::Util::TimeoutError => :never) do
120
- _query(sql, @query_options.merge(options))
121
- end
118
+ # Set default program_name in performance_schema.session_connect_attrs
119
+ # and performance_schema.session_account_connect_attrs
120
+ def parse_connect_attrs(conn_attrs)
121
+ return {} if Mysql2::Client::CONNECT_ATTRS.zero?
122
+ conn_attrs ||= {}
123
+ conn_attrs[:program_name] ||= $PROGRAM_NAME
124
+ conn_attrs.each_with_object({}) do |(key, value), hash|
125
+ hash[key.to_s] = value.to_s
122
126
  end
123
- else
124
- def query(sql, options = {})
127
+ end
128
+
129
+ def query(sql, options = {})
130
+ Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_CLASS => :never) do
125
131
  _query(sql, @query_options.merge(options))
126
132
  end
127
133
  end
data/lib/mysql2/em.rb CHANGED
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  require 'eventmachine'
4
2
  require 'mysql2'
5
3
 
@@ -17,7 +15,7 @@ module Mysql2
17
15
  detach
18
16
  begin
19
17
  result = @client.async_result
20
- rescue => e
18
+ rescue StandardError => e
21
19
  @deferable.fail(e)
22
20
  else
23
21
  @deferable.succeed(result)
@@ -41,7 +39,7 @@ module Mysql2
41
39
 
42
40
  def query(sql, opts = {})
43
41
  if ::EM.reactor_running?
44
- super(sql, opts.merge(:async => true))
42
+ super(sql, opts.merge(async: true))
45
43
  deferable = ::EM::DefaultDeferrable.new
46
44
  @watch = ::EM.watch(socket, Watcher, self, deferable)
47
45
  @watch.notify_readable = true
data/lib/mysql2/error.rb CHANGED
@@ -1,32 +1,65 @@
1
- # encoding: UTF-8
2
-
3
1
  module Mysql2
4
2
  class Error < StandardError
5
3
  ENCODE_OPTS = {
6
- :undef => :replace,
7
- :invalid => :replace,
8
- :replace => '?'.freeze,
4
+ undef: :replace,
5
+ invalid: :replace,
6
+ replace: '?'.freeze,
7
+ }.freeze
8
+
9
+ ConnectionError = Class.new(Error)
10
+ TimeoutError = Class.new(Error)
11
+
12
+ CODES = {
13
+ 1205 => TimeoutError, # ER_LOCK_WAIT_TIMEOUT
14
+
15
+ 1044 => ConnectionError, # ER_DBACCESS_DENIED_ERROR
16
+ 1045 => ConnectionError, # ER_ACCESS_DENIED_ERROR
17
+ 1152 => ConnectionError, # ER_ABORTING_CONNECTION
18
+ 1153 => ConnectionError, # ER_NET_PACKET_TOO_LARGE
19
+ 1154 => ConnectionError, # ER_NET_READ_ERROR_FROM_PIPE
20
+ 1155 => ConnectionError, # ER_NET_FCNTL_ERROR
21
+ 1156 => ConnectionError, # ER_NET_PACKETS_OUT_OF_ORDER
22
+ 1157 => ConnectionError, # ER_NET_UNCOMPRESS_ERROR
23
+ 1158 => ConnectionError, # ER_NET_READ_ERROR
24
+ 1159 => ConnectionError, # ER_NET_READ_INTERRUPTED
25
+ 1160 => ConnectionError, # ER_NET_ERROR_ON_WRITE
26
+ 1161 => ConnectionError, # ER_NET_WRITE_INTERRUPTED
27
+
28
+ 2001 => ConnectionError, # CR_SOCKET_CREATE_ERROR
29
+ 2002 => ConnectionError, # CR_CONNECTION_ERROR
30
+ 2003 => ConnectionError, # CR_CONN_HOST_ERROR
31
+ 2004 => ConnectionError, # CR_IPSOCK_ERROR
32
+ 2005 => ConnectionError, # CR_UNKNOWN_HOST
33
+ 2006 => ConnectionError, # CR_SERVER_GONE_ERROR
34
+ 2007 => ConnectionError, # CR_VERSION_ERROR
35
+ 2009 => ConnectionError, # CR_WRONG_HOST_INFO
36
+ 2012 => ConnectionError, # CR_SERVER_HANDSHAKE_ERR
37
+ 2013 => ConnectionError, # CR_SERVER_LOST
38
+ 2020 => ConnectionError, # CR_NET_PACKET_TOO_LARGE
39
+ 2026 => ConnectionError, # CR_SSL_CONNECTION_ERROR
40
+ 2027 => ConnectionError, # CR_MALFORMED_PACKET
41
+ 2047 => ConnectionError, # CR_CONN_UNKNOW_PROTOCOL
42
+ 2048 => ConnectionError, # CR_INVALID_CONN_HANDLE
43
+ 2049 => ConnectionError, # CR_UNUSED_1
9
44
  }.freeze
10
45
 
11
46
  attr_reader :error_number, :sql_state
12
47
 
13
48
  # Mysql gem compatibility
14
- alias_method :errno, :error_number
15
- alias_method :error, :message
49
+ alias errno error_number
50
+ alias error message
16
51
 
17
- def initialize(msg)
18
- @server_version ||= nil
52
+ def initialize(msg, server_version = nil, error_number = nil, sql_state = nil)
53
+ @server_version = server_version
54
+ @error_number = error_number
55
+ @sql_state = sql_state ? sql_state.encode(ENCODE_OPTS) : nil
19
56
 
20
57
  super(clean_message(msg))
21
58
  end
22
59
 
23
60
  def self.new_with_args(msg, server_version, error_number, sql_state)
24
- err = allocate
25
- err.instance_variable_set('@server_version', server_version)
26
- err.instance_variable_set('@error_number', error_number)
27
- err.instance_variable_set('@sql_state', sql_state.respond_to?(:encode) ? sql_state.encode(ENCODE_OPTS) : sql_state)
28
- err.send(:initialize, msg)
29
- err
61
+ error_class = CODES.fetch(error_number, self)
62
+ error_class.new(msg, server_version, error_number, sql_state)
30
63
  end
31
64
 
32
65
  private
@@ -55,12 +88,8 @@ module Mysql2
55
88
  # encoding, we'll assume UTF-8 and clean the string of anything that's not a
56
89
  # valid UTF-8 character.
57
90
  #
58
- # Except for if we're on 1.8, where we'll do nothing ;)
59
- #
60
- # Returns a valid UTF-8 string in Ruby 1.9+, the original string on Ruby 1.8
91
+ # Returns a valid UTF-8 string.
61
92
  def clean_message(message)
62
- return message unless message.respond_to?(:encode)
63
-
64
93
  if @server_version && @server_version > 50500
65
94
  message.encode(ENCODE_OPTS)
66
95
  else
data/lib/mysql2/result.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  module Mysql2
2
2
  class Result
3
+ attr_reader :server_flags
4
+
3
5
  include Enumerable
4
6
  end
5
7
  end