mysql2 0.2.23 → 0.2.24
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 +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
|
[](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
|