mysql2 0.2.23 → 0.2.24
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +9 -6
- data/ext/mysql2/client.c +32 -15
- data/ext/mysql2/client.h +1 -1
- data/ext/mysql2/infile.c +2 -0
- data/ext/mysql2/result.c +26 -7
- data/lib/mysql2/error.rb +4 -5
- data/lib/mysql2/version.rb +1 -1
- data/spec/mysql2/client_spec.rb +28 -14
- data/spec/mysql2/result_spec.rb +10 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 53c6d16a0c12323bd8da93f743e94a27236d37f1
|
4
|
+
data.tar.gz: 278f215f56357c0c606359dd5565c4a9ec1bdc2a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a4d27f578c9b0a772f3e7835fe34bcfb0844dfca568a12f244f86aaea706abb5523ed665c7cac44714f49780a1d04afa4f9264a2fcf0ec1272fc394980d97a8b
|
7
|
+
data.tar.gz: 570628b590e4abc575f7699997b101133ee56c70ae0a06c2ed0a530621d9a77c78acd7cc81cf75ff5b2af8d23f31fe618c2d73314f3d677159aabac0095107e0
|
data/README.md
CHANGED
@@ -1,16 +1,16 @@
|
|
1
|
-
# Mysql2 - A modern, simple and very fast
|
1
|
+
# Mysql2 - A modern, simple and very fast MySQL library for Ruby - binding to libmysql
|
2
2
|
|
3
3
|
[![Build Status](https://travis-ci.org/brianmario/mysql2.png)](https://travis-ci.org/brianmario/mysql2)
|
4
4
|
|
5
5
|
The Mysql2 gem is meant to serve the extremely common use-case of connecting, querying and iterating on results.
|
6
|
-
Some database libraries out there serve as direct 1:1 mappings of the already complex C
|
6
|
+
Some database libraries out there serve as direct 1:1 mappings of the already complex C APIs available.
|
7
7
|
This one is not.
|
8
8
|
|
9
9
|
It also forces the use of UTF-8 [or binary] for the connection [and all strings in 1.9, unless Encoding.default_internal is set then it'll convert from UTF-8 to that encoding] and uses encoding-aware MySQL API calls where it can.
|
10
10
|
|
11
11
|
The API consists of two classes:
|
12
12
|
|
13
|
-
`Mysql2::Client` - your connection to the database
|
13
|
+
`Mysql2::Client` - your connection to the database.
|
14
14
|
|
15
15
|
`Mysql2::Result` - returned from issuing a #query on the connection. It includes Enumerable.
|
16
16
|
|
@@ -26,7 +26,7 @@ By default, the mysql2 gem will try to find a copy of MySQL in this order:
|
|
26
26
|
|
27
27
|
* Option `--with-mysql-dir`, if provided (see below).
|
28
28
|
* Option `--with-mysql-config`, if provided (see below).
|
29
|
-
* Several typical paths for `
|
29
|
+
* Several typical paths for `mysql_config` (default for the majority of users).
|
30
30
|
* The directory `/usr/local`.
|
31
31
|
|
32
32
|
### Configuration options
|
@@ -104,7 +104,10 @@ results.each do |row|
|
|
104
104
|
# conveniently, row is a hash
|
105
105
|
# the keys are the fields, as you'd expect
|
106
106
|
# the values are pre-built ruby primitives mapped from their corresponding field types in MySQL
|
107
|
-
#
|
107
|
+
puts row["id"] # row["id"].class == Fixnum
|
108
|
+
if row["dne"] # non-existant hash entry is nil
|
109
|
+
puts row["dne"]
|
110
|
+
end
|
108
111
|
end
|
109
112
|
```
|
110
113
|
|
@@ -335,7 +338,7 @@ result = client.query("SELECT * FROM table_with_boolean_field", :cast_booleans =
|
|
335
338
|
|
336
339
|
### Skipping casting
|
337
340
|
|
338
|
-
Mysql2 casting is fast, but not as fast as not casting data. In rare cases where typecasting is not needed, it will be faster to disable it by providing :cast => false.
|
341
|
+
Mysql2 casting is fast, but not as fast as not casting data. In rare cases where typecasting is not needed, it will be faster to disable it by providing :cast => false. (Note that :cast => false overrides :cast_booleans => true.)
|
339
342
|
|
340
343
|
``` ruby
|
341
344
|
client = Mysql2::Client.new
|
data/ext/mysql2/client.c
CHANGED
@@ -6,7 +6,9 @@
|
|
6
6
|
#include <sys/types.h>
|
7
7
|
#include <sys/socket.h>
|
8
8
|
#endif
|
9
|
+
#ifndef _MSC_VER
|
9
10
|
#include <unistd.h>
|
11
|
+
#endif
|
10
12
|
#include <fcntl.h>
|
11
13
|
#include "wait_for_single_fd.h"
|
12
14
|
|
@@ -167,26 +169,30 @@ static void *nogvl_connect(void *ptr) {
|
|
167
169
|
|
168
170
|
#ifndef _WIN32
|
169
171
|
/*
|
170
|
-
* Redirect clientfd to
|
171
|
-
*
|
172
|
-
*
|
173
|
-
*
|
174
|
-
*
|
172
|
+
* Redirect clientfd to /dev/null for mysql_close and SSL_close to write,
|
173
|
+
* shutdown, and close. The hack is needed to prevent shutdown() from breaking
|
174
|
+
* a socket that may be in use by the parent or other processes after fork.
|
175
|
+
*
|
176
|
+
* /dev/null is used to absorb writes; previously a dummy socket was used, but
|
177
|
+
* it could not abosrb writes and caused openssl to go into an infinite loop.
|
178
|
+
*
|
175
179
|
* Returns Qtrue or Qfalse (success or failure)
|
180
|
+
*
|
181
|
+
* Note: if this function is needed on Windows, use "nul" instead of "/dev/null"
|
176
182
|
*/
|
177
183
|
static VALUE invalidate_fd(int clientfd)
|
178
184
|
{
|
179
185
|
#ifdef SOCK_CLOEXEC
|
180
186
|
/* Atomically set CLOEXEC on the new FD in case another thread forks */
|
181
|
-
int sockfd =
|
187
|
+
int sockfd = open("/dev/null", O_RDWR | O_CLOEXEC);
|
182
188
|
if (sockfd < 0) {
|
183
189
|
/* Maybe SOCK_CLOEXEC is defined but not available on this kernel */
|
184
|
-
int sockfd =
|
190
|
+
int sockfd = open("/dev/null", O_RDWR);
|
185
191
|
fcntl(sockfd, F_SETFD, FD_CLOEXEC);
|
186
192
|
}
|
187
193
|
#else
|
188
194
|
/* Well we don't have SOCK_CLOEXEC, so just set FD_CLOEXEC quickly */
|
189
|
-
int sockfd =
|
195
|
+
int sockfd = open("/dev/null", O_RDWR);
|
190
196
|
fcntl(sockfd, F_SETFD, FD_CLOEXEC);
|
191
197
|
#endif
|
192
198
|
|
@@ -326,10 +332,10 @@ static VALUE rb_mysql_info(VALUE self) {
|
|
326
332
|
|
327
333
|
static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags) {
|
328
334
|
struct nogvl_connect_args args;
|
329
|
-
VALUE rv;
|
330
|
-
GET_CLIENT(self);
|
331
335
|
time_t start_time, end_time;
|
332
336
|
unsigned int elapsed_time, connect_timeout;
|
337
|
+
VALUE rv;
|
338
|
+
GET_CLIENT(self);
|
333
339
|
|
334
340
|
args.host = NIL_P(host) ? NULL : StringValuePtr(host);
|
335
341
|
args.unix_socket = NIL_P(socket) ? NULL : StringValuePtr(socket);
|
@@ -349,11 +355,11 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
|
|
349
355
|
time(&end_time);
|
350
356
|
/* avoid long connect timeout from system time changes */
|
351
357
|
if (end_time < start_time)
|
352
|
-
|
358
|
+
start_time = end_time;
|
353
359
|
elapsed_time = end_time - start_time;
|
354
360
|
/* avoid an early timeout due to time truncating milliseconds off the start time */
|
355
361
|
if (elapsed_time > 0)
|
356
|
-
|
362
|
+
elapsed_time--;
|
357
363
|
if (elapsed_time >= wrapper->connect_timeout)
|
358
364
|
break;
|
359
365
|
connect_timeout = wrapper->connect_timeout - elapsed_time;
|
@@ -764,17 +770,17 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) {
|
|
764
770
|
|
765
771
|
switch(opt) {
|
766
772
|
case MYSQL_OPT_CONNECT_TIMEOUT:
|
767
|
-
intval =
|
773
|
+
intval = NUM2UINT(value);
|
768
774
|
retval = &intval;
|
769
775
|
break;
|
770
776
|
|
771
777
|
case MYSQL_OPT_READ_TIMEOUT:
|
772
|
-
intval =
|
778
|
+
intval = NUM2UINT(value);
|
773
779
|
retval = &intval;
|
774
780
|
break;
|
775
781
|
|
776
782
|
case MYSQL_OPT_WRITE_TIMEOUT:
|
777
|
-
intval =
|
783
|
+
intval = NUM2UINT(value);
|
778
784
|
retval = &intval;
|
779
785
|
break;
|
780
786
|
|
@@ -1233,6 +1239,13 @@ void init_mysql2_client() {
|
|
1233
1239
|
}
|
1234
1240
|
}
|
1235
1241
|
|
1242
|
+
/* Initializing mysql library, so different threads could call Client.new */
|
1243
|
+
/* without race condition in the library */
|
1244
|
+
if (mysql_library_init(0, NULL, NULL) != 0) {
|
1245
|
+
rb_raise(rb_eRuntimeError, "Could not initialize MySQL client library");
|
1246
|
+
return;
|
1247
|
+
}
|
1248
|
+
|
1236
1249
|
#if 0
|
1237
1250
|
mMysql2 = rb_define_module("Mysql2"); Teach RDoc about Mysql2 constant.
|
1238
1251
|
#endif
|
@@ -1365,6 +1378,10 @@ void init_mysql2_client() {
|
|
1365
1378
|
#ifdef CLIENT_SECURE_CONNECTION
|
1366
1379
|
rb_const_set(cMysql2Client, rb_intern("SECURE_CONNECTION"),
|
1367
1380
|
LONG2NUM(CLIENT_SECURE_CONNECTION));
|
1381
|
+
#else
|
1382
|
+
/* HACK because MySQL5.7 no longer defines this constant,
|
1383
|
+
* but we're using it in our default connection flags. */
|
1384
|
+
rb_const_set(cMysql2Client, rb_intern("SECURE_CONNECTION"), LONG2NUM(0));
|
1368
1385
|
#endif
|
1369
1386
|
|
1370
1387
|
#ifdef CLIENT_MULTI_STATEMENTS
|
data/ext/mysql2/client.h
CHANGED
data/ext/mysql2/infile.c
CHANGED
data/ext/mysql2/result.c
CHANGED
@@ -176,6 +176,20 @@ static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_e
|
|
176
176
|
}
|
177
177
|
#endif
|
178
178
|
|
179
|
+
/* Interpret microseconds digits left-aligned in fixed-width field.
|
180
|
+
* e.g. 10.123 seconds means 10 seconds and 123000 microseconds,
|
181
|
+
* because the microseconds are to the right of the decimal point.
|
182
|
+
*/
|
183
|
+
static unsigned int msec_char_to_uint(char *msec_char, size_t len)
|
184
|
+
{
|
185
|
+
int i;
|
186
|
+
for (i = 0; i < (len - 1); i++) {
|
187
|
+
if (msec_char[i] == '\0') {
|
188
|
+
msec_char[i] = '0';
|
189
|
+
}
|
190
|
+
}
|
191
|
+
return (unsigned int)strtoul(msec_char, NULL, 10);
|
192
|
+
}
|
179
193
|
|
180
194
|
static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezone, int symbolizeKeys, int asArray, int castBool, int cast, MYSQL_FIELD * fields) {
|
181
195
|
VALUE rowVal;
|
@@ -274,13 +288,16 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
|
|
274
288
|
}
|
275
289
|
case MYSQL_TYPE_TIME: { /* TIME field */
|
276
290
|
int tokens;
|
277
|
-
unsigned int hour=0, min=0, sec=0;
|
278
|
-
|
291
|
+
unsigned int hour=0, min=0, sec=0, msec=0;
|
292
|
+
char msec_char[7] = {'0','0','0','0','0','0','\0'};
|
293
|
+
|
294
|
+
tokens = sscanf(row[i], "%2u:%2u:%2u.%6s", &hour, &min, &sec, msec_char);
|
279
295
|
if (tokens < 3) {
|
280
296
|
val = Qnil;
|
281
297
|
break;
|
282
298
|
}
|
283
|
-
|
299
|
+
msec = msec_char_to_uint(msec_char, sizeof(msec_char));
|
300
|
+
val = rb_funcall(rb_cTime, db_timezone, 6, opt_time_year, opt_time_month, opt_time_month, UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec));
|
284
301
|
if (!NIL_P(app_timezone)) {
|
285
302
|
if (app_timezone == intern_local) {
|
286
303
|
val = rb_funcall(val, intern_localtime, 0);
|
@@ -294,9 +311,10 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
|
|
294
311
|
case MYSQL_TYPE_DATETIME: { /* DATETIME field */
|
295
312
|
int tokens;
|
296
313
|
unsigned int year=0, month=0, day=0, hour=0, min=0, sec=0, msec=0;
|
314
|
+
char msec_char[7] = {'0','0','0','0','0','0','\0'};
|
297
315
|
uint64_t seconds;
|
298
316
|
|
299
|
-
tokens = sscanf(row[i], "%4u-%2u-%2u %2u:%2u:%2u.%
|
317
|
+
tokens = sscanf(row[i], "%4u-%2u-%2u %2u:%2u:%2u.%6s", &year, &month, &day, &hour, &min, &sec, msec_char);
|
300
318
|
if (tokens < 6) { /* msec might be empty */
|
301
319
|
val = Qnil;
|
302
320
|
break;
|
@@ -307,7 +325,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
|
|
307
325
|
val = Qnil;
|
308
326
|
} else {
|
309
327
|
if (month < 1 || day < 1) {
|
310
|
-
rb_raise(cMysql2Error, "Invalid date: %s", row[i]);
|
328
|
+
rb_raise(cMysql2Error, "Invalid date in field '%.*s': %s", fields[i].name_length, fields[i].name, row[i]);
|
311
329
|
val = Qnil;
|
312
330
|
} else {
|
313
331
|
if (seconds < MYSQL2_MIN_TIME || seconds > MYSQL2_MAX_TIME) { /* use DateTime for larger date range, does not support microseconds */
|
@@ -324,7 +342,8 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
|
|
324
342
|
val = rb_funcall(val, intern_new_offset, 1, opt_utc_offset);
|
325
343
|
}
|
326
344
|
}
|
327
|
-
} else {
|
345
|
+
} else {
|
346
|
+
msec = msec_char_to_uint(msec_char, sizeof(msec_char));
|
328
347
|
val = rb_funcall(rb_cTime, db_timezone, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec));
|
329
348
|
if (!NIL_P(app_timezone)) {
|
330
349
|
if (app_timezone == intern_local) {
|
@@ -351,7 +370,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
|
|
351
370
|
val = Qnil;
|
352
371
|
} else {
|
353
372
|
if (month < 1 || day < 1) {
|
354
|
-
rb_raise(cMysql2Error, "Invalid date: %s", row[i]);
|
373
|
+
rb_raise(cMysql2Error, "Invalid date in field '%.*s': %s", fields[i].name_length, fields[i].name, row[i]);
|
355
374
|
val = Qnil;
|
356
375
|
} else {
|
357
376
|
val = rb_funcall(cDate, intern_new, 3, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day));
|
data/lib/mysql2/error.rb
CHANGED
@@ -5,7 +5,8 @@ module Mysql2
|
|
5
5
|
REPLACEMENT_CHAR = '?'
|
6
6
|
ENCODE_OPTS = {:undef => :replace, :invalid => :replace, :replace => REPLACEMENT_CHAR}
|
7
7
|
|
8
|
-
attr_accessor :error_number
|
8
|
+
attr_accessor :error_number
|
9
|
+
attr_reader :sql_state
|
9
10
|
attr_writer :server_version
|
10
11
|
|
11
12
|
# Mysql gem compatibility
|
@@ -18,10 +19,8 @@ module Mysql2
|
|
18
19
|
super(clean_message(msg))
|
19
20
|
end
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
@sql_state = state.encode(ENCODE_OPTS)
|
24
|
-
end
|
22
|
+
def sql_state=(state)
|
23
|
+
@sql_state = ''.respond_to?(:encode) ? state.encode(ENCODE_OPTS) : state
|
25
24
|
end
|
26
25
|
|
27
26
|
private
|
data/lib/mysql2/version.rb
CHANGED
data/spec/mysql2/client_spec.rb
CHANGED
@@ -91,15 +91,26 @@ describe Mysql2::Client do
|
|
91
91
|
result = client.query("SELECT @something;")
|
92
92
|
result.first['@something'].should eq('setting_value')
|
93
93
|
|
94
|
-
#
|
94
|
+
# get the current connection id
|
95
|
+
result = client.query("SELECT CONNECTION_ID()")
|
96
|
+
first_conn_id = result.first['CONNECTION_ID()']
|
97
|
+
|
98
|
+
# break the current connection
|
95
99
|
begin
|
96
|
-
|
97
|
-
|
98
|
-
end
|
99
|
-
rescue Timeout::Error
|
100
|
+
client.query("KILL #{first_conn_id}")
|
101
|
+
rescue Mysql2::Error
|
100
102
|
end
|
101
|
-
client.ping.should be_false
|
102
103
|
|
104
|
+
client.ping # reconnect now
|
105
|
+
|
106
|
+
# get the new connection id
|
107
|
+
result = client.query("SELECT CONNECTION_ID()")
|
108
|
+
second_conn_id = result.first['CONNECTION_ID()']
|
109
|
+
|
110
|
+
# confirm reconnect by checking the new connection id
|
111
|
+
first_conn_id.should_not == second_conn_id
|
112
|
+
|
113
|
+
# At last, check that the init command executed
|
103
114
|
result = client.query("SELECT @something;")
|
104
115
|
result.first['@something'].should eq('setting_value')
|
105
116
|
end
|
@@ -109,17 +120,20 @@ describe Mysql2::Client do
|
|
109
120
|
end
|
110
121
|
|
111
122
|
it "should be able to connect via SSL options" do
|
112
|
-
ssl = @client.query "SHOW VARIABLES LIKE '
|
113
|
-
|
114
|
-
pending("DON'T WORRY, THIS TEST PASSES - but SSL is not
|
115
|
-
|
123
|
+
ssl = @client.query "SHOW VARIABLES LIKE 'have_ssl'"
|
124
|
+
ssl_uncompiled = ssl.any? {|x| x['Value'] == 'OFF'}
|
125
|
+
pending("DON'T WORRY, THIS TEST PASSES - but SSL is not compiled into your MySQL daemon.") if ssl_uncompiled
|
126
|
+
ssl_disabled = ssl.any? {|x| x['Value'] == 'DISABLED'}
|
127
|
+
pending("DON'T WORRY, THIS TEST PASSES - but SSL is not enabled in your MySQL daemon.") if ssl_disabled
|
128
|
+
|
129
|
+
# You may need to adjust the lines below to match your SSL certificate paths
|
116
130
|
ssl_client = nil
|
117
131
|
lambda {
|
118
132
|
ssl_client = Mysql2::Client.new(
|
119
|
-
:sslkey
|
120
|
-
:sslcert
|
121
|
-
:sslca
|
122
|
-
:sslcapath => '/
|
133
|
+
:sslkey => '/etc/mysql/client-key.pem',
|
134
|
+
:sslcert => '/etc/mysql/client-cert.pem',
|
135
|
+
:sslca => '/etc/mysql/ca-cert.pem',
|
136
|
+
:sslcapath => '/etc/mysql/',
|
123
137
|
:sslcipher => 'DHE-RSA-AES256-SHA'
|
124
138
|
)
|
125
139
|
}.should_not raise_error(Mysql2::Error)
|
data/spec/mysql2/result_spec.rb
CHANGED
@@ -335,6 +335,16 @@ describe Mysql2::Result do
|
|
335
335
|
@test_result['enum_test'].should eql('val1')
|
336
336
|
end
|
337
337
|
|
338
|
+
it "should raise an error given an invalid DATETIME" do
|
339
|
+
begin
|
340
|
+
@client.query("SELECT CAST('1972-00-27 00:00:00' AS DATETIME) as bad_datetime").each
|
341
|
+
rescue Mysql2::Error => e
|
342
|
+
error = e
|
343
|
+
end
|
344
|
+
|
345
|
+
error.message.should eql("Invalid date in field 'bad_datetime': 1972-00-27 00:00:00")
|
346
|
+
end
|
347
|
+
|
338
348
|
if defined? Encoding
|
339
349
|
context "string encoding for ENUM values" do
|
340
350
|
it "should default to the connection's encoding if Encoding.default_internal is nil" do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mysql2
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.24
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian Lopez
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-11-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: eventmachine
|