mysql2 0.4.1 → 0.4.6

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.
data/ext/mysql2/client.h CHANGED
@@ -43,6 +43,7 @@ typedef struct {
43
43
  int reconnect_enabled;
44
44
  unsigned int connect_timeout;
45
45
  int active;
46
+ int automatic_close;
46
47
  int connected;
47
48
  int initialized;
48
49
  int refcount;
@@ -50,18 +51,8 @@ typedef struct {
50
51
  MYSQL *client;
51
52
  } mysql_client_wrapper;
52
53
 
53
- #define REQUIRE_CONNECTED(wrapper) \
54
- REQUIRE_INITIALIZED(wrapper) \
55
- if (!wrapper->connected && !wrapper->reconnect_enabled) { \
56
- rb_raise(cMysql2Error, "closed MySQL connection"); \
57
- }
58
-
59
54
  void rb_mysql_client_set_active_thread(VALUE self);
60
55
 
61
- #define MARK_CONN_INACTIVE(conn) do {\
62
- wrapper->active_thread = Qnil; \
63
- } while(0)
64
-
65
56
  #define GET_CLIENT(self) \
66
57
  mysql_client_wrapper *wrapper; \
67
58
  Data_Get_Struct(self, mysql_client_wrapper, wrapper);
@@ -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
@@ -105,31 +125,55 @@ wishlist = [
105
125
  '-Wno-missing-field-initializers', # gperf generates bad code
106
126
  '-Wno-missing-variable-declarations', # missing symbols due to ruby native ext initialization
107
127
  '-Wno-padded', # mysql :(
128
+ '-Wno-reserved-id-macro', # rubby :(
108
129
  '-Wno-sign-conversion', # gperf generates bad code
109
130
  '-Wno-static-in-inline', # gperf generates bad code
110
131
  '-Wno-switch-enum', # result.c -- enum_field_types (when not fully covered, e.g. mysql 5.6+)
111
132
  '-Wno-undef', # rubinius :(
133
+ '-Wno-unreachable-code', # rubby :(
112
134
  '-Wno-used-but-marked-unused', # rubby :(
113
135
  ]
114
136
 
115
- if ENV['CI']
116
- wishlist += [
117
- '-Werror',
118
- '-fsanitize=address',
119
- '-fsanitize=cfi',
120
- '-fsanitize=integer',
121
- '-fsanitize=memory',
122
- '-fsanitize=thread',
123
- '-fsanitize=undefined',
124
- ]
125
- end
126
-
127
137
  usable_flags = wishlist.select do |flag|
128
- try_link('int main() {return 0;}', flag)
138
+ try_link('int main() {return 0;}', "-Werror #{flag}")
129
139
  end
130
140
 
131
141
  $CFLAGS << ' ' << usable_flags.join(' ')
132
142
 
143
+ enabled_sanitizers = disabled_sanitizers = []
144
+ # Specify a commna-separated list of sanitizers, or try them all by default
145
+ sanitizers = with_config('sanitize')
146
+ case sanitizers
147
+ when true
148
+ # Try them all, turn on whatever we can
149
+ enabled_sanitizers = %w(address cfi integer memory thread undefined).select do |s|
150
+ try_link('int main() {return 0;}', "-Werror -fsanitize=#{s}")
151
+ end
152
+ abort "-----\nCould not enable any sanitizers!\n-----" if enabled_sanitizers.empty?
153
+ when String
154
+ # Figure out which sanitizers are supported
155
+ enabled_sanitizers, disabled_sanitizers = sanitizers.split(',').partition do |s|
156
+ try_link('int main() {return 0;}', "-Werror -fsanitize=#{s}")
157
+ end
158
+ end
159
+
160
+ unless disabled_sanitizers.empty?
161
+ abort "-----\nCould not enable requested sanitizers: #{disabled_sanitizers.join(',')}\n-----"
162
+ end
163
+
164
+ unless enabled_sanitizers.empty?
165
+ warn "-----\nEnabling sanitizers: #{enabled_sanitizers.join(',')}\n-----"
166
+ enabled_sanitizers.each do |s|
167
+ # address sanitizer requires runtime support
168
+ if s == 'address' # rubocop:disable Style/IfUnlessModifier
169
+ have_library('asan') || $LDFLAGS << ' -fsanitize=address'
170
+ end
171
+ $CFLAGS << " -fsanitize=#{s}"
172
+ end
173
+ # Options for line numbers in backtraces
174
+ $CFLAGS << ' -g -fno-omit-frame-pointer'
175
+ end
176
+
133
177
  if RUBY_PLATFORM =~ /mswin|mingw/
