mysql2 0.2.3 → 0.2.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.rspec +2 -0
- data/CHANGELOG.md +18 -0
- data/README.rdoc +17 -7
- data/Rakefile +0 -37
- data/VERSION +1 -1
- data/benchmark/query_with_mysql_casting.rb +1 -1
- data/ext/mysql2/client.c +97 -84
- data/ext/mysql2/client.h +2 -1
- data/ext/mysql2/extconf.rb +1 -1
- data/ext/mysql2/result.c +14 -7
- data/ext/mysql2/result.h +1 -1
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +9 -1
- data/lib/mysql2/client.rb +9 -7
- data/lib/mysql2/em.rb +1 -1
- data/lib/mysql2/error.rb +4 -0
- data/lib/mysql2.rb +1 -1
- data/mysql2.gemspec +5 -4
- data/spec/em/em_spec.rb +42 -19
- data/spec/mysql2/client_spec.rb +83 -31
- data/spec/mysql2/error_spec.rb +9 -0
- data/spec/mysql2/result_spec.rb +7 -2
- data/spec/spec_helper.rb +3 -2
- data/tasks/compile.rake +40 -5
- data/tasks/jeweler.rake +17 -0
- data/tasks/rspec.rake +16 -0
- metadata +7 -6
- data/benchmark/thread_alone.rb +0 -20
- data/spec/spec.opts +0 -2
data/.gitignore
CHANGED
data/.rspec
ADDED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,23 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 0.2.5 (October 19th, 2010)
|
4
|
+
* fixes for easier Win32 binary gem deployment for targeting 1.8 and 1.9 in the same gem
|
5
|
+
* refactor of connection checks and management to avoid race conditions with the GC/threading to prevent the unexpected loss of connections
|
6
|
+
* update the default flags during connection
|
7
|
+
* add support for setting wait_timeout on AR adapter
|
8
|
+
* upgrade to rspec2
|
9
|
+
* bugfix for an edge case where the GC would clean up a Mysql2::Client object before the underlying MYSQL pointer had been initialized
|
10
|
+
* fix to CFLAGS to allow compilation on SPARC with sunstudio compiler - Anko painting <anko.com+github@gmail.com>
|
11
|
+
|
12
|
+
## 0.2.4 (September 17th, 2010)
|
13
|
+
* a few patches for win32 support from Luis Lavena - thanks man!
|
14
|
+
* bugfix from Eric Wong to avoid a potential stack overflow during Mysql2::Client#escape
|
15
|
+
* added the ability to turn internal row caching on/off via the :cache_rows => true/false option
|
16
|
+
* a couple of small patches for rbx compatibility
|
17
|
+
* set IndexDefinition#length in AR adapter - Kouhei Yanagita <yanagi@shakenbu.org>
|
18
|
+
* fix a long-standing data corruption bug - thank you thank you thank you to @joedamato (http://github.com/ice799)
|
19
|
+
* bugfix from calling mysql_close on a closed/freed connection surfaced by the above fix
|
20
|
+
|
3
21
|
## 0.2.3 (August 20th, 2010)
|
4
22
|
* connection flags can now be passed to the constructor via the :flags key
|
5
23
|
* 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
|
@@ -224,7 +234,7 @@ then iterating over every row using an #each like method yielding a block:
|
|
224
234
|
|
225
235
|
== Special Thanks
|
226
236
|
|
227
|
-
* Eric Wong - for the contribution (and informative explanations
|
237
|
+
* Eric Wong - for the contribution (and the informative explanations) of some thread-safety, non-blocking I/O and cleanup patches. You rock dude
|
228
238
|
* Yury Korolev (http://github.com/yury) - for TONS of help testing the ActiveRecord adapter
|
229
239
|
* Aaron Patterson (http://github.com/tenderlove) - tons of contributions, suggestions and general badassness
|
230
240
|
* Mike Perham (http://github.com/mperham) - Async ActiveRecord adapter (uses Fibers and EventMachine)
|
data/Rakefile
CHANGED
@@ -1,42 +1,5 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
-
begin
|
3
|
-
require 'jeweler'
|
4
|
-
JEWELER = Jeweler::Tasks.new do |gem|
|
5
|
-
gem.name = "mysql2"
|
6
|
-
gem.summary = "A simple, fast Mysql library for Ruby, binding to libmysql"
|
7
|
-
gem.email = "seniorlopez@gmail.com"
|
8
|
-
gem.homepage = "http://github.com/brianmario/mysql2"
|
9
|
-
gem.authors = ["Brian Lopez"]
|
10
|
-
gem.require_paths = ["lib", "ext"]
|
11
|
-
gem.extra_rdoc_files = `git ls-files *.rdoc`.split("\n")
|
12
|
-
gem.files = `git ls-files`.split("\n")
|
13
|
-
gem.extensions = ["ext/mysql2/extconf.rb"]
|
14
|
-
gem.files.include %w(lib/jeweler/templates/.document lib/jeweler/templates/.gitignore)
|
15
|
-
# gem.rubyforge_project = "mysql2"
|
16
|
-
end
|
17
|
-
rescue LoadError
|
18
|
-
puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install jeweler -s http://gems.github.com"
|
19
|
-
end
|
20
|
-
|
21
2
|
require 'rake'
|
22
|
-
require 'spec/rake/spectask'
|
23
|
-
|
24
|
-
desc "Run all examples with RCov"
|
25
|
-
Spec::Rake::SpecTask.new('spec:rcov') do |t|
|
26
|
-
t.spec_files = FileList['spec/']
|
27
|
-
t.rcov = true
|
28
|
-
t.rcov_opts = lambda do
|
29
|
-
IO.readlines("spec/rcov.opts").map {|l| l.chomp.split " "}.flatten
|
30
|
-
end
|
31
|
-
end
|
32
|
-
Spec::Rake::SpecTask.new('spec') do |t|
|
33
|
-
t.spec_files = FileList['spec/']
|
34
|
-
t.spec_opts << '--options' << 'spec/spec.opts'
|
35
|
-
t.verbose = true
|
36
|
-
t.warning = true
|
37
|
-
end
|
38
|
-
|
39
|
-
task :default => :spec
|
40
3
|
|
41
4
|
# Load custom tasks
|
42
5
|
Dir['tasks/*.rake'].sort.each { |f| load f }
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.2.
|
1
|
+
0.2.5
|
@@ -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) { \
|
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,11 +83,10 @@ 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;
|
89
87
|
|
90
88
|
/* may initialize embedded server and read /etc/services off disk */
|
91
|
-
client = mysql_init(
|
92
|
-
|
89
|
+
client = mysql_init((MYSQL *)ptr);
|
93
90
|
return client ? Qtrue : Qfalse;
|
94
91
|
}
|
95
92
|
|
@@ -107,38 +104,42 @@ static VALUE nogvl_connect(void *ptr) {
|
|
107
104
|
return client ? Qtrue : Qfalse;
|
108
105
|
}
|
109
106
|
|
110
|
-
static
|
111
|
-
mysql_client_wrapper *
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
* we'll send a QUIT message to the server, but that message is more of a
|
116
|
-
* formality than a hard requirement since the socket is getting shutdown
|
117
|
-
* anyways, so ensure the socket write does not block our interpreter
|
118
|
-
*/
|
119
|
-
int fd = client->net.fd;
|
120
|
-
int flags;
|
121
|
-
|
122
|
-
if (fd >= 0) {
|
107
|
+
static VALUE nogvl_close(void *ptr) {
|
108
|
+
mysql_client_wrapper *wrapper = ptr;
|
109
|
+
if (!wrapper->closed) {
|
110
|
+
wrapper->closed = 1;
|
111
|
+
|
123
112
|
/*
|
113
|
+
* we'll send a QUIT message to the server, but that message is more of a
|
114
|
+
* formality than a hard requirement since the socket is getting shutdown
|
115
|
+
* anyways, so ensure the socket write does not block our interpreter
|
116
|
+
*
|
117
|
+
*
|
124
118
|
* if the socket is dead we have no chance of blocking,
|
125
119
|
* so ignore any potential fcntl errors since they don't matter
|
126
120
|
*/
|
127
|
-
|
121
|
+
#ifndef _WIN32
|
122
|
+
int flags = fcntl(wrapper->client->net.fd, F_GETFL);
|
128
123
|
if (flags > 0 && !(flags & O_NONBLOCK))
|
129
|
-
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
124
|
+
fcntl(wrapper->client->net.fd, F_SETFL, flags | O_NONBLOCK);
|
125
|
+
#else
|
126
|
+
u_long iMode = 1;
|
127
|
+
ioctlsocket(wrapper->client->net.fd, FIONBIO, &iMode);
|
128
|
+
#endif
|
129
|
+
|
130
|
+
mysql_close(wrapper->client);
|
131
|
+
free(wrapper->client);
|
130
132
|
}
|
131
133
|
|
132
|
-
|
133
|
-
mysql_close(client);
|
134
|
-
xfree(ptr);
|
134
|
+
return Qnil;
|
135
135
|
}
|
136
136
|
|
137
|
-
static
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
137
|
+
static void rb_mysql_client_free(void * ptr) {
|
138
|
+
mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr;
|
139
|
+
|
140
|
+
nogvl_close(wrapper);
|
141
|
+
|
142
|
+
xfree(ptr);
|
142
143
|
}
|
143
144
|
|
144
145
|
static VALUE allocate(VALUE klass) {
|
@@ -147,12 +148,14 @@ static VALUE allocate(VALUE klass) {
|
|
147
148
|
obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper);
|
148
149
|
wrapper->encoding = Qnil;
|
149
150
|
wrapper->active = 0;
|
151
|
+
wrapper->closed = 1;
|
152
|
+
wrapper->client = (MYSQL*)malloc(sizeof(MYSQL));
|
150
153
|
return obj;
|
151
154
|
}
|
152
155
|
|
153
156
|
static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags) {
|
154
157
|
struct nogvl_connect_args args;
|
155
|
-
GET_CLIENT(self)
|
158
|
+
GET_CLIENT(self);
|
156
159
|
|
157
160
|
args.host = NIL_P(host) ? "localhost" : StringValuePtr(host);
|
158
161
|
args.unix_socket = NIL_P(socket) ? NULL : StringValuePtr(socket);
|
@@ -160,12 +163,12 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
|
|
160
163
|
args.user = NIL_P(user) ? NULL : StringValuePtr(user);
|
161
164
|
args.passwd = NIL_P(pass) ? NULL : StringValuePtr(pass);
|
162
165
|
args.db = NIL_P(database) ? NULL : StringValuePtr(database);
|
163
|
-
args.mysql = client;
|
164
|
-
args.client_flag =
|
166
|
+
args.mysql = wrapper->client;
|
167
|
+
args.client_flag = NUM2ULONG(flags);
|
165
168
|
|
166
169
|
if (rb_thread_blocking_region(nogvl_connect, &args, RUBY_UBF_IO, 0) == Qfalse) {
|
167
170
|
// unable to connect
|
168
|
-
return rb_raise_mysql2_error(client);
|
171
|
+
return rb_raise_mysql2_error(wrapper->client);
|
169
172
|
}
|
170
173
|
|
171
174
|
return self;
|
@@ -178,9 +181,11 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
|
|
178
181
|
* for the garbage collector.
|
179
182
|
*/
|
180
183
|
static VALUE rb_mysql_client_close(VALUE self) {
|
181
|
-
GET_CLIENT(self)
|
184
|
+
GET_CLIENT(self);
|
182
185
|
|
183
|
-
|
186
|
+
if (!wrapper->closed) {
|
187
|
+
rb_thread_blocking_region(nogvl_close, wrapper, RUBY_UBF_IO, 0);
|
188
|
+
}
|
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,8 +332,9 @@ 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
|
|
337
|
+
REQUIRE_OPEN_DB(wrapper);
|
332
338
|
Check_Type(str, T_STRING);
|
333
339
|
#ifdef HAVE_RUBY_ENCODING_H
|
334
340
|
rb_encoding *default_internal_enc = rb_default_internal_encoding();
|
@@ -338,15 +344,14 @@ static VALUE rb_mysql_client_escape(VALUE self, VALUE str) {
|
|
338
344
|
#endif
|
339
345
|
|
340
346
|
oldLen = RSTRING_LEN(str);
|
341
|
-
|
347
|
+
newStr = rb_str_new(0, oldLen*2+1);
|
342
348
|
|
343
|
-
|
344
|
-
newLen = mysql_real_escape_string(client, escaped, StringValuePtr(str), oldLen);
|
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) {
|
@@ -360,6 +365,7 @@ static VALUE rb_mysql_client_escape(VALUE self, VALUE str) {
|
|
360
365
|
static VALUE rb_mysql_client_info(VALUE self) {
|
361
366
|
VALUE version = rb_hash_new(), client_info;
|
362
367
|
GET_CLIENT(self);
|
368
|
+
|
363
369
|
#ifdef HAVE_RUBY_ENCODING_H
|
364
370
|
rb_encoding *default_internal_enc = rb_default_internal_encoding();
|
365
371
|
rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
|
@@ -379,17 +385,17 @@ static VALUE rb_mysql_client_info(VALUE self) {
|
|
379
385
|
|
380
386
|
static VALUE rb_mysql_client_server_info(VALUE self) {
|
381
387
|
VALUE version, server_info;
|
382
|
-
GET_CLIENT(self)
|
388
|
+
GET_CLIENT(self);
|
389
|
+
|
390
|
+
REQUIRE_OPEN_DB(wrapper);
|
383
391
|
#ifdef HAVE_RUBY_ENCODING_H
|
384
392
|
rb_encoding *default_internal_enc = rb_default_internal_encoding();
|
385
393
|
rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
|
386
394
|
#endif
|
387
395
|
|
388
|
-
REQUIRE_OPEN_DB(client);
|
389
|
-
|
390
396
|
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));
|
397
|
+
rb_hash_aset(version, sym_id, LONG2FIX(mysql_get_server_version(wrapper->client)));
|
398
|
+
server_info = rb_str_new2(mysql_get_server_info(wrapper->client));
|
393
399
|
#ifdef HAVE_RUBY_ENCODING_H
|
394
400
|
rb_enc_associate(server_info, conn_enc);
|
395
401
|
if (default_internal_enc) {
|
@@ -401,34 +407,40 @@ static VALUE rb_mysql_client_server_info(VALUE self) {
|
|
401
407
|
}
|
402
408
|
|
403
409
|
static VALUE rb_mysql_client_socket(VALUE self) {
|
404
|
-
GET_CLIENT(self)
|
405
|
-
REQUIRE_OPEN_DB(
|
406
|
-
return INT2NUM(client->net.fd);
|
410
|
+
GET_CLIENT(self);
|
411
|
+
REQUIRE_OPEN_DB(wrapper);
|
412
|
+
return INT2NUM(wrapper->client->net.fd);
|
407
413
|
}
|
408
414
|
|
409
415
|
static VALUE rb_mysql_client_last_id(VALUE self) {
|
410
|
-
GET_CLIENT(self)
|
411
|
-
REQUIRE_OPEN_DB(
|
412
|
-
return ULL2NUM(mysql_insert_id(client));
|
416
|
+
GET_CLIENT(self);
|
417
|
+
REQUIRE_OPEN_DB(wrapper);
|
418
|
+
return ULL2NUM(mysql_insert_id(wrapper->client));
|
413
419
|
}
|
414
420
|
|
415
421
|
static VALUE rb_mysql_client_affected_rows(VALUE self) {
|
416
|
-
GET_CLIENT(self)
|
417
|
-
|
418
|
-
|
422
|
+
GET_CLIENT(self);
|
423
|
+
my_ulonglong retVal;
|
424
|
+
|
425
|
+
REQUIRE_OPEN_DB(wrapper);
|
426
|
+
retVal = mysql_affected_rows(wrapper->client);
|
427
|
+
if (retVal == (my_ulonglong)-1) {
|
428
|
+
rb_raise_mysql2_error(wrapper->client);
|
429
|
+
}
|
430
|
+
return ULL2NUM(retVal);
|
419
431
|
}
|
420
432
|
|
421
433
|
static VALUE set_reconnect(VALUE self, VALUE value) {
|
422
434
|
my_bool reconnect;
|
423
|
-
GET_CLIENT(self)
|
435
|
+
GET_CLIENT(self);
|
424
436
|
|
425
437
|
if(!NIL_P(value)) {
|
426
438
|
reconnect = value == Qfalse ? 0 : 1;
|
427
439
|
|
428
440
|
/* set default reconnect behavior */
|
429
|
-
if (mysql_options(client, MYSQL_OPT_RECONNECT, &reconnect)) {
|
441
|
+
if (mysql_options(wrapper->client, MYSQL_OPT_RECONNECT, &reconnect)) {
|
430
442
|
/* TODO: warning - unable to set reconnect behavior */
|
431
|
-
rb_warn("%s\n", mysql_error(client));
|
443
|
+
rb_warn("%s\n", mysql_error(wrapper->client));
|
432
444
|
}
|
433
445
|
}
|
434
446
|
return value;
|
@@ -436,16 +448,16 @@ static VALUE set_reconnect(VALUE self, VALUE value) {
|
|
436
448
|
|
437
449
|
static VALUE set_connect_timeout(VALUE self, VALUE value) {
|
438
450
|
unsigned int connect_timeout = 0;
|
439
|
-
GET_CLIENT(self)
|
451
|
+
GET_CLIENT(self);
|
440
452
|
|
441
453
|
if(!NIL_P(value)) {
|
442
454
|
connect_timeout = NUM2INT(value);
|
443
455
|
if(0 == connect_timeout) return value;
|
444
456
|
|
445
457
|
/* set default connection timeout behavior */
|
446
|
-
if (mysql_options(client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout)) {
|
458
|
+
if (mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout)) {
|
447
459
|
/* TODO: warning - unable to set connection timeout */
|
448
|
-
rb_warn("%s\n", mysql_error(client));
|
460
|
+
rb_warn("%s\n", mysql_error(wrapper->client));
|
449
461
|
}
|
450
462
|
}
|
451
463
|
return value;
|
@@ -453,7 +465,7 @@ static VALUE set_connect_timeout(VALUE self, VALUE value) {
|
|
453
465
|
|
454
466
|
static VALUE set_charset_name(VALUE self, VALUE value) {
|
455
467
|
char * charset_name;
|
456
|
-
GET_CLIENT(self)
|
468
|
+
GET_CLIENT(self);
|
457
469
|
|
458
470
|
#ifdef HAVE_RUBY_ENCODING_H
|
459
471
|
VALUE new_encoding;
|
@@ -469,19 +481,19 @@ static VALUE set_charset_name(VALUE self, VALUE value) {
|
|
469
481
|
|
470
482
|
charset_name = StringValuePtr(value);
|
471
483
|
|
472
|
-
if (mysql_options(client, MYSQL_SET_CHARSET_NAME, charset_name)) {
|
484
|
+
if (mysql_options(wrapper->client, MYSQL_SET_CHARSET_NAME, charset_name)) {
|
473
485
|
/* TODO: warning - unable to set charset */
|
474
|
-
rb_warn("%s\n", mysql_error(client));
|
486
|
+
rb_warn("%s\n", mysql_error(wrapper->client));
|
475
487
|
}
|
476
488
|
|
477
489
|
return value;
|
478
490
|
}
|
479
491
|
|
480
492
|
static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE capath, VALUE cipher) {
|
481
|
-
GET_CLIENT(self)
|
493
|
+
GET_CLIENT(self);
|
482
494
|
|
483
495
|
if(!NIL_P(ca) || !NIL_P(key)) {
|
484
|
-
mysql_ssl_set(client,
|
496
|
+
mysql_ssl_set(wrapper->client,
|
485
497
|
NIL_P(key) ? NULL : StringValuePtr(key),
|
486
498
|
NIL_P(cert) ? NULL : StringValuePtr(cert),
|
487
499
|
NIL_P(ca) ? NULL : StringValuePtr(ca),
|
@@ -493,13 +505,14 @@ static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE
|
|
493
505
|
}
|
494
506
|
|
495
507
|
static VALUE init_connection(VALUE self) {
|
496
|
-
GET_CLIENT(self)
|
508
|
+
GET_CLIENT(self);
|
497
509
|
|
498
|
-
if (rb_thread_blocking_region(nogvl_init, client, RUBY_UBF_IO, 0) == Qfalse) {
|
510
|
+
if (rb_thread_blocking_region(nogvl_init, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
|
499
511
|
/* TODO: warning - not enough memory? */
|
500
|
-
return rb_raise_mysql2_error(client);
|
512
|
+
return rb_raise_mysql2_error(wrapper->client);
|
501
513
|
}
|
502
514
|
|
515
|
+
wrapper->closed = 0;
|
503
516
|
return self;
|
504
517
|
}
|
505
518
|
|
data/ext/mysql2/client.h
CHANGED
data/ext/mysql2/extconf.rb
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
|
@@ -616,8 +617,15 @@ module ActiveRecord
|
|
616
617
|
# Turn this off. http://dev.rubyonrails.org/ticket/6778
|
617
618
|
variable_assignments = ['SQL_AUTO_IS_NULL=0']
|
618
619
|
encoding = @config[:encoding]
|
620
|
+
|
621
|
+
# make sure we set the encoding
|
619
622
|
variable_assignments << "NAMES '#{encoding}'" if encoding
|
620
623
|
|
624
|
+
# increase timeout so mysql server doesn't disconnect us
|
625
|
+
wait_timeout = @config[:wait_timeout]
|
626
|
+
wait_timeout = 2592000 unless wait_timeout.is_a?(Fixnum)
|
627
|
+
variable_assignments << "@@wait_timeout = #{wait_timeout}"
|
628
|
+
|
621
629
|
execute("SET #{variable_assignments.join(', ')}", :skip_logging)
|
622
630
|
end
|
623
631
|
|
data/lib/mysql2/client.rb
CHANGED
@@ -2,12 +2,14 @@ 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
|
12
|
+
:connect_flags => REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION
|
11
13
|
}
|
12
14
|
|
13
15
|
def initialize(opts = {})
|
@@ -30,7 +32,7 @@ module Mysql2
|
|
30
32
|
port = opts[:port] || 3306
|
31
33
|
database = opts[:database]
|
32
34
|
socket = opts[:socket]
|
33
|
-
flags = opts[:flags]
|
35
|
+
flags = opts[:flags] ? opts[:flags] | @query_options[:connect_flags] : @query_options[:connect_flags]
|
34
36
|
|
35
37
|
connect user, pass, host, port, database, socket, flags
|
36
38
|
end
|
data/lib/mysql2/em.rb
CHANGED
data/lib/mysql2/error.rb
CHANGED
data/lib/mysql2.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.5"
|
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-10-19}
|
13
13
|
s.email = %q{seniorlopez@gmail.com}
|
14
14
|
s.extensions = ["ext/mysql2/extconf.rb"]
|
15
15
|
s.extra_rdoc_files = [
|
@@ -17,6 +17,7 @@ Gem::Specification.new do |s|
|
|
17
17
|
]
|
18
18
|
s.files = [
|
19
19
|
".gitignore",
|
20
|
+
".rspec",
|
20
21
|
"CHANGELOG.md",
|
21
22
|
"MIT-LICENSE",
|
22
23
|
"README.rdoc",
|
@@ -29,7 +30,6 @@ Gem::Specification.new do |s|
|
|
29
30
|
"benchmark/query_without_mysql_casting.rb",
|
30
31
|
"benchmark/sequel.rb",
|
31
32
|
"benchmark/setup_db.rb",
|
32
|
-
"benchmark/thread_alone.rb",
|
33
33
|
"examples/eventmachine.rb",
|
34
34
|
"examples/threaded.rb",
|
35
35
|
"ext/mysql2/client.c",
|
@@ -54,10 +54,11 @@ Gem::Specification.new do |s|
|
|
54
54
|
"spec/mysql2/error_spec.rb",
|
55
55
|
"spec/mysql2/result_spec.rb",
|
56
56
|
"spec/rcov.opts",
|
57
|
-
"spec/spec.opts",
|
58
57
|
"spec/spec_helper.rb",
|
59
58
|
"tasks/benchmarks.rake",
|
60
59
|
"tasks/compile.rake",
|
60
|
+
"tasks/jeweler.rake",
|
61
|
+
"tasks/rspec.rake",
|
61
62
|
"tasks/vendor_mysql.rake"
|
62
63
|
]
|
63
64
|
s.homepage = %q{http://github.com/brianmario/mysql2}
|
data/spec/em/em_spec.rb
CHANGED
@@ -1,26 +1,49 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
-
|
3
|
-
require '
|
2
|
+
if defined? EventMachine
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'mysql2/em'
|
4
5
|
|
5
|
-
describe Mysql2::EM::Client do
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
6
|
+
describe Mysql2::EM::Client do
|
7
|
+
it "should support async queries" do
|
8
|
+
results = []
|
9
|
+
EM.run do
|
10
|
+
client1 = Mysql2::EM::Client.new
|
11
|
+
defer1 = client1.query "SELECT sleep(0.1) as first_query"
|
12
|
+
defer1.callback do |result|
|
13
|
+
results << result.first
|
14
|
+
EM.stop_event_loop
|
15
|
+
end
|
16
|
+
|
17
|
+
client2 = Mysql2::EM::Client.new
|
18
|
+
defer2 = client2.query "SELECT sleep(0.025) second_query"
|
19
|
+
defer2.callback do |result|
|
20
|
+
results << result.first
|
21
|
+
end
|
14
22
|
end
|
15
23
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
24
|
+
results[0].keys.should include("second_query")
|
25
|
+
results[1].keys.should include("first_query")
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should support queries in callbacks" do
|
29
|
+
results = []
|
30
|
+
EM.run do
|
31
|
+
client = Mysql2::EM::Client.new
|
32
|
+
defer1 = client.query "SELECT sleep(0.025) as first_query"
|
33
|
+
defer1.callback do |result|
|
34
|
+
results << result.first
|
35
|
+
defer2 = client.query "SELECT sleep(0.025) as second_query"
|
36
|
+
defer2.callback do |result|
|
37
|
+
results << result.first
|
38
|
+
EM.stop_event_loop
|
39
|
+
end
|
40
|
+
end
|
20
41
|
end
|
42
|
+
|
43
|
+
results[0].keys.should include("first_query")
|
44
|
+
results[1].keys.should include("second_query")
|
21
45
|
end
|
22
|
-
|
23
|
-
results[0].keys.should include("second_query")
|
24
|
-
results[1].keys.should include("first_query")
|
25
46
|
end
|
26
|
-
|
47
|
+
else
|
48
|
+
puts "EventMachine not installed, skipping the specs that use it"
|
49
|
+
end
|
data/spec/mysql2/client_spec.rb
CHANGED
@@ -23,10 +23,10 @@ describe Mysql2::Client do
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
client = klient.new :flags => Mysql2::Client::FOUND_ROWS
|
26
|
-
client.connect_args.last.last
|
26
|
+
(client.connect_args.last.last & Mysql2::Client::FOUND_ROWS).should be_true
|
27
27
|
end
|
28
28
|
|
29
|
-
it "should default flags to
|
29
|
+
it "should default flags to (REMEMBER_OPTIONS, LONG_PASSWORD, LONG_FLAG, TRANSACTIONS, PROTOCOL_41, SECURE_CONNECTION)" do
|
30
30
|
klient = Class.new(Mysql2::Client) do
|
31
31
|
attr_reader :connect_args
|
32
32
|
def connect *args
|
@@ -35,7 +35,12 @@ describe Mysql2::Client do
|
|
35
35
|
end
|
36
36
|
end
|
37
37
|
client = klient.new
|
38
|
-
client.connect_args.last.last
|
38
|
+
(client.connect_args.last.last & (Mysql2::Client::REMEMBER_OPTIONS |
|
39
|
+
Mysql2::Client::LONG_PASSWORD |
|
40
|
+
Mysql2::Client::LONG_FLAG |
|
41
|
+
Mysql2::Client::TRANSACTIONS |
|
42
|
+
Mysql2::Client::PROTOCOL_41 |
|
43
|
+
Mysql2::Client::SECURE_CONNECTION)).should be_true
|
39
44
|
end
|
40
45
|
|
41
46
|
it "should have a global default_query_options hash" do
|
@@ -71,6 +76,9 @@ describe Mysql2::Client do
|
|
71
76
|
|
72
77
|
it "should be able to close properly" do
|
73
78
|
@client.close.should be_nil
|
79
|
+
lambda {
|
80
|
+
@client.query "SELECT 1"
|
81
|
+
}.should raise_error(Mysql2::Error)
|
74
82
|
end
|
75
83
|
|
76
84
|
it "should respond to #query" do
|
@@ -103,45 +111,75 @@ describe Mysql2::Client do
|
|
103
111
|
}.should raise_error(Mysql2::Error)
|
104
112
|
end
|
105
113
|
|
114
|
+
it "should require an open connection" do
|
115
|
+
@client.close
|
116
|
+
lambda {
|
117
|
+
@client.query "SELECT 1"
|
118
|
+
}.should raise_error(Mysql2::Error)
|
119
|
+
end
|
120
|
+
|
106
121
|
# XXX this test is not deterministic (because Unix signal handling is not)
|
107
122
|
# and may fail on a loaded system
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
123
|
+
if RUBY_PLATFORM !~ /mingw|mswin/
|
124
|
+
it "should run signal handlers while waiting for a response" do
|
125
|
+
mark = {}
|
126
|
+
trap(:USR1) { mark[:USR1] = Time.now }
|
127
|
+
begin
|
128
|
+
mark[:START] = Time.now
|
129
|
+
pid = fork do
|
130
|
+
sleep 1 # wait for client "SELECT sleep(2)" query to start
|
131
|
+
Process.kill(:USR1, Process.ppid)
|
132
|
+
sleep # wait for explicit kill to prevent GC disconnect
|
133
|
+
end
|
134
|
+
@client.query("SELECT sleep(2)")
|
135
|
+
mark[:END] = Time.now
|
136
|
+
mark.include?(:USR1).should be_true
|
137
|
+
(mark[:USR1] - mark[:START]).should >= 1
|
138
|
+
(mark[:USR1] - mark[:START]).should < 1.1
|
139
|
+
(mark[:END] - mark[:USR1]).should > 0.9
|
140
|
+
(mark[:END] - mark[:START]).should >= 2
|
141
|
+
(mark[:END] - mark[:START]).should < 2.1
|
142
|
+
Process.kill(:TERM, pid)
|
143
|
+
Process.waitpid2(pid)
|
144
|
+
ensure
|
145
|
+
trap(:USR1, 'DEFAULT')
|
117
146
|
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
147
|
end
|
131
|
-
end
|
148
|
+
end
|
132
149
|
end
|
133
150
|
|
134
151
|
it "should respond to #escape" do
|
135
152
|
@client.should respond_to(:escape)
|
136
153
|
end
|
137
154
|
|
138
|
-
|
139
|
-
|
140
|
-
|
155
|
+
context "#escape" do
|
156
|
+
it "should return a new SQL-escape version of the passed string" do
|
157
|
+
@client.escape("abc'def\"ghi\0jkl%mno").should eql("abc\\'def\\\"ghi\\0jkl%mno")
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should return the passed string if nothing was escaped" do
|
161
|
+
str = "plain"
|
162
|
+
@client.escape(str).object_id.should eql(str.object_id)
|
163
|
+
end
|
164
|
+
|
165
|
+
it "should not overflow the thread stack" do
|
166
|
+
lambda {
|
167
|
+
Thread.new { @client.escape("'" * 256 * 1024) }.join
|
168
|
+
}.should_not raise_error(SystemStackError)
|
169
|
+
end
|
141
170
|
|
142
|
-
|
143
|
-
|
144
|
-
|
171
|
+
it "should not overflow the process stack" do
|
172
|
+
lambda {
|
173
|
+
Thread.new { @client.escape("'" * 1024 * 1024 * 4) }.join
|
174
|
+
}.should_not raise_error(SystemStackError)
|
175
|
+
end
|
176
|
+
|
177
|
+
it "should require an open connection" do
|
178
|
+
@client.close
|
179
|
+
lambda {
|
180
|
+
@client.escape ""
|
181
|
+
}.should raise_error(Mysql2::Error)
|
182
|
+
end
|
145
183
|
end
|
146
184
|
|
147
185
|
it "should respond to #info" do
|
@@ -189,6 +227,13 @@ describe Mysql2::Client do
|
|
189
227
|
server_info[:version].class.should eql(String)
|
190
228
|
end
|
191
229
|
|
230
|
+
it "#server_info should require an open connection" do
|
231
|
+
@client.close
|
232
|
+
lambda {
|
233
|
+
@client.server_info
|
234
|
+
}.should raise_error(Mysql2::Error)
|
235
|
+
end
|
236
|
+
|
192
237
|
if defined? Encoding
|
193
238
|
context "strings returned by #server_info" do
|
194
239
|
it "should default to the connection's encoding if Encoding.default_internal is nil" do
|
@@ -217,6 +262,13 @@ describe Mysql2::Client do
|
|
217
262
|
@client.socket.should_not eql(0)
|
218
263
|
end
|
219
264
|
|
265
|
+
it "#socket should require an open connection" do
|
266
|
+
@client.close
|
267
|
+
lambda {
|
268
|
+
@client.socket
|
269
|
+
}.should raise_error(Mysql2::Error)
|
270
|
+
end
|
271
|
+
|
220
272
|
it "should raise a Mysql2::Error exception upon connection failure" do
|
221
273
|
lambda {
|
222
274
|
bad_client = Mysql2::Client.new :host => "dfjhdi9wrhw", :username => 'asdfasdf8d2h'
|
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/spec/spec_helper.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
-
|
3
|
+
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
|
4
|
+
require 'rspec'
|
4
5
|
require 'mysql2'
|
5
6
|
require 'timeout'
|
6
7
|
|
7
|
-
|
8
|
+
RSpec.configure do |config|
|
8
9
|
config.before(:all) do
|
9
10
|
client = Mysql2::Client.new :database => 'test'
|
10
11
|
client.query %[
|
data/tasks/compile.rake
CHANGED
@@ -1,19 +1,54 @@
|
|
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.51"
|
5
|
+
MYSQL_MIRROR = ENV['MYSQL_MIRROR'] || "http://mysql.he.net/"
|
6
6
|
|
7
|
-
|
7
|
+
def gemspec
|
8
|
+
@clean_gemspec ||= eval(File.read(File.expand_path('../../mysql2.gemspec', __FILE__)))
|
9
|
+
end
|
10
|
+
|
11
|
+
Rake::ExtensionTask.new("mysql2", gemspec) do |ext|
|
8
12
|
# reference where the vendored MySQL got extracted
|
9
13
|
mysql_lib = File.expand_path(File.join(File.dirname(__FILE__), '..', 'vendor', "mysql-#{MYSQL_VERSION}-win32"))
|
10
14
|
|
15
|
+
# DRY options feed into compile or cross-compile process
|
16
|
+
windows_options = [
|
17
|
+
"--with-mysql-include=#{mysql_lib}/include",
|
18
|
+
"--with-mysql-lib=#{mysql_lib}/lib/opt"
|
19
|
+
]
|
20
|
+
|
11
21
|
# automatically add build options to avoid need of manual input
|
12
22
|
if RUBY_PLATFORM =~ /mswin|mingw/ then
|
13
|
-
ext.config_options
|
14
|
-
|
23
|
+
ext.config_options = windows_options
|
24
|
+
else
|
25
|
+
ext.cross_compile = true
|
26
|
+
ext.cross_platform = ['x86-mingw32', 'x86-mswin32-60']
|
27
|
+
ext.cross_config_options = windows_options
|
28
|
+
|
29
|
+
# inject 1.8/1.9 pure-ruby entry point when cross compiling only
|
30
|
+
ext.cross_compiling do |spec|
|
31
|
+
spec.files << 'lib/mysql2/mysql2.rb'
|
32
|
+
end
|
15
33
|
end
|
16
34
|
|
17
35
|
ext.lib_dir = File.join 'lib', 'mysql2'
|
36
|
+
|
37
|
+
# clean compiled extension
|
38
|
+
CLEAN.include "#{ext.lib_dir}/*.#{RbConfig::CONFIG['DLEXT']}"
|
18
39
|
end
|
19
40
|
Rake::Task[:spec].prerequisites << :compile
|
41
|
+
|
42
|
+
file 'lib/mysql2/mysql2.rb' do |t|
|
43
|
+
name = gemspec.name
|
44
|
+
File.open(t.name, 'wb') do |f|
|
45
|
+
f.write <<-eoruby
|
46
|
+
RUBY_VERSION =~ /(\\d+.\\d+)/
|
47
|
+
require "#{name}/\#{$1}/#{name}"
|
48
|
+
eoruby
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
if Rake::Task.task_defined?(:cross)
|
53
|
+
Rake::Task[:cross].prerequisites << "lib/mysql2/mysql2.rb"
|
54
|
+
end
|
data/tasks/jeweler.rake
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
begin
|
2
|
+
require 'jeweler'
|
3
|
+
JEWELER = Jeweler::Tasks.new do |gem|
|
4
|
+
gem.name = "mysql2"
|
5
|
+
gem.summary = "A simple, fast Mysql library for Ruby, binding to libmysql"
|
6
|
+
gem.email = "seniorlopez@gmail.com"
|
7
|
+
gem.homepage = "http://github.com/brianmario/mysql2"
|
8
|
+
gem.authors = ["Brian Lopez"]
|
9
|
+
gem.require_paths = ["lib", "ext"]
|
10
|
+
gem.extra_rdoc_files = `git ls-files *.rdoc`.split("\n")
|
11
|
+
gem.files = `git ls-files`.split("\n")
|
12
|
+
gem.extensions = ["ext/mysql2/extconf.rb"]
|
13
|
+
gem.files.include %w(lib/jeweler/templates/.document lib/jeweler/templates/.gitignore)
|
14
|
+
end
|
15
|
+
rescue LoadError
|
16
|
+
puts "jeweler, or one of its dependencies, is not available. Install it with: sudo gem install jeweler"
|
17
|
+
end
|
data/tasks/rspec.rake
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
begin
|
2
|
+
require 'rspec'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
|
5
|
+
desc "Run all examples with RCov"
|
6
|
+
RSpec::Core::RakeTask.new('spec:rcov') do |t|
|
7
|
+
t.rcov = true
|
8
|
+
end
|
9
|
+
RSpec::Core::RakeTask.new('spec') do |t|
|
10
|
+
t.verbose = true
|
11
|
+
end
|
12
|
+
|
13
|
+
task :default => :spec
|
14
|
+
rescue LoadError
|
15
|
+
puts "rspec, or one of its dependencies, is not available. Install it with: sudo gem install rspec"
|
16
|
+
end
|
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: 29
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 2
|
9
|
-
-
|
10
|
-
version: 0.2.
|
9
|
+
- 5
|
10
|
+
version: 0.2.5
|
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-10-19 00:00:00 -07:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|
@@ -29,6 +29,7 @@ extra_rdoc_files:
|
|
29
29
|
- README.rdoc
|
30
30
|
files:
|
31
31
|
- .gitignore
|
32
|
+
- .rspec
|
32
33
|
- CHANGELOG.md
|
33
34
|
- MIT-LICENSE
|
34
35
|
- README.rdoc
|
@@ -41,7 +42,6 @@ files:
|
|
41
42
|
- benchmark/query_without_mysql_casting.rb
|
42
43
|
- benchmark/sequel.rb
|
43
44
|
- benchmark/setup_db.rb
|
44
|
-
- benchmark/thread_alone.rb
|
45
45
|
- examples/eventmachine.rb
|
46
46
|
- examples/threaded.rb
|
47
47
|
- ext/mysql2/client.c
|
@@ -66,10 +66,11 @@ files:
|
|
66
66
|
- spec/mysql2/error_spec.rb
|
67
67
|
- spec/mysql2/result_spec.rb
|
68
68
|
- spec/rcov.opts
|
69
|
-
- spec/spec.opts
|
70
69
|
- spec/spec_helper.rb
|
71
70
|
- tasks/benchmarks.rake
|
72
71
|
- tasks/compile.rake
|
72
|
+
- tasks/jeweler.rake
|
73
|
+
- tasks/rspec.rake
|
73
74
|
- tasks/vendor_mysql.rake
|
74
75
|
has_rdoc: true
|
75
76
|
homepage: http://github.com/brianmario/mysql2
|
data/benchmark/thread_alone.rb
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
3
|
-
|
4
|
-
require 'rubygems'
|
5
|
-
require 'benchmark'
|
6
|
-
require 'mysql2'
|
7
|
-
|
8
|
-
iterations = 1000
|
9
|
-
client = Mysql2::Client.new(:host => "localhost", :username => "root", :database => "test")
|
10
|
-
query = lambda{ iterations.times{ client.query("SELECT mysql2_test.* FROM mysql2_test") } }
|
11
|
-
Benchmark.bmbm do |x|
|
12
|
-
x.report('select') do
|
13
|
-
query.call
|
14
|
-
end
|
15
|
-
x.report('rb_thread_select') do
|
16
|
-
thread = Thread.new{ sleep(10) }
|
17
|
-
query.call
|
18
|
-
thread.kill
|
19
|
-
end
|
20
|
-
end
|
data/spec/spec.opts
DELETED