mysql2 0.2.3 → 0.2.4
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.
- data/CHANGELOG.md +9 -0
- data/README.rdoc +16 -6
- data/VERSION +1 -1
- data/benchmark/query_with_mysql_casting.rb +1 -1
- data/ext/mysql2/client.c +74 -69
- data/ext/mysql2/client.h +2 -1
- data/ext/mysql2/result.c +14 -7
- data/ext/mysql2/result.h +1 -1
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -1
- data/lib/mysql2.rb +1 -1
- data/lib/mysql2/client.rb +7 -6
- data/lib/mysql2/error.rb +4 -0
- data/mysql2.gemspec +2 -2
- data/spec/mysql2/client_spec.rb +36 -22
- data/spec/mysql2/error_spec.rb +9 -0
- data/spec/mysql2/result_spec.rb +7 -2
- data/tasks/compile.rake +16 -3
- metadata +4 -4
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.4 (September 17th, 2010)
|
|
4
|
+
* a few patches for win32 support from Luis Lavena - thanks man!
|
|
5
|
+
* bugfix from Eric Wong to avoid a potential stack overflow during Mysql2::Client#escape
|
|
6
|
+
* added the ability to turn internal row caching on/off via the :cache_rows => true/false option
|
|
7
|
+
* a couple of small patches for rbx compatibility
|
|
8
|
+
* set IndexDefinition#length in AR adapter - Kouhei Yanagita <yanagi@shakenbu.org>
|
|
9
|
+
* fix a long-standing data corruption bug - thank you thank you thank you to @joedamato (http://github.com/ice799)
|
|
10
|
+
* bugfix from calling mysql_close on a closed/freed connection surfaced by the above fix
|
|
11
|
+
|
|
3
12
|
## 0.2.3 (August 20th, 2010)
|
|
4
13
|
* connection flags can now be passed to the constructor via the :flags key
|
|
5
14
|
* switch AR adapter connection over to use FOUND_ROWS option
|
data/README.rdoc
CHANGED
|
@@ -89,18 +89,18 @@ or
|
|
|
89
89
|
|
|
90
90
|
=== Array of Arrays
|
|
91
91
|
|
|
92
|
-
Pass the
|
|
92
|
+
Pass the :as => :array option to any of the above methods of configuration
|
|
93
93
|
|
|
94
94
|
=== Array of Hashes
|
|
95
95
|
|
|
96
|
-
The default result type is set to :hash, but you can override a previous setting to something else with
|
|
96
|
+
The default result type is set to :hash, but you can override a previous setting to something else with :as => :hash
|
|
97
97
|
|
|
98
98
|
=== Others...
|
|
99
99
|
|
|
100
|
-
I may add support for
|
|
100
|
+
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.
|
|
101
101
|
If you'd like to see either of these (or others), open an issue and start bugging me about it ;)
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
=== Timezones
|
|
104
104
|
|
|
105
105
|
Mysql2 now supports two timezone options:
|
|
106
106
|
|
|
@@ -112,14 +112,14 @@ Then, if :application_timezone is set to say - :local - Mysql2 will then convert
|
|
|
112
112
|
|
|
113
113
|
Both options only allow two values - :local or :utc - with the exception that :application_timezone can be [and defaults to] nil
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
=== Casting "boolean" columns
|
|
116
116
|
|
|
117
117
|
You can now tell Mysql2 to cast tinyint(1) fields to boolean values in Ruby with the :cast_booleans option.
|
|
118
118
|
|
|
119
119
|
client = Mysql2::Client.new
|
|
120
120
|
result = client.query("SELECT * FROM table_with_boolean_field", :cast_booleans => true)
|
|
121
121
|
|
|
122
|
-
|
|
122
|
+
=== Async
|
|
123
123
|
|
|
124
124
|
Mysql2::Client takes advantage of the MySQL C API's (undocumented) non-blocking function mysql_send_query for *all* queries.
|
|
125
125
|
But, in order to take full advantage of it in your Ruby code, you can do:
|
|
@@ -136,6 +136,14 @@ NOTE: Because of the way MySQL's query API works, this method will block until t
|
|
|
136
136
|
So if you really need things to stay async, it's best to just monitor the socket with something like EventMachine.
|
|
137
137
|
If you need multiple query concurrency take a look at using a connection pool.
|
|
138
138
|
|
|
139
|
+
=== Row Caching
|
|
140
|
+
|
|
141
|
+
By default, Mysql2 will cache rows that have been created in Ruby (since this happens lazily).
|
|
142
|
+
This is especially helpful since it saves the cost of creating the row in Ruby if you were to iterate over the collection again.
|
|
143
|
+
|
|
144
|
+
If you only plan on using each row once, then it's much more efficient to disable this behavior by setting the :cache_rows option to false.
|
|
145
|
+
This would be helpful if you wanted to iterate over the results in a streaming manner. Meaning the GC would cleanup rows you don't need anymore as you're iterating over the result set.
|
|
146
|
+
|
|
139
147
|
== ActiveRecord
|
|
140
148
|
|
|
141
149
|
To use the ActiveRecord driver, all you should need to do is have this gem installed and set the adapter in your database.yml to "mysql2".
|
|
@@ -182,6 +190,8 @@ For example, if you were to yield 4 rows from a 100 row dataset, only 4 hashes w
|
|
|
182
190
|
Now say you were to iterate over that same collection again, this time yielding 15 rows - the 4 previous rows that had already been turned into ruby hashes would be pulled from an internal cache, then 11 more would be created and stored in that cache.
|
|
183
191
|
Once the entire dataset has been converted into ruby objects, Mysql2::Result will free the Mysql C result object as it's no longer needed.
|
|
184
192
|
|
|
193
|
+
This caching behavior can be disabled by setting the :cache_rows option to false.
|
|
194
|
+
|
|
185
195
|
As for field values themselves, I'm workin on it - but expect that soon.
|
|
186
196
|
|
|
187
197
|
== Compatibility
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.2.
|
|
1
|
+
0.2.4
|
|
@@ -70,7 +70,7 @@ Benchmark.bmbm do |x|
|
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
do_mysql = DataObjects::Connection.new("mysql://localhost/#{database}")
|
|
73
|
-
command =
|
|
73
|
+
command = do_mysql.create_command sql
|
|
74
74
|
x.report do
|
|
75
75
|
puts "do_mysql"
|
|
76
76
|
number_of.times do
|
data/ext/mysql2/client.c
CHANGED
|
@@ -8,20 +8,18 @@ static VALUE intern_encoding_from_charset;
|
|
|
8
8
|
static ID sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as, sym_array;
|
|
9
9
|
static ID intern_merge, intern_error_number_eql, intern_sql_state_eql;
|
|
10
10
|
|
|
11
|
-
#define REQUIRE_OPEN_DB(
|
|
12
|
-
if(!
|
|
11
|
+
#define REQUIRE_OPEN_DB(wrapper) \
|
|
12
|
+
if(wrapper->closed || !wrapper->client->net.vio) { \
|
|
13
13
|
rb_raise(cMysql2Error, "closed MySQL connection"); \
|
|
14
14
|
return Qnil; \
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
#define MARK_CONN_INACTIVE(conn) \
|
|
18
|
-
wrapper->active = 0
|
|
18
|
+
wrapper->active = 0
|
|
19
19
|
|
|
20
20
|
#define GET_CLIENT(self) \
|
|
21
21
|
mysql_client_wrapper *wrapper; \
|
|
22
|
-
|
|
23
|
-
Data_Get_Struct(self, mysql_client_wrapper, wrapper); \
|
|
24
|
-
client = &wrapper->client;
|
|
22
|
+
Data_Get_Struct(self, mysql_client_wrapper, wrapper)
|
|
25
23
|
|
|
26
24
|
/*
|
|
27
25
|
* used to pass all arguments to mysql_real_connect while inside
|
|
@@ -85,12 +83,11 @@ static VALUE rb_raise_mysql2_error(MYSQL *client) {
|
|
|
85
83
|
}
|
|
86
84
|
|
|
87
85
|
static VALUE nogvl_init(void *ptr) {
|
|
88
|
-
MYSQL
|
|
86
|
+
MYSQL **client = (MYSQL **)ptr;
|
|
89
87
|
|
|
90
88
|
/* may initialize embedded server and read /etc/services off disk */
|
|
91
|
-
client = mysql_init(NULL);
|
|
92
|
-
|
|
93
|
-
return client ? Qtrue : Qfalse;
|
|
89
|
+
*client = mysql_init(NULL);
|
|
90
|
+
return *client ? Qtrue : Qfalse;
|
|
94
91
|
}
|
|
95
92
|
|
|
96
93
|
static VALUE nogvl_connect(void *ptr) {
|
|
@@ -108,36 +105,43 @@ static VALUE nogvl_connect(void *ptr) {
|
|
|
108
105
|
}
|
|
109
106
|
|
|
110
107
|
static void rb_mysql_client_free(void * ptr) {
|
|
111
|
-
mysql_client_wrapper *
|
|
112
|
-
MYSQL * client = &wrapper->client;
|
|
108
|
+
mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr;
|
|
113
109
|
|
|
114
110
|
/*
|
|
115
111
|
* we'll send a QUIT message to the server, but that message is more of a
|
|
116
112
|
* formality than a hard requirement since the socket is getting shutdown
|
|
117
113
|
* anyways, so ensure the socket write does not block our interpreter
|
|
118
114
|
*/
|
|
119
|
-
int fd = client->net.fd;
|
|
120
|
-
int flags;
|
|
115
|
+
int fd = wrapper->client->net.fd;
|
|
121
116
|
|
|
122
117
|
if (fd >= 0) {
|
|
123
118
|
/*
|
|
124
119
|
* if the socket is dead we have no chance of blocking,
|
|
125
120
|
* so ignore any potential fcntl errors since they don't matter
|
|
126
121
|
*/
|
|
127
|
-
|
|
122
|
+
#ifndef _WIN32
|
|
123
|
+
int flags = fcntl(fd, F_GETFL);
|
|
128
124
|
if (flags > 0 && !(flags & O_NONBLOCK))
|
|
129
125
|
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
|
126
|
+
#else
|
|
127
|
+
u_long iMode = 1;
|
|
128
|
+
ioctlsocket(fd, FIONBIO, &iMode);
|
|
129
|
+
#endif
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
/* It's safe to call mysql_close() on an already closed connection. */
|
|
133
|
-
|
|
133
|
+
if (!wrapper->closed) {
|
|
134
|
+
mysql_close(wrapper->client);
|
|
135
|
+
}
|
|
134
136
|
xfree(ptr);
|
|
135
137
|
}
|
|
136
138
|
|
|
137
139
|
static VALUE nogvl_close(void * ptr) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
140
|
+
mysql_client_wrapper *wrapper = ptr;
|
|
141
|
+
if (!wrapper->closed) {
|
|
142
|
+
mysql_close(wrapper->client);
|
|
143
|
+
wrapper->closed = 1;
|
|
144
|
+
}
|
|
141
145
|
return Qnil;
|
|
142
146
|
}
|
|
143
147
|
|
|
@@ -147,12 +151,13 @@ static VALUE allocate(VALUE klass) {
|
|
|
147
151
|
obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper);
|
|
148
152
|
wrapper->encoding = Qnil;
|
|
149
153
|
wrapper->active = 0;
|
|
154
|
+
wrapper->closed = 0;
|
|
150
155
|
return obj;
|
|
151
156
|
}
|
|
152
157
|
|
|
153
158
|
static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags) {
|
|
154
159
|
struct nogvl_connect_args args;
|
|
155
|
-
GET_CLIENT(self)
|
|
160
|
+
GET_CLIENT(self);
|
|
156
161
|
|
|
157
162
|
args.host = NIL_P(host) ? "localhost" : StringValuePtr(host);
|
|
158
163
|
args.unix_socket = NIL_P(socket) ? NULL : StringValuePtr(socket);
|
|
@@ -160,12 +165,12 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
|
|
|
160
165
|
args.user = NIL_P(user) ? NULL : StringValuePtr(user);
|
|
161
166
|
args.passwd = NIL_P(pass) ? NULL : StringValuePtr(pass);
|
|
162
167
|
args.db = NIL_P(database) ? NULL : StringValuePtr(database);
|
|
163
|
-
args.mysql = client;
|
|
168
|
+
args.mysql = wrapper->client;
|
|
164
169
|
args.client_flag = NUM2INT(flags);
|
|
165
170
|
|
|
166
171
|
if (rb_thread_blocking_region(nogvl_connect, &args, RUBY_UBF_IO, 0) == Qfalse) {
|
|
167
172
|
// unable to connect
|
|
168
|
-
return rb_raise_mysql2_error(client);
|
|
173
|
+
return rb_raise_mysql2_error(wrapper->client);
|
|
169
174
|
}
|
|
170
175
|
|
|
171
176
|
return self;
|
|
@@ -178,9 +183,9 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
|
|
|
178
183
|
* for the garbage collector.
|
|
179
184
|
*/
|
|
180
185
|
static VALUE rb_mysql_client_close(VALUE self) {
|
|
181
|
-
GET_CLIENT(self)
|
|
186
|
+
GET_CLIENT(self);
|
|
182
187
|
|
|
183
|
-
rb_thread_blocking_region(nogvl_close,
|
|
188
|
+
rb_thread_blocking_region(nogvl_close, wrapper, RUBY_UBF_IO, 0);
|
|
184
189
|
|
|
185
190
|
return Qnil;
|
|
186
191
|
}
|
|
@@ -221,30 +226,30 @@ static VALUE nogvl_store_result(void *ptr) {
|
|
|
221
226
|
|
|
222
227
|
static VALUE rb_mysql_client_async_result(VALUE self) {
|
|
223
228
|
MYSQL_RES * result;
|
|
224
|
-
GET_CLIENT(self)
|
|
229
|
+
GET_CLIENT(self);
|
|
225
230
|
|
|
226
|
-
REQUIRE_OPEN_DB(
|
|
227
|
-
if (rb_thread_blocking_region(nogvl_read_query_result, client, RUBY_UBF_IO, 0) == Qfalse) {
|
|
231
|
+
REQUIRE_OPEN_DB(wrapper);
|
|
232
|
+
if (rb_thread_blocking_region(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
|
|
228
233
|
// an error occurred, mark this connection inactive
|
|
229
234
|
MARK_CONN_INACTIVE(self);
|
|
230
|
-
return rb_raise_mysql2_error(client);
|
|
235
|
+
return rb_raise_mysql2_error(wrapper->client);
|
|
231
236
|
}
|
|
232
237
|
|
|
233
|
-
result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, client, RUBY_UBF_IO, 0);
|
|
238
|
+
result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper->client, RUBY_UBF_IO, 0);
|
|
234
239
|
|
|
235
240
|
// we have our result, mark this connection inactive
|
|
236
241
|
MARK_CONN_INACTIVE(self);
|
|
237
242
|
|
|
238
243
|
if (result == NULL) {
|
|
239
|
-
if (mysql_field_count(client) != 0) {
|
|
240
|
-
rb_raise_mysql2_error(client);
|
|
244
|
+
if (mysql_field_count(wrapper->client) != 0) {
|
|
245
|
+
rb_raise_mysql2_error(wrapper->client);
|
|
241
246
|
}
|
|
242
247
|
return Qnil;
|
|
243
248
|
}
|
|
244
249
|
|
|
245
250
|
VALUE resultObj = rb_mysql_result_to_obj(result);
|
|
246
251
|
// pass-through query options for result construction later
|
|
247
|
-
rb_iv_set(resultObj, "@query_options",
|
|
252
|
+
rb_iv_set(resultObj, "@query_options", rb_funcall(rb_iv_get(self, "@query_options"), rb_intern("dup"), 0));
|
|
248
253
|
|
|
249
254
|
#ifdef HAVE_RUBY_ENCODING_H
|
|
250
255
|
mysql2_result_wrapper * result_wrapper;
|
|
@@ -260,10 +265,10 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
|
|
|
260
265
|
int fd, retval;
|
|
261
266
|
int async = 0;
|
|
262
267
|
VALUE opts, defaults;
|
|
263
|
-
GET_CLIENT(self)
|
|
268
|
+
GET_CLIENT(self);
|
|
264
269
|
|
|
265
|
-
REQUIRE_OPEN_DB(
|
|
266
|
-
args.mysql = client;
|
|
270
|
+
REQUIRE_OPEN_DB(wrapper);
|
|
271
|
+
args.mysql = wrapper->client;
|
|
267
272
|
|
|
268
273
|
// see if this connection is still waiting on a result from a previous query
|
|
269
274
|
if (wrapper->active == 0) {
|
|
@@ -294,13 +299,13 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
|
|
|
294
299
|
if (rb_thread_blocking_region(nogvl_send_query, &args, RUBY_UBF_IO, 0) == Qfalse) {
|
|
295
300
|
// an error occurred, we're not active anymore
|
|
296
301
|
MARK_CONN_INACTIVE(self);
|
|
297
|
-
return rb_raise_mysql2_error(client);
|
|
302
|
+
return rb_raise_mysql2_error(wrapper->client);
|
|
298
303
|
}
|
|
299
304
|
|
|
300
305
|
if (!async) {
|
|
301
306
|
// the below code is largely from do_mysql
|
|
302
307
|
// http://github.com/datamapper/do
|
|
303
|
-
fd = client->net.fd;
|
|
308
|
+
fd = wrapper->client->net.fd;
|
|
304
309
|
for(;;) {
|
|
305
310
|
FD_ZERO(&fdset);
|
|
306
311
|
FD_SET(fd, &fdset);
|
|
@@ -327,7 +332,7 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
|
|
|
327
332
|
static VALUE rb_mysql_client_escape(VALUE self, VALUE str) {
|
|
328
333
|
VALUE newStr;
|
|
329
334
|
unsigned long newLen, oldLen;
|
|
330
|
-
GET_CLIENT(self)
|
|
335
|
+
GET_CLIENT(self);
|
|
331
336
|
|
|
332
337
|
Check_Type(str, T_STRING);
|
|
333
338
|
#ifdef HAVE_RUBY_ENCODING_H
|
|
@@ -338,15 +343,15 @@ static VALUE rb_mysql_client_escape(VALUE self, VALUE str) {
|
|
|
338
343
|
#endif
|
|
339
344
|
|
|
340
345
|
oldLen = RSTRING_LEN(str);
|
|
341
|
-
|
|
346
|
+
newStr = rb_str_new(0, oldLen*2+1);
|
|
342
347
|
|
|
343
|
-
REQUIRE_OPEN_DB(
|
|
344
|
-
newLen = mysql_real_escape_string(client,
|
|
348
|
+
REQUIRE_OPEN_DB(wrapper);
|
|
349
|
+
newLen = mysql_real_escape_string(wrapper->client, RSTRING_PTR(newStr), StringValuePtr(str), oldLen);
|
|
345
350
|
if (newLen == oldLen) {
|
|
346
351
|
// no need to return a new ruby string if nothing changed
|
|
347
352
|
return str;
|
|
348
353
|
} else {
|
|
349
|
-
newStr
|
|
354
|
+
rb_str_resize(newStr, newLen);
|
|
350
355
|
#ifdef HAVE_RUBY_ENCODING_H
|
|
351
356
|
rb_enc_associate(newStr, conn_enc);
|
|
352
357
|
if (default_internal_enc) {
|
|
@@ -379,17 +384,17 @@ static VALUE rb_mysql_client_info(VALUE self) {
|
|
|
379
384
|
|
|
380
385
|
static VALUE rb_mysql_client_server_info(VALUE self) {
|
|
381
386
|
VALUE version, server_info;
|
|
382
|
-
GET_CLIENT(self)
|
|
387
|
+
GET_CLIENT(self);
|
|
383
388
|
#ifdef HAVE_RUBY_ENCODING_H
|
|
384
389
|
rb_encoding *default_internal_enc = rb_default_internal_encoding();
|
|
385
390
|
rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
|
|
386
391
|
#endif
|
|
387
392
|
|
|
388
|
-
REQUIRE_OPEN_DB(
|
|
393
|
+
REQUIRE_OPEN_DB(wrapper);
|
|
389
394
|
|
|
390
395
|
version = rb_hash_new();
|
|
391
|
-
rb_hash_aset(version, sym_id, LONG2FIX(mysql_get_server_version(client)));
|
|
392
|
-
server_info = rb_str_new2(mysql_get_server_info(client));
|
|
396
|
+
rb_hash_aset(version, sym_id, LONG2FIX(mysql_get_server_version(wrapper->client)));
|
|
397
|
+
server_info = rb_str_new2(mysql_get_server_info(wrapper->client));
|
|
393
398
|
#ifdef HAVE_RUBY_ENCODING_H
|
|
394
399
|
rb_enc_associate(server_info, conn_enc);
|
|
395
400
|
if (default_internal_enc) {
|
|
@@ -401,34 +406,34 @@ static VALUE rb_mysql_client_server_info(VALUE self) {
|
|
|
401
406
|
}
|
|
402
407
|
|
|
403
408
|
static VALUE rb_mysql_client_socket(VALUE self) {
|
|
404
|
-
GET_CLIENT(self)
|
|
405
|
-
REQUIRE_OPEN_DB(
|
|
406
|
-
return INT2NUM(client->net.fd);
|
|
409
|
+
GET_CLIENT(self);
|
|
410
|
+
REQUIRE_OPEN_DB(wrapper);
|
|
411
|
+
return INT2NUM(wrapper->client->net.fd);
|
|
407
412
|
}
|
|
408
413
|
|
|
409
414
|
static VALUE rb_mysql_client_last_id(VALUE self) {
|
|
410
|
-
GET_CLIENT(self)
|
|
411
|
-
REQUIRE_OPEN_DB(
|
|
412
|
-
return ULL2NUM(mysql_insert_id(client));
|
|
415
|
+
GET_CLIENT(self);
|
|
416
|
+
REQUIRE_OPEN_DB(wrapper);
|
|
417
|
+
return ULL2NUM(mysql_insert_id(wrapper->client));
|
|
413
418
|
}
|
|
414
419
|
|
|
415
420
|
static VALUE rb_mysql_client_affected_rows(VALUE self) {
|
|
416
|
-
GET_CLIENT(self)
|
|
417
|
-
REQUIRE_OPEN_DB(
|
|
418
|
-
return ULL2NUM(mysql_affected_rows(client));
|
|
421
|
+
GET_CLIENT(self);
|
|
422
|
+
REQUIRE_OPEN_DB(wrapper);
|
|
423
|
+
return ULL2NUM(mysql_affected_rows(wrapper->client));
|
|
419
424
|
}
|
|
420
425
|
|
|
421
426
|
static VALUE set_reconnect(VALUE self, VALUE value) {
|
|
422
427
|
my_bool reconnect;
|
|
423
|
-
GET_CLIENT(self)
|
|
428
|
+
GET_CLIENT(self);
|
|
424
429
|
|
|
425
430
|
if(!NIL_P(value)) {
|
|
426
431
|
reconnect = value == Qfalse ? 0 : 1;
|
|
427
432
|
|
|
428
433
|
/* set default reconnect behavior */
|
|
429
|
-
if (mysql_options(client, MYSQL_OPT_RECONNECT, &reconnect)) {
|
|
434
|
+
if (mysql_options(wrapper->client, MYSQL_OPT_RECONNECT, &reconnect)) {
|
|
430
435
|
/* TODO: warning - unable to set reconnect behavior */
|
|
431
|
-
rb_warn("%s\n", mysql_error(client));
|
|
436
|
+
rb_warn("%s\n", mysql_error(wrapper->client));
|
|
432
437
|
}
|
|
433
438
|
}
|
|
434
439
|
return value;
|
|
@@ -436,16 +441,16 @@ static VALUE set_reconnect(VALUE self, VALUE value) {
|
|
|
436
441
|
|
|
437
442
|
static VALUE set_connect_timeout(VALUE self, VALUE value) {
|
|
438
443
|
unsigned int connect_timeout = 0;
|
|
439
|
-
GET_CLIENT(self)
|
|
444
|
+
GET_CLIENT(self);
|
|
440
445
|
|
|
441
446
|
if(!NIL_P(value)) {
|
|
442
447
|
connect_timeout = NUM2INT(value);
|
|
443
448
|
if(0 == connect_timeout) return value;
|
|
444
449
|
|
|
445
450
|
/* set default connection timeout behavior */
|
|
446
|
-
if (mysql_options(client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout)) {
|
|
451
|
+
if (mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout)) {
|
|
447
452
|
/* TODO: warning - unable to set connection timeout */
|
|
448
|
-
rb_warn("%s\n", mysql_error(client));
|
|
453
|
+
rb_warn("%s\n", mysql_error(wrapper->client));
|
|
449
454
|
}
|
|
450
455
|
}
|
|
451
456
|
return value;
|
|
@@ -453,7 +458,7 @@ static VALUE set_connect_timeout(VALUE self, VALUE value) {
|
|
|
453
458
|
|
|
454
459
|
static VALUE set_charset_name(VALUE self, VALUE value) {
|
|
455
460
|
char * charset_name;
|
|
456
|
-
GET_CLIENT(self)
|
|
461
|
+
GET_CLIENT(self);
|
|
457
462
|
|
|
458
463
|
#ifdef HAVE_RUBY_ENCODING_H
|
|
459
464
|
VALUE new_encoding;
|
|
@@ -469,19 +474,19 @@ static VALUE set_charset_name(VALUE self, VALUE value) {
|
|
|
469
474
|
|
|
470
475
|
charset_name = StringValuePtr(value);
|
|
471
476
|
|
|
472
|
-
if (mysql_options(client, MYSQL_SET_CHARSET_NAME, charset_name)) {
|
|
477
|
+
if (mysql_options(wrapper->client, MYSQL_SET_CHARSET_NAME, charset_name)) {
|
|
473
478
|
/* TODO: warning - unable to set charset */
|
|
474
|
-
rb_warn("%s\n", mysql_error(client));
|
|
479
|
+
rb_warn("%s\n", mysql_error(wrapper->client));
|
|
475
480
|
}
|
|
476
481
|
|
|
477
482
|
return value;
|
|
478
483
|
}
|
|
479
484
|
|
|
480
485
|
static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE capath, VALUE cipher) {
|
|
481
|
-
GET_CLIENT(self)
|
|
486
|
+
GET_CLIENT(self);
|
|
482
487
|
|
|
483
488
|
if(!NIL_P(ca) || !NIL_P(key)) {
|
|
484
|
-
mysql_ssl_set(client,
|
|
489
|
+
mysql_ssl_set(wrapper->client,
|
|
485
490
|
NIL_P(key) ? NULL : StringValuePtr(key),
|
|
486
491
|
NIL_P(cert) ? NULL : StringValuePtr(cert),
|
|
487
492
|
NIL_P(ca) ? NULL : StringValuePtr(ca),
|
|
@@ -493,11 +498,11 @@ static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE
|
|
|
493
498
|
}
|
|
494
499
|
|
|
495
500
|
static VALUE init_connection(VALUE self) {
|
|
496
|
-
GET_CLIENT(self)
|
|
501
|
+
GET_CLIENT(self);
|
|
497
502
|
|
|
498
|
-
if (rb_thread_blocking_region(nogvl_init, client, RUBY_UBF_IO, 0) == Qfalse) {
|
|
503
|
+
if (rb_thread_blocking_region(nogvl_init, ((void *) &wrapper->client), RUBY_UBF_IO, 0) == Qfalse) {
|
|
499
504
|
/* TODO: warning - not enough memory? */
|
|
500
|
-
return rb_raise_mysql2_error(client);
|
|
505
|
+
return rb_raise_mysql2_error(wrapper->client);
|
|
501
506
|
}
|
|
502
507
|
|
|
503
508
|
return self;
|
data/ext/mysql2/client.h
CHANGED
data/ext/mysql2/result.c
CHANGED
|
@@ -12,7 +12,7 @@ static VALUE intern_encoding_from_charset;
|
|
|
12
12
|
static ID intern_new, intern_utc, intern_local, intern_encoding_from_charset_code,
|
|
13
13
|
intern_localtime, intern_local_offset, intern_civil, intern_new_offset;
|
|
14
14
|
static ID sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, sym_application_timezone,
|
|
15
|
-
sym_local, sym_utc, sym_cast_booleans;
|
|
15
|
+
sym_local, sym_utc, sym_cast_booleans, sym_cache_rows;
|
|
16
16
|
static ID intern_merge;
|
|
17
17
|
|
|
18
18
|
static void rb_mysql_result_mark(void * wrapper) {
|
|
@@ -316,7 +316,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
|
|
|
316
316
|
ID db_timezone, app_timezone, dbTz, appTz;
|
|
317
317
|
mysql2_result_wrapper * wrapper;
|
|
318
318
|
unsigned long i;
|
|
319
|
-
int symbolizeKeys = 0, asArray = 0, castBool = 0;
|
|
319
|
+
int symbolizeKeys = 0, asArray = 0, castBool = 0, cacheRows = 1;
|
|
320
320
|
|
|
321
321
|
GetMysql2Result(self, wrapper);
|
|
322
322
|
|
|
@@ -339,6 +339,10 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
|
|
|
339
339
|
castBool = 1;
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
+
if (rb_hash_aref(opts, sym_cache_rows) == Qfalse) {
|
|
343
|
+
cacheRows = 0;
|
|
344
|
+
}
|
|
345
|
+
|
|
342
346
|
dbTz = rb_hash_aref(opts, sym_database_timezone);
|
|
343
347
|
if (dbTz == sym_local) {
|
|
344
348
|
db_timezone = intern_local;
|
|
@@ -369,7 +373,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
|
|
|
369
373
|
wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
|
|
370
374
|
}
|
|
371
375
|
|
|
372
|
-
if (wrapper->lastRowProcessed == wrapper->numberOfRows) {
|
|
376
|
+
if (cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) {
|
|
373
377
|
// we've already read the entire dataset from the C result into our
|
|
374
378
|
// internal array. Lets hand that over to the user since it's ready to go
|
|
375
379
|
for (i = 0; i < wrapper->numberOfRows; i++) {
|
|
@@ -380,11 +384,13 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
|
|
|
380
384
|
rowsProcessed = RARRAY_LEN(wrapper->rows);
|
|
381
385
|
for (i = 0; i < wrapper->numberOfRows; i++) {
|
|
382
386
|
VALUE row;
|
|
383
|
-
if (i < rowsProcessed) {
|
|
387
|
+
if (cacheRows && i < rowsProcessed) {
|
|
384
388
|
row = rb_ary_entry(wrapper->rows, i);
|
|
385
389
|
} else {
|
|
386
390
|
row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool);
|
|
387
|
-
|
|
391
|
+
if (cacheRows) {
|
|
392
|
+
rb_ary_store(wrapper->rows, i, row);
|
|
393
|
+
}
|
|
388
394
|
wrapper->lastRowProcessed++;
|
|
389
395
|
}
|
|
390
396
|
|
|
@@ -453,11 +459,12 @@ void init_mysql2_result() {
|
|
|
453
459
|
sym_cast_booleans = ID2SYM(rb_intern("cast_booleans"));
|
|
454
460
|
sym_database_timezone = ID2SYM(rb_intern("database_timezone"));
|
|
455
461
|
sym_application_timezone = ID2SYM(rb_intern("application_timezone"));
|
|
462
|
+
sym_cache_rows = ID2SYM(rb_intern("cache_rows"));
|
|
456
463
|
|
|
457
|
-
rb_global_variable(&opt_decimal_zero); //never GC
|
|
458
464
|
opt_decimal_zero = rb_str_new2("0.0");
|
|
459
|
-
rb_global_variable(&
|
|
465
|
+
rb_global_variable(&opt_decimal_zero); //never GC
|
|
460
466
|
opt_float_zero = rb_float_new((double)0);
|
|
467
|
+
rb_global_variable(&opt_float_zero);
|
|
461
468
|
opt_time_year = INT2NUM(2000);
|
|
462
469
|
opt_time_month = INT2NUM(1);
|
|
463
470
|
opt_utc_offset = INT2NUM(0);
|
data/ext/mysql2/result.h
CHANGED
|
@@ -447,10 +447,11 @@ module ActiveRecord
|
|
|
447
447
|
if current_index != row[:Key_name]
|
|
448
448
|
next if row[:Key_name] == PRIMARY # skip the primary key
|
|
449
449
|
current_index = row[:Key_name]
|
|
450
|
-
indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique] == 0, [])
|
|
450
|
+
indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique] == 0, [], [])
|
|
451
451
|
end
|
|
452
452
|
|
|
453
453
|
indexes.last.columns << row[:Column_name]
|
|
454
|
+
indexes.last.lengths << row[:Sub_part]
|
|
454
455
|
end
|
|
455
456
|
indexes
|
|
456
457
|
end
|
data/lib/mysql2.rb
CHANGED
data/lib/mysql2/client.rb
CHANGED
|
@@ -2,12 +2,13 @@ module Mysql2
|
|
|
2
2
|
class Client
|
|
3
3
|
attr_reader :query_options
|
|
4
4
|
@@default_query_options = {
|
|
5
|
-
:as => :hash,
|
|
6
|
-
:async => false,
|
|
7
|
-
:cast_booleans => false,
|
|
8
|
-
:symbolize_keys => false,
|
|
9
|
-
:database_timezone => :local,
|
|
10
|
-
:application_timezone => nil
|
|
5
|
+
:as => :hash, # the type of object you want each row back as; also supports :array (an array of values)
|
|
6
|
+
:async => false, # don't wait for a result after sending the query, you'll have to monitor the socket yourself then eventually call Mysql2::Client#async_result
|
|
7
|
+
:cast_booleans => false, # cast tinyint(1) fields as true/false in ruby
|
|
8
|
+
:symbolize_keys => false, # return field names as symbols instead of strings
|
|
9
|
+
:database_timezone => :local, # timezone Mysql2 will assume datetime objects are stored in
|
|
10
|
+
:application_timezone => nil, # timezone Mysql2 will convert to before handing the object back to the caller
|
|
11
|
+
:cache_rows => true # tells Mysql2 to use it's internal row cache for results
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
def initialize(opts = {})
|
data/lib/mysql2/error.rb
CHANGED
data/mysql2.gemspec
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
Gem::Specification.new do |s|
|
|
7
7
|
s.name = %q{mysql2}
|
|
8
|
-
s.version = "0.2.
|
|
8
|
+
s.version = "0.2.4"
|
|
9
9
|
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
|
11
11
|
s.authors = ["Brian Lopez"]
|
|
12
|
-
s.date = %q{2010-
|
|
12
|
+
s.date = %q{2010-09-17}
|
|
13
13
|
s.email = %q{seniorlopez@gmail.com}
|
|
14
14
|
s.extensions = ["ext/mysql2/extconf.rb"]
|
|
15
15
|
s.extra_rdoc_files = [
|
data/spec/mysql2/client_spec.rb
CHANGED
|
@@ -105,30 +105,32 @@ describe Mysql2::Client do
|
|
|
105
105
|
|
|
106
106
|
# XXX this test is not deterministic (because Unix signal handling is not)
|
|
107
107
|
# and may fail on a loaded system
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
108
|
+
if RUBY_PLATFORM !~ /mingw|mswin/
|
|
109
|
+
it "should run signal handlers while waiting for a response" do
|
|
110
|
+
mark = {}
|
|
111
|
+
trap(:USR1) { mark[:USR1] = Time.now }
|
|
112
|
+
begin
|
|
113
|
+
mark[:START] = Time.now
|
|
114
|
+
pid = fork do
|
|
115
|
+
sleep 1 # wait for client "SELECT sleep(2)" query to start
|
|
116
|
+
Process.kill(:USR1, Process.ppid)
|
|
117
|
+
sleep # wait for explicit kill to prevent GC disconnect
|
|
118
|
+
end
|
|
119
|
+
@client.query("SELECT sleep(2)")
|
|
120
|
+
mark[:END] = Time.now
|
|
121
|
+
mark.include?(:USR1).should be_true
|
|
122
|
+
(mark[:USR1] - mark[:START]).should >= 1
|
|
123
|
+
(mark[:USR1] - mark[:START]).should < 1.1
|
|
124
|
+
(mark[:END] - mark[:USR1]).should > 0.9
|
|
125
|
+
(mark[:END] - mark[:START]).should >= 2
|
|
126
|
+
(mark[:END] - mark[:START]).should < 2.1
|
|
127
|
+
Process.kill(:TERM, pid)
|
|
128
|
+
Process.waitpid2(pid)
|
|
129
|
+
ensure
|
|
130
|
+
trap(:USR1, 'DEFAULT')
|
|
117
131
|
end
|
|
118
|
-
@client.query("SELECT sleep(2)")
|
|
119
|
-
mark[:END] = Time.now
|
|
120
|
-
mark.include?(:USR1).should be_true
|
|
121
|
-
(mark[:USR1] - mark[:START]).should >= 1
|
|
122
|
-
(mark[:USR1] - mark[:START]).should < 1.1
|
|
123
|
-
(mark[:END] - mark[:USR1]).should > 0.9
|
|
124
|
-
(mark[:END] - mark[:START]).should >= 2
|
|
125
|
-
(mark[:END] - mark[:START]).should < 2.1
|
|
126
|
-
Process.kill(:TERM, pid)
|
|
127
|
-
Process.waitpid2(pid)
|
|
128
|
-
ensure
|
|
129
|
-
trap(:USR1, 'DEFAULT')
|
|
130
132
|
end
|
|
131
|
-
end
|
|
133
|
+
end
|
|
132
134
|
end
|
|
133
135
|
|
|
134
136
|
it "should respond to #escape" do
|
|
@@ -144,6 +146,18 @@ describe Mysql2::Client do
|
|
|
144
146
|
@client.escape(str).object_id.should eql(str.object_id)
|
|
145
147
|
end
|
|
146
148
|
|
|
149
|
+
it "#escape should not overflow the thread stack" do
|
|
150
|
+
lambda {
|
|
151
|
+
Thread.new { @client.escape("'" * 256 * 1024) }.join
|
|
152
|
+
}.should_not raise_error(SystemStackError)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
it "#escape should not overflow the process stack" do
|
|
156
|
+
lambda {
|
|
157
|
+
Thread.new { @client.escape("'" * 1024 * 1024 * 4) }.join
|
|
158
|
+
}.should_not raise_error(SystemStackError)
|
|
159
|
+
end
|
|
160
|
+
|
|
147
161
|
it "should respond to #info" do
|
|
148
162
|
@client.should respond_to(:info)
|
|
149
163
|
end
|
data/spec/mysql2/error_spec.rb
CHANGED
|
@@ -13,4 +13,13 @@ describe Mysql2::Error do
|
|
|
13
13
|
it "should respond to #sql_state" do
|
|
14
14
|
@error.should respond_to(:sql_state)
|
|
15
15
|
end
|
|
16
|
+
|
|
17
|
+
# Mysql gem compatibility
|
|
18
|
+
it "should alias #error_number to #errno" do
|
|
19
|
+
@error.should respond_to(:errno)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "should alias #message to #error" do
|
|
23
|
+
@error.should respond_to(:error)
|
|
24
|
+
end
|
|
16
25
|
end
|
data/spec/mysql2/result_spec.rb
CHANGED
|
@@ -47,8 +47,13 @@ describe Mysql2::Result do
|
|
|
47
47
|
end
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
it "should cache previously yielded results" do
|
|
51
|
-
@result.first.should eql(@result.first)
|
|
50
|
+
it "should cache previously yielded results by default" do
|
|
51
|
+
@result.first.object_id.should eql(@result.first.object_id)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it "should not cache previously yielded results if cache_rows is disabled" do
|
|
55
|
+
result = @client.query "SELECT 1", :cache_rows => false
|
|
56
|
+
result.first.object_id.should_not eql(result.first.object_id)
|
|
52
57
|
end
|
|
53
58
|
end
|
|
54
59
|
|
data/tasks/compile.rake
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
gem 'rake-compiler', '~> 0.7.1'
|
|
2
2
|
require "rake/extensiontask"
|
|
3
3
|
|
|
4
|
-
MYSQL_VERSION = "5.1.
|
|
5
|
-
MYSQL_MIRROR = ENV['MYSQL_MIRROR'] || "http://mysql.
|
|
4
|
+
MYSQL_VERSION = "5.1.50"
|
|
5
|
+
MYSQL_MIRROR = ENV['MYSQL_MIRROR'] || "http://mysql.mirrors.pair.com"
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
def gemspec
|
|
9
|
+
@clean_gemspec ||= eval(File.read(File.expand_path('../../mysql2.gemspec', __FILE__)))
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
Rake::ExtensionTask.new("mysql2", gemspec) do |ext|
|
|
8
13
|
# reference where the vendored MySQL got extracted
|
|
9
14
|
mysql_lib = File.expand_path(File.join(File.dirname(__FILE__), '..', 'vendor', "mysql-#{MYSQL_VERSION}-win32"))
|
|
10
15
|
|
|
@@ -12,8 +17,16 @@ Rake::ExtensionTask.new("mysql2", JEWELER.gemspec) do |ext|
|
|
|
12
17
|
if RUBY_PLATFORM =~ /mswin|mingw/ then
|
|
13
18
|
ext.config_options << "--with-mysql-include=#{mysql_lib}/include"
|
|
14
19
|
ext.config_options << "--with-mysql-lib=#{mysql_lib}/lib/opt"
|
|
20
|
+
else
|
|
21
|
+
ext.cross_compile = true
|
|
22
|
+
ext.cross_platform = ['x86-mingw32', 'x86-mswin32-60']
|
|
23
|
+
ext.cross_config_options << "--with-mysql-include=#{mysql_lib}/include"
|
|
24
|
+
ext.cross_config_options << "--with-mysql-lib=#{mysql_lib}/lib/opt"
|
|
15
25
|
end
|
|
16
26
|
|
|
17
27
|
ext.lib_dir = File.join 'lib', 'mysql2'
|
|
28
|
+
|
|
29
|
+
# clean compiled extension
|
|
30
|
+
CLEAN.include "#{ext.lib_dir}/*.#{RbConfig::CONFIG['DLEXT']}"
|
|
18
31
|
end
|
|
19
32
|
Rake::Task[:spec].prerequisites << :compile
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mysql2
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
hash:
|
|
4
|
+
hash: 31
|
|
5
5
|
prerelease: false
|
|
6
6
|
segments:
|
|
7
7
|
- 0
|
|
8
8
|
- 2
|
|
9
|
-
-
|
|
10
|
-
version: 0.2.
|
|
9
|
+
- 4
|
|
10
|
+
version: 0.2.4
|
|
11
11
|
platform: ruby
|
|
12
12
|
authors:
|
|
13
13
|
- Brian Lopez
|
|
@@ -15,7 +15,7 @@ autorequire:
|
|
|
15
15
|
bindir: bin
|
|
16
16
|
cert_chain: []
|
|
17
17
|
|
|
18
|
-
date: 2010-
|
|
18
|
+
date: 2010-09-17 00:00:00 -07:00
|
|
19
19
|
default_executable:
|
|
20
20
|
dependencies: []
|
|
21
21
|
|