mysql2 0.3.14 → 0.3.15

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: c05c6f7a1476667b0e6f1f8b94288a70e82cad15
4
- data.tar.gz: b28cb5dcccf46727e60e0d30971a8e25183c4df2
3
+ metadata.gz: 1895b2fe6d621bc0608c1c16c7dde5872aff9af9
4
+ data.tar.gz: 4757365df13127c09c35c3b2cbf84ea35c9ab872
5
5
  SHA512:
6
- metadata.gz: fa54239e2f7a16ecd4bd3481274f71619c97f61e607245e17aa091e654dc2334cb89b4d604999156aa7d7cb6905f5342aed97c99a5caca1fb32773ca22e7e01d
7
- data.tar.gz: 65d9be8d59967b977b09f95321a00e67fbfb0822da39a57a1dfb18164cd109d7a4f966174800fbc0e1d864727b7ba463deee2a123c4f9cbbf2cd05508b93b9e0
6
+ metadata.gz: 9feaf2446a8f5c72594fb3958d2675b94b64849f1a8aa0054a7d2fd31191ac82566ae5d008d7e3cc84af329bf009461010e3e8617919abefeede90567ea6f571
7
+ data.tar.gz: 114eb344643a6ec32cf8624e268551334776eb0196105888dfff3a21f42b4889123a00a32f5692015c1ab1638b02b565c7a20a14a6df3d8371f471d9ad0ba702
data/README.md CHANGED
@@ -66,7 +66,7 @@ files are. For example, if you unzipped the connector to c:\mysql-connector-c-6.
66
66
  the gem like this:
67
67
 
68
68
  gem install mysql2 -- --with-mysql-dir=c:\mysql-connector-c-6.1.1-win32
69
-
69
+
70
70
  Finally, you must copy libmysql.dll from the lib subdirectory of your MySQL or MySQL connector directory into
71
71
  your ruby\bin directory. In the above example, libmysql.dll would be located at
72
72
  c:\mysql-connector-c-6.1.1-win32\lib .
@@ -160,22 +160,67 @@ Mysql2::Client.new(
160
160
  :default_group = 'my.cfg section'
161
161
  )
162
162
  ```
163
+
164
+ ### SSL options
165
+
166
+ Setting any of the following options will enable an SSL connection, but only if
167
+ your MySQL client library and server have been compiled with SSL support.
168
+ MySQL client library defaults will be used for any parameters that are left out
169
+ or set to nil. Relative paths are allowed, and may be required by managed
170
+ hosting providers such as Heroku.
171
+
172
+ ``` ruby
173
+ Mysql2::Client.new(
174
+ # ...options as above...,
175
+ :sslkey => '/path/to/client-key.pem',
176
+ :sslcert => '/path/to/client-cert.pem',
177
+ :sslca => '/path/to/ca-cert.pem',
178
+ :sslcapath => '/path/to/cacerts',
179
+ :sslcipher => 'DHE-RSA-AES256-SHA'
180
+ )
181
+ ```
182
+
163
183
  ### Multiple result sets
164
184
 
165
- You can also retrieve multiple result sets. For this to work you need to connect with
166
- flags `Mysql2::Client::MULTI_STATEMENTS`. Using multiple result sets is normally used
167
- when calling stored procedures that return more than one result set
185
+ You can also retrieve multiple result sets. For this to work you need to
186
+ connect with flags `Mysql2::Client::MULTI_STATEMENTS`. Multiple result sets can
187
+ be used with stored procedures that return more than one result set, and for
188
+ bundling several SQL statements into a single call to `client.query`.
168
189
 
169
190
  ``` ruby
170
- client = Mysql2::Client.new(:host => "localhost", :username => "root", :flags => Mysql2::Client::MULTI_STATEMENTS )
171
- result = client.query( 'CALL sp_customer_list( 25, 10 )')
191
+ client = Mysql2::Client.new(:host => "localhost", :username => "root", :flags => Mysql2::Client::MULTI_STATEMENTS)
192
+ result = client.query('CALL sp_customer_list( 25, 10 )')
172
193
  # result now contains the first result set
