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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4fcf19fa554da3669deb070a7089eac9c4b1dc22
4
- data.tar.gz: c8616ad6f03b717803b3dbe57a4d51008a62d770
3
+ metadata.gz: 53c6d16a0c12323bd8da93f743e94a27236d37f1
4
+ data.tar.gz: 278f215f56357c0c606359dd5565c4a9ec1bdc2a
5
5
  SHA512:
6
- metadata.gz: eb991cb7b45721f0904cdaf1fe996ab8d0c3cf7e12369e582c26af2076fdfd819c5e7e9db6bd2de711577f8be332de731fe94ddf3e1bd9e66bbfc410bf82acd1
7
- data.tar.gz: 29dd571c4d28ed12fb2bcedff13862b4610e80ec785f28cda72fbe2a68976f74d67c6ef10c7bec9a8393dd6fb8510f3cafa368a8bfbc2d5f248d5e9c4dbe64e4
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 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.2.23"
2
+ VERSION = "0.2.24"
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.2.23
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-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