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