mysql2 0.4.4 → 0.4.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +72 -47
- data/ext/mysql2/client.c +165 -30
- data/ext/mysql2/client.h +1 -8
- data/ext/mysql2/extconf.rb +26 -2
- data/ext/mysql2/mysql2_ext.h +0 -4
- data/ext/mysql2/result.c +12 -3
- data/ext/mysql2/result.h +3 -2
- data/ext/mysql2/statement.c +106 -15
- data/lib/mysql2/client.rb +19 -6
- data/lib/mysql2/version.rb +1 -1
- data/spec/configuration.yml.example +0 -6
- data/spec/em/em_spec.rb +1 -0
- data/spec/mysql2/client_spec.rb +152 -104
- data/spec/mysql2/error_spec.rb +4 -6
- data/spec/mysql2/result_spec.rb +62 -36
- data/spec/mysql2/statement_spec.rb +125 -52
- data/spec/spec_helper.rb +73 -59
- data/spec/ssl/gen_certs.sh +1 -1
- data/support/5072E1F5.asc +432 -0
- metadata +12 -11
data/ext/mysql2/result.c
CHANGED
@@ -262,8 +262,8 @@ static void rb_mysql_result_alloc_result_buffers(VALUE self, MYSQL_FIELD *fields
|
|
262
262
|
if (wrapper->result_buffers != NULL) return;
|
263
263
|
|
264
264
|
wrapper->result_buffers = xcalloc(wrapper->numberOfFields, sizeof(MYSQL_BIND));
|
265
|
-
wrapper->is_null = xcalloc(wrapper->numberOfFields, sizeof(
|
266
|
-
wrapper->error = xcalloc(wrapper->numberOfFields, sizeof(
|
265
|
+
wrapper->is_null = xcalloc(wrapper->numberOfFields, sizeof(bool));
|
266
|
+
wrapper->error = xcalloc(wrapper->numberOfFields, sizeof(bool));
|
267
267
|
wrapper->length = xcalloc(wrapper->numberOfFields, sizeof(unsigned long));
|
268
268
|
|
269
269
|
for (i = 0; i < wrapper->numberOfFields; i++) {
|
@@ -405,6 +405,13 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co
|
|
405
405
|
val = INT2NUM(*((signed char*)result_buffer->buffer));
|
406
406
|
}
|
407
407
|
break;
|
408
|
+
case MYSQL_TYPE_BIT: /* BIT field (MySQL 5.0.3 and up) */
|
409
|
+
if (args->castBool && fields[i].length == 1) {
|
410
|
+
val = (*((unsigned char*)result_buffer->buffer) != 0) ? Qtrue : Qfalse;
|
411
|
+
}else{
|
412
|
+
val = rb_str_new(result_buffer->buffer, *(result_buffer->length));
|
413
|
+
}
|
414
|
+
break;
|
408
415
|
case MYSQL_TYPE_SHORT: // short int
|
409
416
|
if (result_buffer->is_unsigned) {
|
410
417
|
val = UINT2NUM(*((unsigned short int*)result_buffer->buffer));
|
@@ -494,7 +501,6 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co
|
|
494
501
|
case MYSQL_TYPE_BLOB: // char[]
|
495
502
|
case MYSQL_TYPE_MEDIUM_BLOB: // char[]
|
496
503
|
case MYSQL_TYPE_LONG_BLOB: // char[]
|
497
|
-
case MYSQL_TYPE_BIT: // char[]
|
498
504
|
case MYSQL_TYPE_SET: // char[]
|
499
505
|
case MYSQL_TYPE_ENUM: // char[]
|
500
506
|
case MYSQL_TYPE_GEOMETRY: // char[]
|
@@ -920,6 +926,9 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
|
|
920
926
|
wrapper->numberOfRows = wrapper->stmt_wrapper ? mysql_stmt_num_rows(wrapper->stmt_wrapper->stmt) : mysql_num_rows(wrapper->result);
|
921
927
|
wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
|
922
928
|
} else if (wrapper->rows && !cacheRows) {
|
929
|
+
if (wrapper->resultFreed) {
|
930
|
+
rb_raise(cMysql2Error, "Result set has already been freed");
|
931
|
+
}
|
923
932
|
mysql_data_seek(wrapper->result, 0);
|
924
933
|
wrapper->lastRowProcessed = 0;
|
925
934
|
wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
|
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,8 +2,11 @@
|
|
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; \
|
@@ -121,7 +124,7 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
|
|
121
124
|
|
122
125
|
// set STMT_ATTR_UPDATE_MAX_LENGTH attr
|
123
126
|
{
|
124
|
-
|
127
|
+
bool truth = 1;
|
125
128
|
if (mysql_stmt_attr_set(stmt_wrapper->stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &truth)) {
|
126
129
|
rb_raise(cMysql2Error, "Unable to initialize prepared statement: set STMT_ATTR_UPDATE_MAX_LENGTH");
|
127
130
|
}
|
@@ -180,7 +183,6 @@ static void *nogvl_execute(void *ptr) {
|
|
180
183
|
static void set_buffer_for_string(MYSQL_BIND* bind_buffer, unsigned long *length_buffer, VALUE string) {
|
181
184
|
unsigned long length;
|
182
185
|
|
183
|
-
bind_buffer->buffer_type = MYSQL_TYPE_STRING;
|
184
186
|
bind_buffer->buffer = RSTRING_PTR(string);
|
185
187
|
|
186
188
|
length = RSTRING_LEN(string);
|
@@ -204,6 +206,50 @@ static void set_buffer_for_string(MYSQL_BIND* bind_buffer, unsigned long *length
|
|
204
206
|
xfree(length_buffers); \
|
205
207
|
}
|
206
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
|
+
|
207
253
|
/* call-seq: stmt.execute
|
208
254
|
*
|
209
255
|
* Executes the current prepared statement, returns +result+.
|
@@ -265,9 +311,23 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
|
|
265
311
|
#endif
|
266
312
|
break;
|
267
313
|
case T_BIGNUM:
|
268
|
-
|
269
|
-
|
270
|
-
|
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
|
+
}
|
271
331
|
break;
|
272
332
|
case T_FLOAT:
|
273
333
|
bind_buffers[i].buffer_type = MYSQL_TYPE_DOUBLE;
|
@@ -275,13 +335,23 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
|
|
275
335
|
*(double*)(bind_buffers[i].buffer) = NUM2DBL(argv[i]);
|
276
336
|
break;
|
277
337
|
case T_STRING:
|
278
|
-
|
279
|
-
|
338
|
+
bind_buffers[i].buffer_type = MYSQL_TYPE_STRING;
|
339
|
+
|
340
|
+
params_enc[i] = argv[i];
|
280
341
|
#ifdef HAVE_RUBY_ENCODING_H
|
281
|
-
|
342
|
+
params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
|
282
343
|
#endif
|
283
|
-
|
284
|
-
|
344
|
+
set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
|
345
|
+
break;
|
346
|
+
case T_TRUE:
|
347
|
+
bind_buffers[i].buffer_type = MYSQL_TYPE_TINY;
|
348
|
+
bind_buffers[i].buffer = xmalloc(sizeof(signed char));
|
349
|
+
*(signed char*)(bind_buffers[i].buffer) = 1;
|
350
|
+
break;
|
351
|
+
case T_FALSE:
|
352
|
+
bind_buffers[i].buffer_type = MYSQL_TYPE_TINY;
|
353
|
+
bind_buffers[i].buffer = xmalloc(sizeof(signed char));
|
354
|
+
*(signed char*)(bind_buffers[i].buffer) = 0;
|
285
355
|
break;
|
286
356
|
default:
|
287
357
|
// TODO: what Ruby type should support MYSQL_TYPE_TIME
|
@@ -294,7 +364,13 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
|
|
294
364
|
|
295
365
|
memset(&t, 0, sizeof(MYSQL_TIME));
|
296
366
|
t.neg = 0;
|
297
|
-
|
367
|
+
|
368
|
+
if (CLASS_OF(argv[i]) == rb_cTime) {
|
369
|
+
t.second_part = FIX2INT(rb_funcall(rb_time, intern_usec, 0));
|
370
|
+
} else if (CLASS_OF(argv[i]) == cDateTime) {
|
371
|
+
t.second_part = NUM2DBL(rb_funcall(rb_time, intern_sec_fraction, 0)) * 1000000;
|
372
|
+
}
|
373
|
+
|
298
374
|
t.second = FIX2INT(rb_funcall(rb_time, intern_sec, 0));
|
299
375
|
t.minute = FIX2INT(rb_funcall(rb_time, intern_min, 0));
|
300
376
|
t.hour = FIX2INT(rb_funcall(rb_time, intern_hour, 0));
|
@@ -402,6 +478,7 @@ static VALUE fields(VALUE self) {
|
|
402
478
|
rb_encoding *default_internal_enc, *conn_enc;
|
403
479
|
#endif
|
404
480
|
GET_STATEMENT(self);
|
481
|
+
GET_CLIENT(stmt_wrapper->client);
|
405
482
|
stmt = stmt_wrapper->stmt;
|
406
483
|
|
407
484
|
#ifdef HAVE_RUBY_ENCODING_H
|
@@ -412,12 +489,22 @@ static VALUE fields(VALUE self) {
|
|
412
489
|
}
|
413
490
|
#endif
|
414
491
|
|
415
|
-
metadata
|
492
|
+
metadata = mysql_stmt_result_metadata(stmt);
|
493
|
+
if (metadata == NULL) {
|
494
|
+
if (mysql_stmt_errno(stmt) != 0) {
|
495
|
+
// either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal.
|
496
|
+
wrapper->active_thread = Qnil;
|
497
|
+
rb_raise_mysql2_stmt_error(stmt_wrapper);
|
498
|
+
}
|
499
|
+
// no data and no error, so query was not a SELECT
|
500
|
+
return Qnil;
|
501
|
+
}
|
502
|
+
|
416
503
|
fields = mysql_fetch_fields(metadata);
|
417
504
|
field_count = mysql_stmt_field_count(stmt);
|
418
505
|
field_list = rb_ary_new2((long)field_count);
|
419
506
|
|
420
|
-
for(i = 0; i < field_count; i++) {
|
507
|
+
for (i = 0; i < field_count; i++) {
|
421
508
|
VALUE rb_field;
|
422
509
|
|
423
510
|
rb_field = rb_str_new(fields[i].name, fields[i].name_length);
|
@@ -492,6 +579,7 @@ void init_mysql2_statement() {
|
|
492
579
|
intern_new_with_args = rb_intern("new_with_args");
|
493
580
|
intern_each = rb_intern("each");
|
494
581
|
|
582
|
+
intern_sec_fraction = rb_intern("sec_fraction");
|
495
583
|
intern_usec = rb_intern("usec");
|
496
584
|
intern_sec = rb_intern("sec");
|
497
585
|
intern_min = rb_intern("min");
|
@@ -501,4 +589,7 @@ void init_mysql2_statement() {
|
|
501
589
|
intern_year = rb_intern("year");
|
502
590
|
|
503
591
|
intern_to_s = rb_intern("to_s");
|
592
|
+
#ifndef HAVE_RB_BIG_CMP
|
593
|
+
id_cmp = rb_intern("<=>");
|
594
|
+
#endif
|
504
595
|
}
|
data/lib/mysql2/client.rb
CHANGED
@@ -19,6 +19,7 @@ module Mysql2
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def initialize(opts = {})
|
22
|
+
fail Mysql2::Error, "Options parameter must be a Hash" unless opts.is_a? Hash
|
22
23
|
opts = Mysql2::Util.key_hash_as_symbols(opts)
|
23
24
|
@read_timeout = nil
|
24
25
|
@query_options = self.class.default_query_options.dup
|
@@ -30,13 +31,13 @@ module Mysql2
|
|
30
31
|
opts[:connect_timeout] = 120 unless opts.key?(:connect_timeout)
|
31
32
|
|
32
33
|
# TODO: stricter validation rather than silent massaging
|
33
|
-
[:reconnect, :connect_timeout, :local_infile, :read_timeout, :write_timeout, :default_file, :default_group, :secure_auth, :init_command, :automatic_close].each do |key|
|
34
|
+
[:reconnect, :connect_timeout, :local_infile, :read_timeout, :write_timeout, :default_file, :default_group, :secure_auth, :init_command, :automatic_close, :enable_cleartext_plugin].each do |key|
|
34
35
|
next unless opts.key?(key)
|
35
36
|
case key
|
36
|
-
when :reconnect, :local_infile, :secure_auth, :automatic_close
|
37
|
+
when :reconnect, :local_infile, :secure_auth, :automatic_close, :enable_cleartext_plugin
|
37
38
|
send(:"#{key}=", !!opts[key]) # rubocop:disable Style/DoubleNegation
|
38
39
|
when :connect_timeout, :read_timeout, :write_timeout
|
39
|
-
send(:"#{key}=", opts[key]) unless opts[key].nil?
|
40
|
+
send(:"#{key}=", Integer(opts[key])) unless opts[key].nil?
|
40
41
|
else
|
41
42
|
send(:"#{key}=", opts[key])
|
42
43
|
end
|
@@ -46,7 +47,8 @@ module Mysql2
|
|
46
47
|
self.charset_name = opts[:encoding] || 'utf8'
|
47
48
|
|
48
49
|
ssl_options = opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslcipher)
|
49
|
-
ssl_set(*ssl_options) if ssl_options.any?
|
50
|
+
ssl_set(*ssl_options) if ssl_options.any? || opts.key?(:sslverify)
|
51
|
+
self.ssl_mode = parse_ssl_mode(opts[:ssl_mode]) if opts[:ssl_mode]
|
50
52
|
|
51
53
|
case opts[:flags]
|
52
54
|
when Array
|
@@ -60,11 +62,11 @@ module Mysql2
|
|
60
62
|
end
|
61
63
|
|
62
64
|
# SSL verify is a connection flag rather than a mysql_ssl_set option
|
63
|
-
flags |= SSL_VERIFY_SERVER_CERT if opts[:sslverify]
|
65
|
+
flags |= SSL_VERIFY_SERVER_CERT if opts[:sslverify]
|
64
66
|
|
65
67
|
if [:user, :pass, :hostname, :dbname, :db, :sock].any? { |k| @query_options.key?(k) }
|
66
68
|
warn "============= WARNING FROM mysql2 ============="
|
67
|
-
warn "The options :user, :pass, :hostname, :dbname, :db, and :sock will be
|
69
|
+
warn "The options :user, :pass, :hostname, :dbname, :db, and :sock are deprecated and will be removed at some point in the future."
|
68
70
|
warn "Instead, please use :username, :password, :host, :port, :database, :socket, :flags for the options."
|
69
71
|
warn "============= END WARNING FROM mysql2 ========="
|
70
72
|
end
|
@@ -87,6 +89,17 @@ module Mysql2
|
|
87
89
|
connect user, pass, host, port, database, socket, flags
|
88
90
|
end
|
89
91
|
|
92
|
+
def parse_ssl_mode(mode)
|
93
|
+
m = mode.to_s.upcase
|
94
|
+
if m.start_with?('SSL_MODE_')
|
95
|
+
return Mysql2::Client.const_get(m) if Mysql2::Client.const_defined?(m)
|
96
|
+
else
|
97
|
+
x = 'SSL_MODE_' + m
|
98
|
+
return Mysql2::Client.const_get(x) if Mysql2::Client.const_defined?(x)
|
99
|
+
end
|
100
|
+
warn "Unknown MySQL ssl_mode flag: #{mode}"
|
101
|
+
end
|
102
|
+
|
90
103
|
def parse_flags_array(flags, initial = 0)
|
91
104
|
flags.reduce(initial) do |memo, f|
|
92
105
|
fneg = f.start_with?('-') ? f[1..-1] : nil
|
data/lib/mysql2/version.rb
CHANGED
data/spec/em/em_spec.rb
CHANGED
@@ -70,6 +70,7 @@ begin
|
|
70
70
|
let(:client) { Mysql2::EM::Client.new DatabaseCredentials['root'] }
|
71
71
|
let(:error) { StandardError.new('some error') }
|
72
72
|
before { allow(client).to receive(:async_result).and_raise(error) }
|
73
|
+
after { client.close }
|
73
74
|
|
74
75
|
it "should swallow exceptions raised in by the client" do
|
75
76
|
errors = []
|