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.
- checksums.yaml +4 -4
- data/README.md +58 -15
- data/ext/mysql2/client.c +190 -50
- data/ext/mysql2/client.h +1 -10
- data/ext/mysql2/extconf.rb +59 -15
- data/ext/mysql2/result.c +52 -37
- data/ext/mysql2/result.h +3 -2
- data/ext/mysql2/statement.c +118 -35
- data/ext/mysql2/statement.h +1 -0
- data/lib/mysql2/client.rb +42 -7
- data/lib/mysql2/version.rb +1 -1
- data/spec/configuration.yml.example +0 -6
- data/spec/mysql2/client_spec.rb +124 -70
- data/spec/mysql2/error_spec.rb +1 -1
- data/spec/mysql2/result_spec.rb +9 -0
- data/spec/mysql2/statement_spec.rb +81 -6
- data/spec/ssl/gen_certs.sh +1 -1
- metadata +3 -3
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);
|
data/ext/mysql2/extconf.rb
CHANGED
@@ -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
|
159
|
-
abort "-----\nCannot link to libmysql.a (my_init)\n
|
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(
|
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
|
-
|
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
|
-
|
97
|
-
|
98
|
-
|
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
|
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(
|
258
|
-
wrapper->error = xcalloc(wrapper->numberOfFields, sizeof(
|
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
|
-
|
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->
|
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
|
-
|
911
|
-
|
912
|
-
|
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
|
-
|
25
|
-
|
25
|
+
bool *is_null;
|
26
|
+
bool *error;
|
26
27
|
unsigned long *length;
|
27
28
|
} mysql2_result_wrapper;
|
28
29
|
|
data/ext/mysql2/statement.c
CHANGED
@@ -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*
|
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 =
|
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*
|
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*
|
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
|
-
|
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 *
|
182
|
-
|
183
|
+
static void set_buffer_for_string(MYSQL_BIND* bind_buffer, unsigned long *length_buffer, VALUE string) {
|
184
|
+
unsigned long length;
|
183
185
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
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
|
-
|
267
|
-
|
268
|
-
|
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
|
-
|
338
|
+
bind_buffers[i].buffer_type = MYSQL_TYPE_STRING;
|
339
|
+
|
340
|
+
params_enc[i] = argv[i];
|
278
341
|
#ifdef HAVE_RUBY_ENCODING_H
|
279
|
-
|
342
|
+
params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
|
280
343
|
#endif
|
281
|
-
|
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
|
-
|
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 (
|
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
|
-
|
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
|
}
|