mysql2 0.4.1 → 0.4.6

Sign up to get free protection for your applications and to get access to all the features.
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
  }