134
178
  # Build libmysql.a interface link library
135
179
  require 'rake'
@@ -155,8 +199,8 @@ if RUBY_PLATFORM =~ /mswin|mingw/
155
199
 
156
200
  # Make sure the generated interface library works (if cross-compiling, trust without verifying)
157
201
  unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
158
- abort "-----\nCannot find libmysql.a\n----" unless have_library('libmysql')
159
- abort "-----\nCannot link to libmysql.a (my_init)\n----" unless have_func('my_init')
202
+ abort "-----\nCannot find libmysql.a\n-----" unless have_library('libmysql')
203
+ abort "-----\nCannot link to libmysql.a (my_init)\n-----" unless have_func('my_init')
160
204
  end
161
205
 
162
206
  # Vendor libmysql.dll
data/ext/mysql2/result.c CHANGED
@@ -48,7 +48,7 @@ static rb_encoding *binaryEncoding;
48
48
  #define MYSQL2_MIN_TIME 62171150401ULL
49
49
  #endif
50
50
 
51
- #define GET_RESULT(obj) \
51
+ #define GET_RESULT(self) \
52
52
  mysql2_result_wrapper *wrapper; \
53
53
  Data_Get_Struct(self, mysql2_result_wrapper, wrapper);
54
54
 
@@ -91,16 +91,22 @@ static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) {
91
91
 
92
92
  if (wrapper->resultFreed != 1) {
93
93
  if (wrapper->stmt_wrapper) {
94
- mysql_stmt_free_result(wrapper->stmt_wrapper->stmt);
94
+ if (!wrapper->stmt_wrapper->closed) {
95
+ mysql_stmt_free_result(wrapper->stmt_wrapper->stmt);
96
+
97
+ /* MySQL BUG? If the statement handle was previously used, and so
98
+ * mysql_stmt_bind_result was called, and if that result set and bind buffers were freed,
99
+ * MySQL still thinks the result set buffer is available and will prefetch the
100
+ * first result in mysql_stmt_execute. This will corrupt or crash the program.
101
+ * By setting bind_result_done back to 0, we make MySQL think that a result set
102
+ * has never been bound to this statement handle before to prevent the prefetch.
103
+ */
104
+ wrapper->stmt_wrapper->stmt->bind_result_done = 0;
105
+ }
95
106
 
96
- /* MySQL BUG? If the statement handle was previously used, and so
97
- * mysql_stmt_bind_result was called, and if that result set and bind buffers were freed,
98
- * MySQL still thinks the result set buffer is available and will prefetch the
99
- * first result in mysql_stmt_execute. This will corrupt or crash the program.
100
- * By setting bind_result_done back to 0, we make MySQL think that a result set
101
- * has never been bound to this statement handle before to prevent the prefetch.
102
- */
103
- wrapper->stmt_wrapper->stmt->bind_result_done = 0;
107
+ if (wrapper->statement != Qnil) {
108
+ decr_mysql2_stmt(wrapper->stmt_wrapper);
109
+ }
104
110
 
105
111
  if (wrapper->result_buffers) {
106
112
  unsigned int i;
@@ -134,13 +140,15 @@ static void rb_mysql_result_free(void *ptr) {
134
140
  decr_mysql2_client(wrapper->client_wrapper);
135
141
  }
136
142
 
137
- if (wrapper->statement != Qnil) {
138
- decr_mysql2_stmt(wrapper->stmt_wrapper);
139
- }
140
-
141
143
  xfree(wrapper);
142
144
  }
143
145
 
146
+ static VALUE rb_mysql_result_free_(VALUE self) {
147
+ GET_RESULT(self);
148
+ rb_mysql_result_free_result(wrapper);
149
+ return Qnil;
150
+ }
151
+
144
152
  /*
145
153
  * for small results, this won't hit the network, but there's no
146
154
  * reliable way for us to tell this so we'll always release the GVL
@@ -203,7 +211,7 @@ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, int symbo
203
211
 
204
212
  #ifdef HAVE_RUBY_ENCODING_H
205
213
  static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_encoding *default_internal_enc, rb_encoding *conn_enc) {
206
- /* if binary flag is set, respect it's wishes */
214
+ /* if binary flag is set, respect its wishes */
207
215
  if (field.flags & BINARY_FLAG && field.charsetnr == 63) {
208
216
  rb_enc_associate(val, binaryEncoding);
209
217
  } else if (!field.charsetnr) {
@@ -254,8 +262,8 @@ static void rb_mysql_result_alloc_result_buffers(VALUE self, MYSQL_FIELD *fields
254
262
  if (wrapper->result_buffers != NULL) return;
255
263
 
256
264
  wrapper->result_buffers = xcalloc(wrapper->numberOfFields, sizeof(MYSQL_BIND));
257
- wrapper->is_null = xcalloc(wrapper->numberOfFields, sizeof(my_bool));
258
- wrapper->error = xcalloc(wrapper->numberOfFields, sizeof(my_bool));
265
+ wrapper->is_null = xcalloc(wrapper->numberOfFields, sizeof(bool));
266
+ wrapper->error = xcalloc(wrapper->numberOfFields, sizeof(bool));
259
267
  wrapper->length = xcalloc(wrapper->numberOfFields, sizeof(unsigned long));
260
268
 
261
269
  for (i = 0; i < wrapper->numberOfFields; i++) {
@@ -309,11 +317,10 @@ static void rb_mysql_result_alloc_result_buffers(VALUE self, MYSQL_FIELD *fields
309
317
  case MYSQL_TYPE_SET: // char[]
310
318
  case MYSQL_TYPE_ENUM: // char[]
311
319
  case MYSQL_TYPE_GEOMETRY: // char[]
320
+ default:
312
321
  wrapper->result_buffers[i].buffer = xmalloc(fields[i].max_length);
313
322
  wrapper->result_buffers[i].buffer_length = fields[i].max_length;
314
323
  break;
315
- default:
316
- rb_raise(cMysql2Error, "unhandled mysql type: %d", fields[i].type);
317
324
  }
318
325
 
319
326
  wrapper->result_buffers[i].is_null = &wrapper->is_null[i];
@@ -339,15 +346,15 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co
339
346
  conn_enc = rb_to_encoding(wrapper->encoding);
340
347
  #endif
341
348
 
349
+ if (wrapper->fields == Qnil) {
350
+ wrapper->numberOfFields = mysql_num_fields(wrapper->result);
351
+ wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
352
+ }
342
353
  if (args->asArray) {
343
354
  rowVal = rb_ary_new2(wrapper->numberOfFields);
344
355
  } else {
345
356
  rowVal = rb_hash_new();
346
357
  }
347
- if (wrapper->fields == Qnil) {
348
- wrapper->numberOfFields = mysql_num_fields(wrapper->result);
349
- wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
350
- }
351
358
 
352
359
  if (wrapper->result_buffers == NULL) {
353
360
  rb_mysql_result_alloc_result_buffers(self, fields);
@@ -491,14 +498,12 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co
491
498
  case MYSQL_TYPE_SET: // char[]
492
499
  case MYSQL_TYPE_ENUM: // char[]
493
500
  case MYSQL_TYPE_GEOMETRY: // char[]
501
+ default:
494
502
  val = rb_str_new(result_buffer->buffer, *(result_buffer->length));
495
503
  #ifdef HAVE_RUBY_ENCODING_H
496
504
  val = mysql2_set_field_string_encoding(val, fields[i], default_internal_enc, conn_enc);
497
505
  #endif
498
506
  break;
499
- default:
500
- rb_raise(cMysql2Error, "unhandled buffer type: %d",
501
- result_buffer->buffer_type);
502
507
  }
503
508
  }
504
509
 
@@ -512,7 +517,6 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co
512
517
  return rowVal;
513
518
  }
514
519
 
515
-
516
520
  static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const result_each_args *args)
517
521
  {
518
522
  VALUE rowVal;
@@ -537,16 +541,16 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const r
537
541
  return Qnil;
538
542
  }
539
543
 
544
+ if (wrapper->fields == Qnil) {
545
+ wrapper->numberOfFields = mysql_num_fields(wrapper->result);
546
+ wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
547
+ }
540
548
  if (args->asArray) {
541
549
  rowVal = rb_ary_new2(wrapper->numberOfFields);
542
550
  } else {
543
551
  rowVal = rb_hash_new();
544
552
  }
545
553
  fieldLengths = mysql_fetch_lengths(wrapper->result);
546
- if (wrapper->fields == Qnil) {
547
- wrapper->numberOfFields = mysql_num_fields(wrapper->result);
548
- wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
549
- }
550
554
 
551
555
  for (i = 0; i < wrapper->numberOfFields; i++) {
552
556
  VALUE field = rb_mysql_result_fetch_field(self, i, args->symbolizeKeys);
@@ -830,7 +834,9 @@ static VALUE rb_mysql_result_each_(VALUE self,
830
834
 
831
835
  if (row == Qnil) {
832
836
  /* we don't need the mysql C dataset around anymore, peace it */
833
- rb_mysql_result_free_result(wrapper);
837
+ if (args->cacheRows) {
838
+ rb_mysql_result_free_result(wrapper);
839
+ }
834
840
  return Qnil;
835
841
  }
836
842
 
@@ -838,7 +844,7 @@ static VALUE rb_mysql_result_each_(VALUE self,
838
844
  rb_yield(row);
839
845
  }
840
846
  }
841
- if (wrapper->lastRowProcessed == wrapper->numberOfRows) {
847
+ if (wrapper->lastRowProcessed == wrapper->numberOfRows && args->cacheRows) {
842
848
  /* we don't need the mysql C dataset around anymore, peace it */
843
849
  rb_mysql_result_free_result(wrapper);
844
850
  }
@@ -858,6 +864,10 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
858
864
 
859
865
  GET_RESULT(self);
860
866
 
867
+ if (wrapper->stmt_wrapper && wrapper->stmt_wrapper->closed) {
868
+ rb_raise(cMysql2Error, "Statement handle already closed");
869
+ }
870
+
861
871
  defaults = rb_iv_get(self, "@query_options");
862
872
  Check_Type(defaults, T_HASH);
863
873
  if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1) {
@@ -878,6 +888,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
878
888
 
879
889
  if (wrapper->stmt_wrapper && !cacheRows && !wrapper->is_streaming) {
880
890
  rb_warn(":cache_rows is forced for prepared statements (if not streaming)");
891
+ cacheRows = 1;
881
892
  }
882
893
 
883
894
  if (wrapper->stmt_wrapper && !cast) {
@@ -905,12 +916,15 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
905
916
  app_timezone = Qnil;
906
917
  }
907
918
 
908
- if (wrapper->lastRowProcessed == 0 && !wrapper->is_streaming) {
919
+ if (wrapper->rows == Qnil && !wrapper->is_streaming) {
909
920
  wrapper->numberOfRows = wrapper->stmt_wrapper ? mysql_stmt_num_rows(wrapper->stmt_wrapper->stmt) : mysql_num_rows(wrapper->result);
910
- if (wrapper->numberOfRows == 0) {
911
- wrapper->rows = rb_ary_new();
912
- return wrapper->rows;
921
+ wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
922
+ } else if (wrapper->rows && !cacheRows) {
923
+ if (wrapper->resultFreed) {
924
+ rb_raise(cMysql2Error, "Result set has already been freed");
913
925
  }
926
+ mysql_data_seek(wrapper->result, 0);
927
+ wrapper->lastRowProcessed = 0;
914
928
  wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
915
929
  }
916
930
 
@@ -1004,6 +1018,7 @@ void init_mysql2_result() {
1004
1018
  cMysql2Result = rb_define_class_under(mMysql2, "Result", rb_cObject);
1005
1019
  rb_define_method(cMysql2Result, "each", rb_mysql_result_each, -1);
1006
1020
  rb_define_method(cMysql2Result, "fields", rb_mysql_result_fetch_fields, 0);
1021
+ rb_define_method(cMysql2Result, "free", rb_mysql_result_free_, 0);
1007
1022
  rb_define_method(cMysql2Result, "count", rb_mysql_result_count, 0);
1008
1023
  rb_define_alias(cMysql2Result, "size", "count");
1009
1024
 
data/ext/mysql2/result.h CHANGED
@@ -1,5 +1,6 @@
1
1
  #ifndef MYSQL2_RESULT_H
2
2
  #define MYSQL2_RESULT_H
3
+ #include <stdbool.h>
3
4
 
4
5
  void init_mysql2_result(void);
5
6
  VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r, VALUE statement);
@@ -21,8 +22,8 @@ typedef struct {
21
22
  mysql_client_wrapper *client_wrapper;
22
23
  /* statement result bind buffers */
23
24
  MYSQL_BIND *result_buffers;
24
- my_bool *is_null;
25
- my_bool *error;
25
+ bool *is_null;
26
+ bool *error;
26
27
  unsigned long *length;
27
28
  } mysql2_result_wrapper;
28
29
 
@@ -2,24 +2,27 @@
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;
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; \
10
13
  Data_Get_Struct(self, mysql_stmt_wrapper, stmt_wrapper); \
11
- if (!stmt_wrapper->stmt) { rb_raise(cMysql2Error, "Invalid statement handle"); }
12
-
14
+ if (!stmt_wrapper->stmt) { rb_raise(cMysql2Error, "Invalid statement handle"); } \
15
+ if (stmt_wrapper->closed) { rb_raise(cMysql2Error, "Statement handle already closed"); }
13
16
 
14
17
  static void rb_mysql_stmt_mark(void * ptr) {
15
- mysql_stmt_wrapper* stmt_wrapper = (mysql_stmt_wrapper *)ptr;
18
+ mysql_stmt_wrapper *stmt_wrapper = ptr;
16
19
  if (!stmt_wrapper) return;
17
20
 
18
21
  rb_gc_mark(stmt_wrapper->client);
19
22
  }
20
23
 
21
24
  static void *nogvl_stmt_close(void * ptr) {
22
- mysql_stmt_wrapper *stmt_wrapper = (mysql_stmt_wrapper *)ptr;
25
+ mysql_stmt_wrapper *stmt_wrapper = ptr;
23
26
  if (stmt_wrapper->stmt) {
24
27
  mysql_stmt_close(stmt_wrapper->stmt);
25
28
  stmt_wrapper->stmt = NULL;
@@ -28,7 +31,7 @@ static void *nogvl_stmt_close(void * ptr) {
28
31
  }
29
32
 
30
33
  static void rb_mysql_stmt_free(void * ptr) {
31
- mysql_stmt_wrapper* stmt_wrapper = (mysql_stmt_wrapper *)ptr;
34
+ mysql_stmt_wrapper *stmt_wrapper = ptr;
32
35
  decr_mysql2_stmt(stmt_wrapper);
33
36
  }
34
37
 
@@ -41,7 +44,6 @@ void decr_mysql2_stmt(mysql_stmt_wrapper *stmt_wrapper) {
41
44
  }
42
45
  }
43
46
 
44
-
45
47
  void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) {
46
48
  VALUE e;
47
49
  GET_CLIENT(stmt_wrapper->client);
@@ -70,7 +72,6 @@ void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) {
70
72
  rb_exc_raise(e);
71
73
  }
72
74
 
73
-
74
75
  /*
75
76
  * used to pass all arguments to mysql_stmt_prepare while inside
76
77
  * nogvl_prepare_statement_args
@@ -93,7 +94,7 @@ static void *nogvl_prepare_statement(void *ptr) {
93
94
  }
94
95
 
95
96
  VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
96
- mysql_stmt_wrapper* stmt_wrapper;
97
+ mysql_stmt_wrapper *stmt_wrapper;
97
98
  VALUE rb_stmt;
98
99
  #ifdef HAVE_RUBY_ENCODING_H
99
100
  rb_encoding *conn_enc;
@@ -105,6 +106,7 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
105
106
  {
106
107
  stmt_wrapper->client = rb_client;
107
108
  stmt_wrapper->refcount = 1;
109
+ stmt_wrapper->closed = 0;
108
110
  stmt_wrapper->stmt = NULL;
109
111
  }
110
112
 
@@ -122,7 +124,7 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
122
124
 
123
125
  // set STMT_ATTR_UPDATE_MAX_LENGTH attr
124
126
  {
125
- my_bool truth = 1;
127
+ bool truth = 1;
126
128
  if (mysql_stmt_attr_set(stmt_wrapper->stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &truth)) {
127
129
  rb_raise(cMysql2Error, "Unable to initialize prepared statement: set STMT_ATTR_UPDATE_MAX_LENGTH");
128
130
  }
@@ -178,14 +180,16 @@ static void *nogvl_execute(void *ptr) {
178
180
  }
179
181
  }
180
182
 
181
- static void *nogvl_stmt_store_result(void *ptr) {
182
- MYSQL_STMT *stmt = ptr;
183
+ static void set_buffer_for_string(MYSQL_BIND* bind_buffer, unsigned long *length_buffer, VALUE string) {
184
+ unsigned long length;
183
185
 
184
- if (mysql_stmt_store_result(stmt)) {
185
- return (void *)Qfalse;
186
- } else {
187
- return (void *)Qtrue;
188
- }
186
+ bind_buffer->buffer = RSTRING_PTR(string);
187
+
188
+ length = RSTRING_LEN(string);
189
+ bind_buffer->buffer_length = length;
190
+ *length_buffer = length;
191
+
192
+ bind_buffer->length = length_buffer;
189
193
  }
190
194
 
191
195
  /* Free each bind_buffer[i].buffer except when params_enc is non-nil, this means
@@ -202,6 +206,50 @@ static void *nogvl_stmt_store_result(void *ptr) {
202
206
  xfree(length_buffers); \
203
207
  }
204
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
+
205
253
  /* call-seq: stmt.execute
206
254
  *
207
255
  * Executes the current prepared statement, returns +result+.
@@ -263,9 +311,23 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
263
311
  #endif
264
312
  break;
265
313
  case T_BIGNUM:
266
- bind_buffers[i].buffer_type = MYSQL_TYPE_LONGLONG;
267
- bind_buffers[i].buffer = xmalloc(sizeof(long long int));
268
- *(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
+ }
269
331
  break;
270
332
  case T_FLOAT:
271
333
  bind_buffers[i].buffer_type = MYSQL_TYPE_DOUBLE;
@@ -273,17 +335,13 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
273
335
  *(double*)(bind_buffers[i].buffer) = NUM2DBL(argv[i]);
274
336
  break;
275
337
  case T_STRING:
276
- {
277
- params_enc[i] = argv[i];
338
+ bind_buffers[i].buffer_type = MYSQL_TYPE_STRING;
339
+
340
+ params_enc[i] = argv[i];
278
341
  #ifdef HAVE_RUBY_ENCODING_H
279
- 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);
280
343
  #endif
281
- bind_buffers[i].buffer_type = MYSQL_TYPE_STRING;
282
- bind_buffers[i].buffer = RSTRING_PTR(params_enc[i]);
283
- bind_buffers[i].buffer_length = RSTRING_LEN(params_enc[i]);
284
- length_buffers[i] = bind_buffers[i].buffer_length;
285
- bind_buffers[i].length = &length_buffers[i];
286
- }
344
+ set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
287
345
  break;
288
346
  default:
289
347
  // TODO: what Ruby type should support MYSQL_TYPE_TIME
@@ -296,7 +354,13 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
296
354
 
297
355
  memset(&t, 0, sizeof(MYSQL_TIME));
298
356
  t.neg = 0;
299
- 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
+
300
364
  t.second = FIX2INT(rb_funcall(rb_time, intern_sec, 0));
301
365
  t.minute = FIX2INT(rb_funcall(rb_time, intern_min, 0));
302
366
  t.hour = FIX2INT(rb_funcall(rb_time, intern_hour, 0));
@@ -322,6 +386,19 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
322
386
  *(MYSQL_TIME*)(bind_buffers[i].buffer) = t;
323
387
  } else if (CLASS_OF(argv[i]) == cBigDecimal) {
324
388
  bind_buffers[i].buffer_type = MYSQL_TYPE_NEWDECIMAL;
389
+
390
+ // DECIMAL are represented with the "string representation of the
391
+ // original server-side value", see
392
+ // https://dev.mysql.com/doc/refman/5.7/en/c-api-prepared-statement-type-conversions.html
393
+ // This should be independent of the locale used both on the server
394
+ // and the client side.
395
+ VALUE rb_val_as_string = rb_funcall(argv[i], intern_to_s, 0);
396
+
397
+ params_enc[i] = rb_val_as_string;
398
+ #ifdef HAVE_RUBY_ENCODING_H
399
+ params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
400
+ #endif
401
+ set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
325
402
  }
326
403
  break;
327
404
  }
@@ -345,8 +422,7 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
345
422
  if (metadata == NULL) {
346
423
  if (mysql_stmt_errno(stmt) != 0) {
347
424
  // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal.
348
-
349
- MARK_CONN_INACTIVE(stmt_wrapper->client);
425
+ wrapper->active_thread = Qnil;
350
426
  rb_raise_mysql2_stmt_error(stmt_wrapper);
351
427
  }
352
428
  // no data and no error, so query was not a SELECT
@@ -360,11 +436,11 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
360
436
  is_streaming = (Qtrue == rb_hash_aref(current, sym_stream));
361
437
  if (!is_streaming) {
362
438
  // recieve the whole result set from the server
363
- if (rb_thread_call_without_gvl(nogvl_stmt_store_result, stmt, RUBY_UBF_IO, 0) == Qfalse) {
439
+ if (mysql_stmt_store_result(stmt)) {
364
440
  mysql_free_result(metadata);
365
441
  rb_raise_mysql2_stmt_error(stmt_wrapper);
366
442
  }
367
- MARK_CONN_INACTIVE(stmt_wrapper->client);
443
+ wrapper->active_thread = Qnil;
368
444
  }
369
445
 
370
446
  resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, metadata, self);
@@ -461,6 +537,7 @@ static VALUE rb_mysql_stmt_affected_rows(VALUE self) {
461
537
  */
462
538
  static VALUE rb_mysql_stmt_close(VALUE self) {
463
539
  GET_STATEMENT(self);
540
+ stmt_wrapper->closed = 1;
464
541
  rb_thread_call_without_gvl(nogvl_stmt_close, stmt_wrapper, RUBY_UBF_IO, 0);
465
542
  return Qnil;
466
543
  }
@@ -481,6 +558,7 @@ void init_mysql2_statement() {
481
558
  intern_new_with_args = rb_intern("new_with_args");
482
559
  intern_each = rb_intern("each");
483
560
 
561
+ intern_sec_fraction = rb_intern("sec_fraction");
484
562
  intern_usec = rb_intern("usec");
485
563
  intern_sec = rb_intern("sec");
486
564
  intern_min = rb_intern("min");
@@ -488,4 +566,9 @@ void init_mysql2_statement() {
488
566
  intern_day = rb_intern("day");
489
567
  intern_month = rb_intern("month");
490
568
  intern_year = rb_intern("year");
569
+
570
+ intern_to_s = rb_intern("to_s");
571
+ #ifndef HAVE_RB_BIG_CMP
572
+ id_cmp = rb_intern("<=>");
573
+ #endif
491
574
  }