mysql2 0.3.16 → 0.3.17

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 24297b49b4e7641f01f0b35438a160e8667c6b3b
4
- data.tar.gz: ccea57bd6d82f744ebb04420791297bcb7a0bfa9
3
+ metadata.gz: afc3856c2a464bb4e2ef5420b2be66713ab2c35a
4
+ data.tar.gz: 5cadd6917753f4df96c088f511d808df403a3035
5
5
  SHA512:
6
- metadata.gz: 0a5c9e85d85d0bbc945b5c723cf437f2ad171a1f6b086f7e92fc18d43cae1a92c1417ec0e3e027c33fc0d78ac79ba4738a7415df5d6776fbe0720e9814976b95
7
- data.tar.gz: bb180191c3ffa181a66e1fc801545374bca55dd2512f63e892a78b57ed6f9cd5862fa0213a44349d94b46cd29619305037e16433126aed0241049303d9bd614e
6
+ metadata.gz: 21707183394033e269afe2b5eb73b7aaca6467448f99733518ac9814e6b48beeada2cd60437f4fac0a0a4204bed98793fcf48a129da134c69add0e72113064d4
7
+ data.tar.gz: 90d6a8411a5c5d908bc037b3307991e2a6e6a5ce306709fdd63d3ad081433ed8f7af0eef5d4039205295962cd7b3b93d950ddeeda0bdd44b35e23683934622b8
data/README.md CHANGED
@@ -1,16 +1,16 @@
1
- # Mysql2 - A modern, simple and very fast Mysql library for Ruby - binding to libmysql
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 API's available.
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 `msyql_config` (default for the majority of users).
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
- # Here's an otter: http://farm1.static.flickr.com/130/398077070_b8795d0ef3_b.jpg
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
@@ -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 a dummy socket for mysql_close to
171
- * write, shutdown, and close on as a no-op.
172
- * We do this hack because we want to call mysql_close to release
173
- * memory, but do not want mysql_close to drop connections in the
174
- * parent if the socket got shared in fork.
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 = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
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 = socket(AF_UNIX, SOCK_STREAM, 0);
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 = socket(AF_UNIX, SOCK_STREAM, 0);
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
- start_time = end_time;
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
- elapsed_time--;
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 = NUM2INT(value);
773
+ intval = NUM2UINT(value);
768
774
  retval = &intval;
769
775
  break;
770
776
 
771
777
  case MYSQL_OPT_READ_TIMEOUT:
772
- intval = NUM2INT(value);
778
+ intval = NUM2UINT(value);
773
779
  retval = &intval;
774
780
  break;
775
781
 
776
782
  case MYSQL_OPT_WRITE_TIMEOUT:
777
- intval = NUM2INT(value);
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
@@ -41,7 +41,7 @@ typedef struct {
41
41
  VALUE active_thread; /* rb_thread_current() or Qnil */
42
42
  long server_version;
43
43
  int reconnect_enabled;
44
- int connect_timeout;
44
+ unsigned int connect_timeout;
45
45
  int active;
46
46
  int connected;
47
47
  int initialized;
@@ -1,7 +1,9 @@
1
1
  #include <mysql2_ext.h>
2
2
 
3
3
  #include <errno.h>
4
+ #ifndef _MSC_VER
4
5
  #include <unistd.h>
6
+ #endif
5
7
  #include <fcntl.h>
6
8
 
7
9
  #define ERROR_LEN 1024
@@ -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
- tokens = sscanf(row[i], "%2u:%2u:%2u", &hour, &min, &sec);
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
- val = rb_funcall(rb_cTime, db_timezone, 6, opt_time_year, opt_time_month, opt_time_month, UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec));
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.%6u", &year, &month, &day, &hour, &min, &sec, &msec);
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 { /* use Time, supports microseconds */
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));
@@ -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, :sql_state
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
- if "".respond_to? :encode
22
- def sql_state=(state)
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
@@ -1,3 +1,3 @@
1
1
  module Mysql2
2
- VERSION = "0.3.16"
2
+ VERSION = "0.3.17"
3
3
  end
@@ -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
- # simulate a broken connection
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
- Timeout.timeout(1, Timeout::Error) do
97
- client.query("SELECT sleep(2)")
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 'have_%ssl'"
113
- ssl_enabled = ssl.any? {|x| x['Value'] == 'ENABLED'}
114
- pending("DON'T WORRY, THIS TEST PASSES - but SSL is not enabled in your MySQL daemon.") unless ssl_enabled
115
- pending("DON'T WORRY, THIS TEST PASSES - but you must update the SSL cert paths in this test and remove this pending state.")
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 => '/path/to/client-key.pem',
120
- :sslcert => '/path/to/client-cert.pem',
121
- :sslca => '/path/to/ca-cert.pem',
122
- :sslcapath => '/path/to/newcerts/',
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)
@@ -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.3.16
4
+ version: 0.3.17
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-05-15 00:00:00.000000000 Z
11
+ date: 2014-11-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: eventmachine