173
- while ( client.next_result)
174
- result = client.store_result
175
- # result now contains the next result set
194
+ while client.next_result
195
+ result = client.store_result
196
+ # result now contains the next result set
176
197
  end
177
198
  ```
178
199
 
200
+ Repeated calls to `client.next_result` will return true, false, or raise an
201
+ exception if the respective query erred. When `client.next_result` returns true,
202
+ call `client.store_result` to retrieve a result object. Exceptions are not
203
+ raised until `client.next_result` is called to find the status of the respective
204
+ query. Subsequent queries are not executed if an earlier query raised an
205
+ exception. Subsequent calls to `client.next_result` will return false.
206
+
207
+ ``` ruby
208
+ result = client.query('SELECT 1; SELECT 2; SELECT A; SELECT 3')
209
+ p result.first
210
+
211
+ while client.next_result
212
+ result = client.store_result
213
+ p result.first
214
+ end
215
+ ```
216
+
217
+ Yields:
218
+ ```
219
+ {"1"=>1}
220
+ {"2"=>2}
221
+ next_result: Unknown column 'A' in 'field list' (Mysql2::Error)
222
+ ```
223
+
179
224
  See https://gist.github.com/1367987 for using MULTI_STATEMENTS with Active Record.
180
225
 
181
226
  ### Secure auth
@@ -186,7 +231,7 @@ The MySQL 5.6.5 client library may also refuse to attempt a connection if provid
186
231
  To bypass this restriction in the client, pass the option :secure_auth => false to Mysql2::Client.new().
187
232
  If using ActiveRecord, your database.yml might look something like this:
188
233
 
189
- ```
234
+ ``` yaml
190
235
  development:
191
236
  adapter: mysql2
192
237
  encoding: utf8
@@ -199,11 +244,12 @@ development:
199
244
  ```
200
245
 
201
246
  ### Reading a MySQL config file
247
+
202
248
  You may read configuration options from a MySQL configuration file by passing
203
249
  the `:default_file` and `:default_group` paramters. For example:
204
250
 
