mysql2 0.4.2 → 0.5.5
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 +5 -5
- data/README.md +231 -86
- data/ext/mysql2/client.c +527 -128
- data/ext/mysql2/client.h +11 -52
- data/ext/mysql2/extconf.rb +100 -21
- data/ext/mysql2/mysql2_ext.c +8 -2
- data/ext/mysql2/mysql2_ext.h +21 -8
- data/ext/mysql2/mysql_enc_name_to_ruby.h +60 -56
- data/ext/mysql2/mysql_enc_to_ruby.h +64 -3
- data/ext/mysql2/result.c +333 -109
- data/ext/mysql2/result.h +1 -0
- data/ext/mysql2/statement.c +247 -90
- data/ext/mysql2/statement.h +0 -2
- data/ext/mysql2/wait_for_single_fd.h +2 -1
- data/lib/mysql2/client.rb +71 -31
- data/lib/mysql2/em.rb +2 -4
- data/lib/mysql2/error.rb +52 -22
- data/lib/mysql2/result.rb +2 -0
- data/lib/mysql2/statement.rb +3 -11
- data/lib/mysql2/version.rb +1 -1
- data/lib/mysql2.rb +19 -15
- data/support/3A79BD29.asc +49 -0
- data/support/5072E1F5.asc +432 -0
- data/support/C74CD1D8.asc +104 -0
- data/support/mysql_enc_to_ruby.rb +8 -3
- data/support/ruby_enc_to_mysql.rb +7 -5
- metadata +19 -61
- data/examples/eventmachine.rb +0 -21
- data/examples/threaded.rb +0 -18
- data/spec/configuration.yml.example +0 -17
- data/spec/em/em_spec.rb +0 -135
- data/spec/my.cnf.example +0 -9
- data/spec/mysql2/client_spec.rb +0 -939
- data/spec/mysql2/error_spec.rb +0 -84
- data/spec/mysql2/result_spec.rb +0 -510
- data/spec/mysql2/statement_spec.rb +0 -684
- data/spec/rcov.opts +0 -3
- data/spec/spec_helper.rb +0 -94
- data/spec/ssl/ca-cert.pem +0 -17
- data/spec/ssl/ca-key.pem +0 -27
- data/spec/ssl/ca.cnf +0 -22
- data/spec/ssl/cert.cnf +0 -22
- data/spec/ssl/client-cert.pem +0 -17
- data/spec/ssl/client-key.pem +0 -27
- data/spec/ssl/client-req.pem +0 -15
- data/spec/ssl/gen_certs.sh +0 -48
- data/spec/ssl/pkcs8-client-key.pem +0 -28
- data/spec/ssl/pkcs8-server-key.pem +0 -28
- data/spec/ssl/server-cert.pem +0 -17
- data/spec/ssl/server-key.pem +0 -27
- data/spec/ssl/server-req.pem +0 -15
- data/spec/test_data +0 -1
data/ext/mysql2/result.h
CHANGED
data/ext/mysql2/statement.c
CHANGED
@@ -1,25 +1,65 @@
|
|
1
1
|
#include <mysql2_ext.h>
|
2
2
|
|
3
|
-
VALUE
|
4
|
-
|
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
|
3
|
+
extern VALUE mMysql2, cMysql2Error;
|
4
|
+
static VALUE cMysql2Statement, cBigDecimal, cDateTime, cDate;
|
5
|
+
static VALUE sym_stream, intern_new_with_args, intern_each, intern_to_s, intern_merge_bang;
|
6
|
+
static VALUE intern_sec_fraction, intern_usec, intern_sec, intern_min, intern_hour, intern_day, intern_month, intern_year,
|
7
|
+
intern_query_options;
|
8
|
+
|
9
|
+
#ifndef NEW_TYPEDDATA_WRAPPER
|
10
|
+
#define TypedData_Get_Struct(obj, type, ignore, sval) Data_Get_Struct(obj, type, sval)
|
11
|
+
#endif
|
7
12
|
|
8
13
|
#define GET_STATEMENT(self) \
|
9
14
|
mysql_stmt_wrapper *stmt_wrapper; \
|
10
|
-
|
15
|
+
TypedData_Get_Struct(self, mysql_stmt_wrapper, &rb_mysql_statement_type, stmt_wrapper); \
|
11
16
|
if (!stmt_wrapper->stmt) { rb_raise(cMysql2Error, "Invalid statement handle"); } \
|
12
17
|
if (stmt_wrapper->closed) { rb_raise(cMysql2Error, "Statement handle already closed"); }
|
13
18
|
|
14
|
-
|
15
19
|
static void rb_mysql_stmt_mark(void * ptr) {
|
16
20
|
mysql_stmt_wrapper *stmt_wrapper = ptr;
|
17
21
|
if (!stmt_wrapper) return;
|
18
22
|
|
19
|
-
|
23
|
+
rb_gc_mark_movable(stmt_wrapper->client);
|
24
|
+
}
|
25
|
+
|
26
|
+
static void rb_mysql_stmt_free(void *ptr) {
|
27
|
+
mysql_stmt_wrapper *stmt_wrapper = ptr;
|
28
|
+
decr_mysql2_stmt(stmt_wrapper);
|
29
|
+
}
|
30
|
+
|
31
|
+
static size_t rb_mysql_stmt_memsize(const void * ptr) {
|
32
|
+
const mysql_stmt_wrapper *stmt_wrapper = ptr;
|
33
|
+
return sizeof(*stmt_wrapper);
|
34
|
+
}
|
35
|
+
|
36
|
+
#ifdef HAVE_RB_GC_MARK_MOVABLE
|
37
|
+
static void rb_mysql_stmt_compact(void * ptr) {
|
38
|
+
mysql_stmt_wrapper *stmt_wrapper = ptr;
|
39
|
+
if (!stmt_wrapper) return;
|
40
|
+
|
41
|
+
rb_mysql2_gc_location(stmt_wrapper->client);
|
20
42
|
}
|
43
|
+
#endif
|
21
44
|
|
22
|
-
static
|
45
|
+
static const rb_data_type_t rb_mysql_statement_type = {
|
46
|
+
"rb_mysql_statement",
|
47
|
+
{
|
48
|
+
rb_mysql_stmt_mark,
|
49
|
+
rb_mysql_stmt_free,
|
50
|
+
rb_mysql_stmt_memsize,
|
51
|
+
#ifdef HAVE_RB_GC_MARK_MOVABLE
|
52
|
+
rb_mysql_stmt_compact,
|
53
|
+
#endif
|
54
|
+
},
|
55
|
+
0,
|
56
|
+
0,
|
57
|
+
#ifdef RUBY_TYPED_FREE_IMMEDIATELY
|
58
|
+
RUBY_TYPED_FREE_IMMEDIATELY,
|
59
|
+
#endif
|
60
|
+
};
|
61
|
+
|
62
|
+
static void *nogvl_stmt_close(void *ptr) {
|
23
63
|
mysql_stmt_wrapper *stmt_wrapper = ptr;
|
24
64
|
if (stmt_wrapper->stmt) {
|
25
65
|
mysql_stmt_close(stmt_wrapper->stmt);
|
@@ -28,11 +68,6 @@ static void *nogvl_stmt_close(void * ptr) {
|
|
28
68
|
return NULL;
|
29
69
|
}
|
30
70
|
|
31
|
-
static void rb_mysql_stmt_free(void * ptr) {
|
32
|
-
mysql_stmt_wrapper *stmt_wrapper = ptr;
|
33
|
-
decr_mysql2_stmt(stmt_wrapper);
|
34
|
-
}
|
35
|
-
|
36
71
|
void decr_mysql2_stmt(mysql_stmt_wrapper *stmt_wrapper) {
|
37
72
|
stmt_wrapper->refcount--;
|
38
73
|
|
@@ -42,14 +77,12 @@ void decr_mysql2_stmt(mysql_stmt_wrapper *stmt_wrapper) {
|
|
42
77
|
}
|
43
78
|
}
|
44
79
|
|
45
|
-
|
46
80
|
void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) {
|
47
81
|
VALUE e;
|
48
82
|
GET_CLIENT(stmt_wrapper->client);
|
49
83
|
VALUE rb_error_msg = rb_str_new2(mysql_stmt_error(stmt_wrapper->stmt));
|
50
|
-
VALUE rb_sql_state =
|
84
|
+
VALUE rb_sql_state = rb_str_new2(mysql_stmt_sqlstate(stmt_wrapper->stmt));
|
51
85
|
|
52
|
-
#ifdef HAVE_RUBY_ENCODING_H
|
53
86
|
rb_encoding *conn_enc;
|
54
87
|
conn_enc = rb_to_encoding(wrapper->encoding);
|
55
88
|
|
@@ -61,7 +94,6 @@ void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) {
|
|
61
94
|
rb_error_msg = rb_str_export_to_enc(rb_error_msg, default_internal_enc);
|
62
95
|
rb_sql_state = rb_str_export_to_enc(rb_sql_state, default_internal_enc);
|
63
96
|
}
|
64
|
-
#endif
|
65
97
|
|
66
98
|
e = rb_funcall(cMysql2Error, intern_new_with_args, 4,
|
67
99
|
rb_error_msg,
|
@@ -71,7 +103,6 @@ void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) {
|
|
71
103
|
rb_exc_raise(e);
|
72
104
|
}
|
73
105
|
|
74
|
-
|
75
106
|
/*
|
76
107
|
* used to pass all arguments to mysql_stmt_prepare while inside
|
77
108
|
* nogvl_prepare_statement_args
|
@@ -96,13 +127,15 @@ static void *nogvl_prepare_statement(void *ptr) {
|
|
96
127
|
VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
|
97
128
|
mysql_stmt_wrapper *stmt_wrapper;
|
98
129
|
VALUE rb_stmt;
|
99
|
-
#ifdef HAVE_RUBY_ENCODING_H
|
100
130
|
rb_encoding *conn_enc;
|
101
|
-
#endif
|
102
131
|
|
103
132
|
Check_Type(sql, T_STRING);
|
104
133
|
|
134
|
+
#ifdef NEW_TYPEDDATA_WRAPPER
|
135
|
+
rb_stmt = TypedData_Make_Struct(cMysql2Statement, mysql_stmt_wrapper, &rb_mysql_statement_type, stmt_wrapper);
|
136
|
+
#else
|
105
137
|
rb_stmt = Data_Make_Struct(cMysql2Statement, mysql_stmt_wrapper, rb_mysql_stmt_mark, rb_mysql_stmt_free, stmt_wrapper);
|
138
|
+
#endif
|
106
139
|
{
|
107
140
|
stmt_wrapper->client = rb_client;
|
108
141
|
stmt_wrapper->refcount = 1;
|
@@ -114,9 +147,7 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
|
|
114
147
|
{
|
115
148
|
GET_CLIENT(rb_client);
|
116
149
|
stmt_wrapper->stmt = mysql_stmt_init(wrapper->client);
|
117
|
-
#ifdef HAVE_RUBY_ENCODING_H
|
118
150
|
conn_enc = rb_to_encoding(wrapper->encoding);
|
119
|
-
#endif
|
120
151
|
}
|
121
152
|
if (stmt_wrapper->stmt == NULL) {
|
122
153
|
rb_raise(cMysql2Error, "Unable to initialize prepared statement: out of memory");
|
@@ -134,11 +165,8 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
|
|
134
165
|
{
|
135
166
|
struct nogvl_prepare_statement_args args;
|
136
167
|
args.stmt = stmt_wrapper->stmt;
|
137
|
-
args.sql = sql;
|
138
|
-
#ifdef HAVE_RUBY_ENCODING_H
|
139
168
|
// ensure the string is in the encoding the connection is expecting
|
140
|
-
args.sql = rb_str_export_to_enc(
|
141
|
-
#endif
|
169
|
+
args.sql = rb_str_export_to_enc(sql, conn_enc);
|
142
170
|
args.sql_ptr = RSTRING_PTR(sql);
|
143
171
|
args.sql_len = RSTRING_LEN(sql);
|
144
172
|
|
@@ -154,7 +182,7 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
|
|
154
182
|
*
|
155
183
|
* Returns the number of parameters the prepared statement expects.
|
156
184
|
*/
|
157
|
-
static VALUE
|
185
|
+
static VALUE rb_mysql_stmt_param_count(VALUE self) {
|
158
186
|
GET_STATEMENT(self);
|
159
187
|
|
160
188
|
return ULL2NUM(mysql_stmt_param_count(stmt_wrapper->stmt));
|
@@ -164,13 +192,13 @@ static VALUE param_count(VALUE self) {
|
|
164
192
|
*
|
165
193
|
* Returns the number of fields the prepared statement returns.
|
166
194
|
*/
|
167
|
-
static VALUE
|
195
|
+
static VALUE rb_mysql_stmt_field_count(VALUE self) {
|
168
196
|
GET_STATEMENT(self);
|
169
197
|
|
170
198
|
return UINT2NUM(mysql_stmt_field_count(stmt_wrapper->stmt));
|
171
199
|
}
|
172
200
|
|
173
|
-
static void *
|
201
|
+
static void *nogvl_stmt_execute(void *ptr) {
|
174
202
|
MYSQL_STMT *stmt = ptr;
|
175
203
|
|
176
204
|
if (mysql_stmt_execute(stmt)) {
|
@@ -180,21 +208,23 @@ static void *nogvl_execute(void *ptr) {
|
|
180
208
|
}
|
181
209
|
}
|
182
210
|
|
183
|
-
static void *
|
184
|
-
|
211
|
+
static void set_buffer_for_string(MYSQL_BIND* bind_buffer, unsigned long *length_buffer, VALUE string) {
|
212
|
+
unsigned long length;
|
185
213
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
214
|
+
bind_buffer->buffer = RSTRING_PTR(string);
|
215
|
+
|
216
|
+
length = RSTRING_LEN(string);
|
217
|
+
bind_buffer->buffer_length = length;
|
218
|
+
*length_buffer = length;
|
219
|
+
|
220
|
+
bind_buffer->length = length_buffer;
|
191
221
|
}
|
192
222
|
|
193
223
|
/* Free each bind_buffer[i].buffer except when params_enc is non-nil, this means
|
194
224
|
* the buffer is a Ruby string pointer and not our memory to manage.
|
195
225
|
*/
|
196
226
|
#define FREE_BINDS \
|
197
|
-
for (i = 0; i <
|
227
|
+
for (i = 0; i < bind_count; i++) { \
|
198
228
|
if (bind_buffers[i].buffer && NIL_P(params_enc[i])) { \
|
199
229
|
xfree(bind_buffers[i].buffer); \
|
200
230
|
} \
|
@@ -204,48 +234,94 @@ static void *nogvl_stmt_store_result(void *ptr) {
|
|
204
234
|
xfree(length_buffers); \
|
205
235
|
}
|
206
236
|
|
237
|
+
/* return 0 if the given bignum can cast as LONG_LONG, otherwise 1 */
|
238
|
+
static int my_big2ll(VALUE bignum, LONG_LONG *ptr)
|
239
|
+
{
|
240
|
+
unsigned LONG_LONG num;
|
241
|
+
size_t len;
|
242
|
+
// rb_absint_size was added in 2.1.0. See:
|
243
|
+
// https://github.com/ruby/ruby/commit/9fea875
|
244
|
+
#ifdef HAVE_RB_ABSINT_SIZE
|
245
|
+
int nlz_bits = 0;
|
246
|
+
len = rb_absint_size(bignum, &nlz_bits);
|
247
|
+
#else
|
248
|
+
len = RBIGNUM_LEN(bignum) * SIZEOF_BDIGITS;
|
249
|
+
#endif
|
250
|
+
if (len > sizeof(LONG_LONG)) goto overflow;
|
251
|
+
if (RBIGNUM_POSITIVE_P(bignum)) {
|
252
|
+
num = rb_big2ull(bignum);
|
253
|
+
if (num > LLONG_MAX)
|
254
|
+
goto overflow;
|
255
|
+
*ptr = num;
|
256
|
+
}
|
257
|
+
else {
|
258
|
+
if (len == 8 &&
|
259
|
+
#ifdef HAVE_RB_ABSINT_SIZE
|
260
|
+
nlz_bits == 0 &&
|
261
|
+
#endif
|
262
|
+
// rb_absint_singlebit_p was added in 2.1.0. See:
|
263
|
+
// https://github.com/ruby/ruby/commit/e5ff9d5
|
264
|
+
#if defined(HAVE_RB_ABSINT_SIZE) && defined(HAVE_RB_ABSINT_SINGLEBIT_P)
|
265
|
+
/* Optimized to avoid object allocation for Ruby 2.1+
|
266
|
+
* only -0x8000000000000000 is safe if `len == 8 && nlz_bits == 0`
|
267
|
+
*/
|
268
|
+
!rb_absint_singlebit_p(bignum)
|
269
|
+
#else
|
270
|
+
rb_big_cmp(bignum, LL2NUM(LLONG_MIN)) == INT2FIX(-1)
|
271
|
+
#endif
|
272
|
+
) {
|
273
|
+
goto overflow;
|
274
|
+
}
|
275
|
+
*ptr = rb_big2ll(bignum);
|
276
|
+
}
|
277
|
+
return 0;
|
278
|
+
overflow:
|
279
|
+
return 1;
|
280
|
+
}
|
281
|
+
|
207
282
|
/* call-seq: stmt.execute
|
208
283
|
*
|
209
284
|
* Executes the current prepared statement, returns +result+.
|
210
285
|
*/
|
211
|
-
static VALUE
|
286
|
+
static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) {
|
212
287
|
MYSQL_BIND *bind_buffers = NULL;
|
213
288
|
unsigned long *length_buffers = NULL;
|
214
289
|
unsigned long bind_count;
|
215
|
-
long i;
|
290
|
+
unsigned long i;
|
216
291
|
MYSQL_STMT *stmt;
|
217
292
|
MYSQL_RES *metadata;
|
293
|
+
VALUE opts;
|
218
294
|
VALUE current;
|
219
295
|
VALUE resultObj;
|
220
|
-
VALUE *params_enc;
|
296
|
+
VALUE *params_enc = NULL;
|
221
297
|
int is_streaming;
|
222
|
-
#ifdef HAVE_RUBY_ENCODING_H
|
223
298
|
rb_encoding *conn_enc;
|
224
|
-
#endif
|
225
299
|
|
226
300
|
GET_STATEMENT(self);
|
227
301
|
GET_CLIENT(stmt_wrapper->client);
|
228
302
|
|
229
|
-
#ifdef HAVE_RUBY_ENCODING_H
|
230
303
|
conn_enc = rb_to_encoding(wrapper->encoding);
|
231
|
-
#endif
|
232
|
-
|
233
|
-
/* Scratch space for string encoding exports, allocate on the stack. */
|
234
|
-
params_enc = alloca(sizeof(VALUE) * argc);
|
235
304
|
|
236
305
|
stmt = stmt_wrapper->stmt;
|
237
|
-
|
238
306
|
bind_count = mysql_stmt_param_count(stmt);
|
239
|
-
|
240
|
-
|
307
|
+
|
308
|
+
// Get count of ordinary arguments, and extract hash opts/keyword arguments
|
309
|
+
// Use a local scope to avoid leaking the temporary count variable
|
310
|
+
{
|
311
|
+
int c = rb_scan_args(argc, argv, "*:", NULL, &opts);
|
312
|
+
if (c != (long)bind_count) {
|
313
|
+
rb_raise(cMysql2Error, "Bind parameter count (%ld) doesn't match number of arguments (%d)", bind_count, c);
|
314
|
+
}
|
241
315
|
}
|
242
316
|
|
243
317
|
// setup any bind variables in the query
|
244
318
|
if (bind_count > 0) {
|
319
|
+
// Scratch space for string encoding exports, allocate on the stack
|
320
|
+
params_enc = alloca(sizeof(VALUE) * bind_count);
|
245
321
|
bind_buffers = xcalloc(bind_count, sizeof(MYSQL_BIND));
|
246
322
|
length_buffers = xcalloc(bind_count, sizeof(unsigned long));
|
247
323
|
|
248
|
-
for (i = 0; i <
|
324
|
+
for (i = 0; i < bind_count; i++) {
|
249
325
|
bind_buffers[i].buffer = NULL;
|
250
326
|
params_enc[i] = Qnil;
|
251
327
|
|
@@ -265,9 +341,19 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
|
|
265
341
|
#endif
|
266
342
|
break;
|
267
343
|
case T_BIGNUM:
|
268
|
-
|
269
|
-
|
270
|
-
|
344
|
+
{
|
345
|
+
LONG_LONG num;
|
346
|
+
if (my_big2ll(argv[i], &num) == 0) {
|
347
|
+
bind_buffers[i].buffer_type = MYSQL_TYPE_LONGLONG;
|
348
|
+
bind_buffers[i].buffer = xmalloc(sizeof(long long int));
|
349
|
+
*(LONG_LONG*)(bind_buffers[i].buffer) = num;
|
350
|
+
} else {
|
351
|
+
/* The bignum was larger than we can fit in LONG_LONG, send it as a string */
|
352
|
+
bind_buffers[i].buffer_type = MYSQL_TYPE_NEWDECIMAL;
|
353
|
+
params_enc[i] = rb_str_export_to_enc(rb_big2str(argv[i], 10), conn_enc);
|
354
|
+
set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
|
355
|
+
}
|
356
|
+
}
|
271
357
|
break;
|
272
358
|
case T_FLOAT:
|
273
359
|
bind_buffers[i].buffer_type = MYSQL_TYPE_DOUBLE;
|
@@ -275,17 +361,21 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
|
|
275
361
|
*(double*)(bind_buffers[i].buffer) = NUM2DBL(argv[i]);
|
276
362
|
break;
|
277
363
|
case T_STRING:
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
364
|
+
bind_buffers[i].buffer_type = MYSQL_TYPE_STRING;
|
365
|
+
|
366
|
+
params_enc[i] = argv[i];
|
367
|
+
params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
|
368
|
+
set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
|
369
|
+
break;
|
370
|
+
case T_TRUE:
|
371
|
+
bind_buffers[i].buffer_type = MYSQL_TYPE_TINY;
|
372
|
+
bind_buffers[i].buffer = xmalloc(sizeof(signed char));
|
373
|
+
*(signed char*)(bind_buffers[i].buffer) = 1;
|
374
|
+
break;
|
375
|
+
case T_FALSE:
|
376
|
+
bind_buffers[i].buffer_type = MYSQL_TYPE_TINY;
|
377
|
+
bind_buffers[i].buffer = xmalloc(sizeof(signed char));
|
378
|
+
*(signed char*)(bind_buffers[i].buffer) = 0;
|
289
379
|
break;
|
290
380
|
default:
|
291
381
|
// TODO: what Ruby type should support MYSQL_TYPE_TIME
|
@@ -298,7 +388,13 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
|
|
298
388
|
|
299
389
|
memset(&t, 0, sizeof(MYSQL_TIME));
|
300
390
|
t.neg = 0;
|
301
|
-
|
391
|
+
|
392
|
+
if (CLASS_OF(argv[i]) == rb_cTime) {
|
393
|
+
t.second_part = FIX2INT(rb_funcall(rb_time, intern_usec, 0));
|
394
|
+
} else if (CLASS_OF(argv[i]) == cDateTime) {
|
395
|
+
t.second_part = NUM2DBL(rb_funcall(rb_time, intern_sec_fraction, 0)) * 1000000;
|
396
|
+
}
|
397
|
+
|
302
398
|
t.second = FIX2INT(rb_funcall(rb_time, intern_sec, 0));
|
303
399
|
t.minute = FIX2INT(rb_funcall(rb_time, intern_min, 0));
|
304
400
|
t.hour = FIX2INT(rb_funcall(rb_time, intern_hour, 0));
|
@@ -324,6 +420,17 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
|
|
324
420
|
*(MYSQL_TIME*)(bind_buffers[i].buffer) = t;
|
325
421
|
} else if (CLASS_OF(argv[i]) == cBigDecimal) {
|
326
422
|
bind_buffers[i].buffer_type = MYSQL_TYPE_NEWDECIMAL;
|
423
|
+
|
424
|
+
// DECIMAL are represented with the "string representation of the
|
425
|
+
// original server-side value", see
|
426
|
+
// https://dev.mysql.com/doc/refman/5.7/en/c-api-prepared-statement-type-conversions.html
|
427
|
+
// This should be independent of the locale used both on the server
|
428
|
+
// and the client side.
|
429
|
+
VALUE rb_val_as_string = rb_funcall(argv[i], intern_to_s, 0);
|
430
|
+
|
431
|
+
params_enc[i] = rb_val_as_string;
|
432
|
+
params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
|
433
|
+
set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
|
327
434
|
}
|
328
435
|
break;
|
329
436
|
}
|
@@ -336,7 +443,40 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
|
|
336
443
|
}
|
337
444
|
}
|
338
445
|
|
339
|
-
|
446
|
+
// Duplicate the options hash, merge! extra opts, put the copy into the Result object
|
447
|
+
current = rb_hash_dup(rb_ivar_get(stmt_wrapper->client, intern_query_options));
|
448
|
+
(void)RB_GC_GUARD(current);
|
449
|
+
Check_Type(current, T_HASH);
|
450
|
+
|
451
|
+
// Merge in hash opts/keyword arguments
|
452
|
+
if (!NIL_P(opts)) {
|
453
|
+
rb_funcall(current, intern_merge_bang, 1, opts);
|
454
|
+
}
|
455
|
+
|
456
|
+
is_streaming = (Qtrue == rb_hash_aref(current, sym_stream));
|
457
|
+
|
458
|
+
// From stmt_execute to mysql_stmt_result_metadata to stmt_store_result, no
|
459
|
+
// Ruby API calls are allowed so that GC is not invoked. If the connection is
|
460
|
+
// in results-streaming-mode for Statement A, and in the middle Statement B
|
461
|
+
// gets garbage collected, a message will be sent to the server notifying it
|
462
|
+
// to release Statement B, resulting in the following error:
|
463
|
+
// Commands out of sync; you can't run this command now
|
464
|
+
//
|
465
|
+
// In streaming mode, statement execute must return a cursor because we
|
466
|
+
// cannot prevent other Statement objects from being garbage collected
|
467
|
+
// between fetches of each row of the result set. The following error
|
468
|
+
// occurs if cursor mode is not set:
|
469
|
+
// Row retrieval was canceled by mysql_stmt_close
|
470
|
+
|
471
|
+
if (is_streaming) {
|
472
|
+
unsigned long type = CURSOR_TYPE_READ_ONLY;
|
473
|
+
if (mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, &type)) {
|
474
|
+
FREE_BINDS;
|
475
|
+
rb_raise(cMysql2Error, "Unable to stream prepared statement, could not set CURSOR_TYPE_READ_ONLY");
|
476
|
+
}
|
477
|
+
}
|
478
|
+
|
479
|
+
if ((VALUE)rb_thread_call_without_gvl(nogvl_stmt_execute, stmt, RUBY_UBF_IO, 0) == Qfalse) {
|
340
480
|
FREE_BINDS;
|
341
481
|
rb_raise_mysql2_stmt_error(stmt_wrapper);
|
342
482
|
}
|
@@ -347,30 +487,26 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
|
|
347
487
|
if (metadata == NULL) {
|
348
488
|
if (mysql_stmt_errno(stmt) != 0) {
|
349
489
|
// either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal.
|
350
|
-
|
351
|
-
MARK_CONN_INACTIVE(stmt_wrapper->client);
|
490
|
+
wrapper->active_fiber = Qnil;
|
352
491
|
rb_raise_mysql2_stmt_error(stmt_wrapper);
|
353
492
|
}
|
354
493
|
// no data and no error, so query was not a SELECT
|
355
494
|
return Qnil;
|
356
495
|
}
|
357
496
|
|
358
|
-
current = rb_hash_dup(rb_iv_get(stmt_wrapper->client, "@query_options"));
|
359
|
-
(void)RB_GC_GUARD(current);
|
360
|
-
Check_Type(current, T_HASH);
|
361
|
-
|
362
|
-
is_streaming = (Qtrue == rb_hash_aref(current, sym_stream));
|
363
497
|
if (!is_streaming) {
|
364
|
-
//
|
365
|
-
if (
|
498
|
+
// receive the whole result set from the server
|
499
|
+
if (mysql_stmt_store_result(stmt)) {
|
366
500
|
mysql_free_result(metadata);
|
367
501
|
rb_raise_mysql2_stmt_error(stmt_wrapper);
|
368
502
|
}
|
369
|
-
|
503
|
+
wrapper->active_fiber = Qnil;
|
370
504
|
}
|
371
505
|
|
372
506
|
resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, metadata, self);
|
373
507
|
|
508
|
+
rb_mysql_set_server_query_flags(wrapper->client, resultObj);
|
509
|
+
|
374
510
|
if (!is_streaming) {
|
375
511
|
// cache all result
|
376
512
|
rb_funcall(resultObj, intern_each, 0);
|
@@ -383,42 +519,47 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
|
|
383
519
|
*
|
384
520
|
* Returns a list of fields that will be returned by this statement.
|
385
521
|
*/
|
386
|
-
static VALUE
|
522
|
+
static VALUE rb_mysql_stmt_fields(VALUE self) {
|
387
523
|
MYSQL_FIELD *fields;
|
388
524
|
MYSQL_RES *metadata;
|
389
525
|
unsigned int field_count;
|
390
526
|
unsigned int i;
|
391
527
|
VALUE field_list;
|
392
528
|
MYSQL_STMT* stmt;
|
393
|
-
#ifdef HAVE_RUBY_ENCODING_H
|
394
529
|
rb_encoding *default_internal_enc, *conn_enc;
|
395
|
-
#endif
|
396
530
|
GET_STATEMENT(self);
|
531
|
+
GET_CLIENT(stmt_wrapper->client);
|
397
532
|
stmt = stmt_wrapper->stmt;
|
398
533
|
|
399
|
-
#ifdef HAVE_RUBY_ENCODING_H
|
400
534
|
default_internal_enc = rb_default_internal_encoding();
|
401
535
|
{
|
402
536
|
GET_CLIENT(stmt_wrapper->client);
|
403
537
|
conn_enc = rb_to_encoding(wrapper->encoding);
|
404
538
|
}
|
405
|
-
#endif
|
406
539
|
|
407
|
-
metadata
|
540
|
+
metadata = mysql_stmt_result_metadata(stmt);
|
541
|
+
if (metadata == NULL) {
|
542
|
+
if (mysql_stmt_errno(stmt) != 0) {
|
543
|
+
// either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal.
|
544
|
+
wrapper->active_fiber = Qnil;
|
545
|
+
rb_raise_mysql2_stmt_error(stmt_wrapper);
|
546
|
+
}
|
547
|
+
// no data and no error, so query was not a SELECT
|
548
|
+
return Qnil;
|
549
|
+
}
|
550
|
+
|
408
551
|
fields = mysql_fetch_fields(metadata);
|
409
552
|
field_count = mysql_stmt_field_count(stmt);
|
410
553
|
field_list = rb_ary_new2((long)field_count);
|
411
554
|
|
412
|
-
for(i = 0; i < field_count; i++) {
|
555
|
+
for (i = 0; i < field_count; i++) {
|
413
556
|
VALUE rb_field;
|
414
557
|
|
415
558
|
rb_field = rb_str_new(fields[i].name, fields[i].name_length);
|
416
|
-
#ifdef HAVE_RUBY_ENCODING_H
|
417
559
|
rb_enc_associate(rb_field, conn_enc);
|
418
560
|
if (default_internal_enc) {
|
419
561
|
rb_field = rb_str_export_to_enc(rb_field, default_internal_enc);
|
420
562
|
}
|
421
|
-
#endif
|
422
563
|
|
423
564
|
rb_ary_store(field_list, (long)i, rb_field);
|
424
565
|
}
|
@@ -469,12 +610,23 @@ static VALUE rb_mysql_stmt_close(VALUE self) {
|
|
469
610
|
}
|
470
611
|
|
471
612
|
void init_mysql2_statement() {
|
613
|
+
cDate = rb_const_get(rb_cObject, rb_intern("Date"));
|
614
|
+
rb_global_variable(&cDate);
|
615
|
+
|
616
|
+
cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
|
617
|
+
rb_global_variable(&cDateTime);
|
618
|
+
|
619
|
+
cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal"));
|
620
|
+
rb_global_variable(&cBigDecimal);
|
621
|
+
|
472
622
|
cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject);
|
623
|
+
rb_undef_alloc_func(cMysql2Statement);
|
624
|
+
rb_global_variable(&cMysql2Statement);
|
473
625
|
|
474
|
-
rb_define_method(cMysql2Statement, "param_count",
|
475
|
-
rb_define_method(cMysql2Statement, "field_count",
|
476
|
-
rb_define_method(cMysql2Statement, "_execute",
|
477
|
-
rb_define_method(cMysql2Statement, "fields",
|
626
|
+
rb_define_method(cMysql2Statement, "param_count", rb_mysql_stmt_param_count, 0);
|
627
|
+
rb_define_method(cMysql2Statement, "field_count", rb_mysql_stmt_field_count, 0);
|
628
|
+
rb_define_method(cMysql2Statement, "_execute", rb_mysql_stmt_execute, -1);
|
629
|
+
rb_define_method(cMysql2Statement, "fields", rb_mysql_stmt_fields, 0);
|
478
630
|
rb_define_method(cMysql2Statement, "last_id", rb_mysql_stmt_last_id, 0);
|
479
631
|
rb_define_method(cMysql2Statement, "affected_rows", rb_mysql_stmt_affected_rows, 0);
|
480
632
|
rb_define_method(cMysql2Statement, "close", rb_mysql_stmt_close, 0);
|
@@ -484,6 +636,7 @@ void init_mysql2_statement() {
|
|
484
636
|
intern_new_with_args = rb_intern("new_with_args");
|
485
637
|
intern_each = rb_intern("each");
|
486
638
|
|
639
|
+
intern_sec_fraction = rb_intern("sec_fraction");
|
487
640
|
intern_usec = rb_intern("usec");
|
488
641
|
intern_sec = rb_intern("sec");
|
489
642
|
intern_min = rb_intern("min");
|
@@ -491,4 +644,8 @@ void init_mysql2_statement() {
|
|
491
644
|
intern_day = rb_intern("day");
|
492
645
|
intern_month = rb_intern("month");
|
493
646
|
intern_year = rb_intern("year");
|
647
|
+
|
648
|
+
intern_to_s = rb_intern("to_s");
|
649
|
+
intern_merge_bang = rb_intern("merge!");
|
650
|
+
intern_query_options = rb_intern("@query_options");
|
494
651
|
}
|
data/ext/mysql2/statement.h
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
/*
|
2
|
-
* backwards compatibility for
|
2
|
+
* backwards compatibility for Rubinius. See
|
3
|
+
* https://github.com/rubinius/rubinius/issues/3771.
|
3
4
|
*
|
4
5
|
* Ruby 1.9.3 provides this API which allows the use of ppoll() on Linux
|
5
6
|
* to minimize select() and malloc() overhead on high-numbered FDs.
|