mysql2 0.2.3 → 0.2.5
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/.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