205
- ```
206
- client = Mysql2::Client.new(:default_file => '/user/.my.cnf', :default_group => 'client')
251
+ ``` ruby
252
+ Mysql2::Client.new(:default_file => '/user/.my.cnf', :default_group => 'client')
207
253
  ```
208
254
 
209
255
 
@@ -254,11 +300,6 @@ Pass the `:as => :array` option to any of the above methods of configuration
254
300
 
255
301
  The default result type is set to :hash, but you can override a previous setting to something else with :as => :hash
256
302
 
257
- ### Others...
258
-
259
- I may add support for `:as => :csv` or even `:as => :json` to allow for *much* more efficient generation of those data types from result sets.
260
- If you'd like to see either of these (or others), open an issue and start bugging me about it ;)
261
-
262
303
  ### Timezones
263
304
 
264
305
  Mysql2 now supports two timezone options:
@@ -405,15 +446,15 @@ As for field values themselves, I'm workin on it - but expect that soon.
405
446
 
406
447
  ## Compatibility
407
448
 
408
- The specs pass on my system (SL 10.6.3, x86_64) in these rubies:
449
+ This gem is regularly tested against the following Ruby versions on Linux and Mac OS X:
409
450
 
410
- * 1.8.7-p249
411
- * ree-1.8.7-2010.01
412
- * 1.9.1-p378
413
- * ruby-trunk
414
- * rbx-head - broken at the moment, working with the rbx team for a solution
451
+ * Ruby MRI 1.8.7, 1.9.2, 1.9.3, 2.0.0 (ongoing patch releases).
452
+ * Ruby Enterprise Edition (based on MRI 1.8.7).
453
+ * Rubinius 2.0 in compatibility modes 1.8, 1.9, 2.0.
415
454
 
416
- The Active Record driver should work on 2.3.5 and 3.0
455
+ The mysql2 gem 0.2.x series includes an Active Record driver that works with AR
456
+ 2.3.x and 3.0.x. Starting in Active Record 3.1, a mysql2 driver is included in
457
+ the Active Record codebase and no longer provided in mysql2 gem 0.3 and above.
417
458
 
418
459
  ## Yeah... but why?
419
460
 
@@ -422,7 +463,6 @@ Someone: Dude, the Mysql gem works fiiiiiine.
422
463
  Me: It sure does, but it only hands you nil and strings for field values. Leaving you to convert
423
464
  them into proper Ruby types in Ruby-land - which is slow as balls.
424
465
 
425
-
426
466
  Someone: OK fine, but do_mysql can already give me back values with Ruby objects mapped to MySQL types.
427
467
 
428
468
  Me: Yep, but it's API is considerably more complex *and* can be ~2x slower.
@@ -1,5 +1,5 @@
1
1
  #include <mysql2_ext.h>
2
- #include <client.h>
2
+
3
3
  #include <errno.h>
4
4
  #ifndef _WIN32
5
5
  #include <sys/socket.h>
@@ -12,7 +12,7 @@
12
12
  VALUE cMysql2Client;
13
13
  extern VALUE mMysql2, cMysql2Error;
14
14
  static VALUE sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as, sym_array, sym_stream;
15
- static ID intern_merge, intern_merge_bang, intern_error_number_eql, intern_sql_state_eql;
15
+ static ID intern_merge, intern_merge_bang, intern_error_number_eql, intern_sql_state_eql, intern_server_version;
16
16
 
17
17
  #ifndef HAVE_RB_HASH_DUP
18
18
  static VALUE rb_hash_dup(VALUE other) {
@@ -125,19 +125,13 @@ static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) {
125
125
  VALUE rb_error_msg = rb_str_new2(mysql_error(wrapper->client));
126
126
  VALUE rb_sql_state = rb_tainted_str_new2(mysql_sqlstate(wrapper->client));
127
127
  VALUE e;
128
- #ifdef HAVE_RUBY_ENCODING_H
129
- rb_encoding *default_internal_enc = rb_default_internal_encoding();
130
- rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
131
128
 
132
- rb_enc_associate(rb_error_msg, conn_enc);
133
- rb_enc_associate(rb_sql_state, conn_enc);
134
- if (default_internal_enc) {
135
- rb_error_msg = rb_str_export_to_enc(rb_error_msg, default_internal_enc);
136
- rb_sql_state = rb_str_export_to_enc(rb_sql_state, default_internal_enc);
137
- }
129
+ #ifdef HAVE_RUBY_ENCODING_H
130
+ rb_enc_associate(rb_error_msg, rb_utf8_encoding());
131
+ rb_enc_associate(rb_sql_state, rb_usascii_encoding());
138
132
  #endif
139
133
 
140
- e = rb_exc_new3(cMysql2Error, rb_error_msg);
134
+ e = rb_funcall(cMysql2Error, rb_intern("new"), 2, rb_error_msg, LONG2FIX(wrapper->server_version));
141
135
  rb_funcall(e, intern_error_number_eql, 1, UINT2NUM(mysql_errno(wrapper->client)));
142
136
  rb_funcall(e, intern_sql_state_eql, 1, rb_sql_state);
143
137
  rb_exc_raise(e);
@@ -146,9 +140,13 @@ static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) {
146
140
 
147
141
  static void *nogvl_init(void *ptr) {
148
142
  MYSQL *client;
143
+ mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr;
149
144
 
150
145
  /* may initialize embedded server and read /etc/services off disk */
151
- client = mysql_init((MYSQL *)ptr);
146
+ client = mysql_init(wrapper->client);
147
+
148
+ if (client) mysql2_set_local_infile(client, wrapper);
149
+
152
150
  return (void*)(client ? Qtrue : Qfalse);
153
151
  }
154
152
 
@@ -196,7 +194,11 @@ static void *nogvl_close(void *ptr) {
196
194
 
197
195
  static void rb_mysql_client_free(void *ptr) {
198
196
  mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr;
197
+ decr_mysql2_client(wrapper);
198
+ }
199
199
 
200
+ void decr_mysql2_client(mysql_client_wrapper *wrapper)
201
+ {
200
202
  wrapper->refcount--;
201
203
  if (wrapper->refcount == 0) {
202
204
  nogvl_close(wrapper);
@@ -211,6 +213,7 @@ static VALUE allocate(VALUE klass) {
211
213
  obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper);
212
214
  wrapper->encoding = Qnil;
213
215
  wrapper->active_thread = Qnil;
216
+ wrapper->server_version = 0;
214
217
  wrapper->reconnect_enabled = 0;
215
218
  wrapper->connected = 0; /* means that a database connection is open */
216
219
  wrapper->initialized = 0; /* means that that the wrapper is initialized */
@@ -303,6 +306,7 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
303
306
  return rb_raise_mysql2_error(wrapper);
304
307
  }
305
308
 
309
+ wrapper->server_version = mysql_get_server_version(wrapper->client);
306
310
  wrapper->connected = 1;
307
311
  return self;
308
312
  }
@@ -1120,7 +1124,7 @@ static VALUE set_read_default_group(VALUE self, VALUE value) {
1120
1124
  static VALUE initialize_ext(VALUE self) {
1121
1125
  GET_CLIENT(self);
1122
1126
 
1123
- if ((VALUE)rb_thread_call_without_gvl(nogvl_init, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
1127
+ if ((VALUE)rb_thread_call_without_gvl(nogvl_init, wrapper, RUBY_UBF_IO, 0) == Qfalse) {
1124
1128
  /* TODO: warning - not enough memory? */
1125
1129
  return rb_raise_mysql2_error(wrapper);
1126
1130
  }
@@ -1135,7 +1139,7 @@ void init_mysql2_client() {
1135
1139
  int i;
1136
1140
  int dots = 0;
1137
1141
  const char *lib = mysql_get_client_info();
1138
-
1142
+
1139
1143
  for (i = 0; lib[i] != 0 && MYSQL_LINK_VERSION[i] != 0; i++) {
1140
1144
  if (lib[i] == '.') {
1141
1145
  dots++;
@@ -1204,6 +1208,7 @@ void init_mysql2_client() {
1204
1208
  intern_merge_bang = rb_intern("merge!");
1205
1209
  intern_error_number_eql = rb_intern("error_number=");
1206
1210
  intern_sql_state_eql = rb_intern("sql_state=");
1211
+ intern_server_version = rb_intern("server_version=");
1207
1212
 
1208
1213
  #ifdef CLIENT_LONG_PASSWORD
1209
1214
  rb_const_set(cMysql2Client, rb_intern("LONG_PASSWORD"),
@@ -36,11 +36,10 @@ rb_thread_call_without_gvl(
36
36
  #endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */
37
37
  #endif /* ! HAVE_RB_THREAD_CALL_WITHOUT_GVL */
38
38
 
39
- void init_mysql2_client();
40
-
41
39
  typedef struct {
42
40
  VALUE encoding;
43
41
  VALUE active_thread; /* rb_thread_current() or Qnil */
42
+ long server_version;
44
43
  int reconnect_enabled;
45
44
  int active;
46
45
  int connected;
@@ -50,4 +49,7 @@ typedef struct {
50
49
  MYSQL *client;
51
50
  } mysql_client_wrapper;
52
51
 
52
+ void init_mysql2_client();
53
+ void decr_mysql2_client(mysql_client_wrapper *wrapper);
54
+
53
55
  #endif
@@ -0,0 +1,119 @@
1
+ #include <mysql2_ext.h>
2
+
3
+ #include <errno.h>
4
+ #include <unistd.h>
5
+
6
+ #define ERROR_LEN 1024
7
+ typedef struct
8
+ {
9
+ int fd;
10
+ char *filename;
11
+ char error[ERROR_LEN];
12
+ mysql_client_wrapper *wrapper;
13
+ } mysql2_local_infile_data;
14
+
15
+ /* MySQL calls this function when a user begins a LOAD DATA LOCAL INFILE query.
16
+ *
17
+ * Allocate a data struct and pass it back through the data pointer.
18
+ *
19
+ * Returns:
20
+ * 0 on success
21
+ * 1 on error
22
+ */
23
+ static int
24
+ mysql2_local_infile_init(void **ptr, const char *filename, void *userdata)
25
+ {
26
+ mysql2_local_infile_data *data = malloc(sizeof(mysql2_local_infile_data));
27
+ if (!data) return 1;
28
+
29
+ *ptr = data;
30
+ data->error[0] = 0;
31
+ data->wrapper = userdata;
32
+
33
+ data->filename = strdup(filename);
34
+ if (!data->filename) {
35
+ snprintf(data->error, ERROR_LEN, "%s: %s", strerror(errno), filename);
36
+ return 1;
37
+ }
38
+
39
+ data->fd = open(filename, O_RDONLY);
40
+ if (data->fd < 0) {
41
+ snprintf(data->error, ERROR_LEN, "%s: %s", strerror(errno), filename);
42
+ return 1;
43
+ }
44
+
45
+ return 0;
46
+ }
47
+
48
+ /* MySQL calls this function to read data from the local file.
49
+ *
50
+ * Returns:
51
+ * > 0 number of bytes read
52
+ * == 0 end of file
53
+ * < 0 error
54
+ */
55
+ static int
56
+ mysql2_local_infile_read(void *ptr, char *buf, uint buf_len)
57
+ {
58
+ int count;
59
+ mysql2_local_infile_data *data = (mysql2_local_infile_data *)ptr;
60
+
61
+ count = (int)read(data->fd, buf, buf_len);
62
+ if (count < 0) {
63
+ snprintf(data->error, ERROR_LEN, "%s: %s", strerror(errno), data->filename);
64
+ }
65
+
66
+ return count;
67
+ }
68
+
69
+ /* MySQL calls this function when we're done with the LOCAL INFILE query.
70
+ *
71
+ * ptr will be null if the init function failed.
72
+ */
73
+ static void
74
+ mysql2_local_infile_end(void *ptr)
75
+ {
76
+ mysql2_local_infile_data *data = (mysql2_local_infile_data *)ptr;
77
+ if (data) {
78
+ if (data->fd >= 0)
79
+ close(data->fd);
80
+ if (data->filename)
81
+ free(data->filename);
82
+ free(data);
83
+ }
84
+ }
85
+
86
+ /* MySQL calls this function if any of the functions above returned an error.
87
+ *
88
+ * This function is called even if init failed, with whatever ptr value
89
+ * init has set, regardless of the return value of the init function.
90
+ *
91
+ * Returns:
92
+ * Error message number (see http://dev.mysql.com/doc/refman/5.0/en/error-messages-client.html)
93
+ */
94
+ static int
95
+ mysql2_local_infile_error(void *ptr, char *error_msg, uint error_msg_len)
96
+ {
97
+ mysql2_local_infile_data *data = (mysql2_local_infile_data *) ptr;
98
+
99
+ if (data) {
100
+ snprintf(error_msg, error_msg_len, "%s", data->error);
101
+ return CR_UNKNOWN_ERROR;
102
+ }
103
+
104
+ snprintf(error_msg, error_msg_len, "Out of memory");
105
+ return CR_OUT_OF_MEMORY;
106
+ }
107
+
108
+ /* Tell MySQL Client to use our own local_infile functions.
109
+ * This is both due to bugginess in the default handlers,
110
+ * and to improve the Rubyness of the handlers here.
111
+ */
112
+ void mysql2_set_local_infile(MYSQL *mysql, void *userdata)
113
+ {
114
+ mysql_set_local_infile_handler(mysql,
115
+ mysql2_local_infile_init,
116
+ mysql2_local_infile_read,
117
+ mysql2_local_infile_end,
118
+ mysql2_local_infile_error, userdata);
119
+ }
@@ -0,0 +1 @@
1
+ void mysql2_set_local_infile(MYSQL *mysql, void *userdata);
@@ -41,5 +41,6 @@ typedef unsigned int uint;
41
41
 
42
42
  #include <client.h>
43
43
  #include <result.h>
44
+ #include <infile.h>
44
45
 
45
46
  #endif
@@ -1,4 +1,5 @@
1
1
  #include <mysql2_ext.h>
2
+
2
3
  #include <stdint.h>
3
4
 
4
5
  #include "mysql_enc_to_ruby.h"
@@ -84,11 +85,7 @@ static void rb_mysql_result_free(void *ptr) {
84
85
 
85
86
  // If the GC gets to client first it will be nil
86
87
  if (wrapper->client != Qnil) {
87
- wrapper->client_wrapper->refcount--;
88
- if (wrapper->client_wrapper->refcount == 0) {
89
- xfree(wrapper->client_wrapper->client);
90
- xfree(wrapper->client_wrapper);
91
- }
88
+ decr_mysql2_client(wrapper->client_wrapper);
92
89
  }
93
90
 
94
91
  xfree(wrapper);
@@ -125,16 +122,12 @@ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int
125
122
 
126
123
  field = mysql_fetch_field_direct(wrapper->result, idx);
127
124
  if (symbolize_keys) {
128
- char buf[field->name_length+1];
129
- memcpy(buf, field->name, field->name_length);
130
- buf[field->name_length] = 0;
131
-
132
125
  #ifdef HAVE_RB_INTERN3
133
- rb_field = rb_intern3(buf, field->name_length, rb_utf8_encoding());
126
+ rb_field = rb_intern3(field->name, field->name_length, rb_utf8_encoding());
134
127
  rb_field = ID2SYM(rb_field);
135
128
  #else
136
129
  VALUE colStr;
137
- colStr = rb_str_new2(buf);
130
+ colStr = rb_str_new(field->name, field->name_length);
138
131
  rb_field = ID2SYM(rb_to_id(colStr));
139
132
  #endif
140
133
  } else {
@@ -433,6 +426,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
433
426
  ID db_timezone, app_timezone, dbTz, appTz;
434
427
  mysql2_result_wrapper * wrapper;
435
428
  unsigned long i;
429
+ const char * errstr;
436
430
  int symbolizeKeys = 0, asArray = 0, castBool = 0, cacheRows = 1, cast = 1, streaming = 0;
437
431
  MYSQL_FIELD * fields = NULL;
438
432
 
@@ -496,7 +490,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
496
490
  }
497
491
 
498
492
  if (wrapper->lastRowProcessed == 0) {
499
- if(streaming) {
493
+ if (streaming) {
500
494
  /* We can't get number of rows if we're streaming, */
501
495
  /* until we've finished fetching all rows */
502
496
  wrapper->numberOfRows = 0;
@@ -512,7 +506,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
512
506
  }
513
507
 
514
508
  if (streaming) {
515
- if(!wrapper->streamingComplete) {
509
+ if (!wrapper->streamingComplete) {
516
510
  VALUE row;
517
511
 
518
512
  fields = mysql_fetch_fields(wrapper->result);
@@ -530,6 +524,13 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
530
524
 
531
525
  wrapper->numberOfRows = wrapper->lastRowProcessed;
532
526
  wrapper->streamingComplete = 1;
527
+
528
+ // Check for errors, the connection might have gone out from under us
529
+ // mysql_error returns an empty string if there is no error
530
+ errstr = mysql_error(wrapper->client_wrapper->client);
531
+ if (errstr[0]) {
532
+ rb_raise(cMysql2Error, "%s", errstr);
533
+ }
533
534
  } else {
534
535
  rb_raise(cMysql2Error, "You have already fetched all the rows for this query and streaming is true. (to reiterate you must requery).");
535
536
  }