mysql2 0.2.18 → 0.2.19b1
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 +2 -0
- data/.rbenv-version +1 -0
- data/Gemfile.lock +61 -0
- data/README.md +70 -4
- data/ext/mysql2/client.c +320 -61
- data/ext/mysql2/client.h +5 -3
- data/ext/mysql2/extconf.rb +6 -5
- data/ext/mysql2/result.c +91 -40
- data/ext/mysql2/result.h +1 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +42 -13
- data/lib/mysql2.rb +18 -3
- data/lib/mysql2/client.rb +39 -12
- data/lib/mysql2/em.rb +13 -3
- data/lib/mysql2/version.rb +1 -1
- data/spec/configuration.yml.example +11 -0
- data/spec/em/em_fiber_spec.rb +2 -2
- data/spec/em/em_spec.rb +68 -7
- data/spec/mysql2/client_spec.rb +114 -12
- data/spec/mysql2/error_spec.rb +2 -2
- data/spec/mysql2/result_spec.rb +57 -5
- data/spec/spec_helper.rb +3 -1
- data/tasks/rspec.rake +11 -1
- metadata +149 -149
data/.gitignore
CHANGED
data/.rbenv-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.9.3
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
mysql2 (0.2.19b1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
activemodel (3.2.1)
|
10
|
+
activesupport (= 3.2.1)
|
11
|
+
builder (~> 3.0.0)
|
12
|
+
activerecord (3.2.1)
|
13
|
+
activemodel (= 3.2.1)
|
14
|
+
activesupport (= 3.2.1)
|
15
|
+
arel (~> 3.0.0)
|
16
|
+
tzinfo (~> 0.3.29)
|
17
|
+
activesupport (3.2.1)
|
18
|
+
i18n (~> 0.6)
|
19
|
+
multi_json (~> 1.0)
|
20
|
+
addressable (2.2.6)
|
21
|
+
arel (3.0.0)
|
22
|
+
builder (3.0.0)
|
23
|
+
data_objects (0.10.8)
|
24
|
+
addressable (~> 2.1)
|
25
|
+
diff-lcs (1.1.3)
|
26
|
+
do_mysql (0.10.8)
|
27
|
+
data_objects (= 0.10.8)
|
28
|
+
eventmachine (0.12.10)
|
29
|
+
faker (1.0.1)
|
30
|
+
i18n (~> 0.4)
|
31
|
+
i18n (0.6.0)
|
32
|
+
multi_json (1.0.4)
|
33
|
+
mysql (2.8.1)
|
34
|
+
rake (0.8.7)
|
35
|
+
rake-compiler (0.7.9)
|
36
|
+
rake
|
37
|
+
rspec (2.8.0)
|
38
|
+
rspec-core (~> 2.8.0)
|
39
|
+
rspec-expectations (~> 2.8.0)
|
40
|
+
rspec-mocks (~> 2.8.0)
|
41
|
+
rspec-core (2.8.0)
|
42
|
+
rspec-expectations (2.8.0)
|
43
|
+
diff-lcs (~> 1.1.2)
|
44
|
+
rspec-mocks (2.8.0)
|
45
|
+
sequel (3.32.0)
|
46
|
+
tzinfo (0.3.31)
|
47
|
+
|
48
|
+
PLATFORMS
|
49
|
+
ruby
|
50
|
+
|
51
|
+
DEPENDENCIES
|
52
|
+
activerecord
|
53
|
+
do_mysql
|
54
|
+
eventmachine
|
55
|
+
faker
|
56
|
+
mysql
|
57
|
+
mysql2!
|
58
|
+
rake (= 0.8.7)
|
59
|
+
rake-compiler (~> 0.7.7)
|
60
|
+
rspec
|
61
|
+
sequel
|
data/README.md
CHANGED
@@ -6,7 +6,7 @@ This one is not.
|
|
6
6
|
|
7
7
|
It also forces the use of UTF-8 [or binary] for the connection [and all strings in 1.9, unless Encoding.default_internal is set then it'll convert from UTF-8 to that encoding] and uses encoding-aware MySQL API calls where it can.
|
8
8
|
|
9
|
-
The API consists of two
|
9
|
+
The API consists of two classes:
|
10
10
|
|
11
11
|
Mysql2::Client - your connection to the database
|
12
12
|
|
@@ -85,6 +85,43 @@ results.each(:as => :array) do |row|
|
|
85
85
|
end
|
86
86
|
```
|
87
87
|
|
88
|
+
## Connection options
|
89
|
+
|
90
|
+
You may set the following connection options in Mysql2::Client.new(...):
|
91
|
+
|
92
|
+
``` ruby
|
93
|
+
Mysql2::Client.new(
|
94
|
+
:host,
|
95
|
+
:username,
|
96
|
+
:password,
|
97
|
+
:port,
|
98
|
+
:database,
|
99
|
+
:socket = '/path/to/mysql.sock',
|
100
|
+
:flags = REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION | MULTI_STATEMENTS,
|
101
|
+
:encoding = 'utf8',
|
102
|
+
:read_timeout = seconds,
|
103
|
+
:connect_timeout = seconds,
|
104
|
+
:reconnect = true/false,
|
105
|
+
:local_infile = true/false,
|
106
|
+
)
|
107
|
+
```
|
108
|
+
|
109
|
+
You can also retrieve multiple result sets. For this to work you need to connect with
|
110
|
+
flags `Mysql2::Client::MULTI_STATEMENTS`. Using multiple result sets is normally used
|
111
|
+
when calling stored procedures that return more than one result set
|
112
|
+
|
113
|
+
``` ruby
|
114
|
+
client = Mysql2::Client.new(:host => "localhost", :username => "root", :flags => Mysql2::Client::MULTI_STATEMENTS )
|
115
|
+
result = client.query( 'CALL sp_customer_list( 25, 10 )')
|
116
|
+
# result now contains the first result set
|
117
|
+
while ( client.next_result)
|
118
|
+
result = client.store_result
|
119
|
+
# result now contains the next result set
|
120
|
+
end
|
121
|
+
```
|
122
|
+
|
123
|
+
See https://gist.github.com/1367987 for using MULTI_STATEMENTS with ActiveRecord.
|
124
|
+
|
88
125
|
## Cascading config
|
89
126
|
|
90
127
|
The default config hash is at:
|
@@ -212,9 +249,24 @@ This is especially helpful since it saves the cost of creating the row in Ruby i
|
|
212
249
|
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.
|
213
250
|
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.
|
214
251
|
|
252
|
+
### Streaming
|
253
|
+
|
254
|
+
`Mysql2::Client` can optionally only fetch rows from the server on demand by setting `:stream => true`. This is handy when handling very large result sets which might not fit in memory on the client.
|
255
|
+
|
256
|
+
``` ruby
|
257
|
+
result = client.query("SELECT * FROM really_big_Table", :stream => true)
|
258
|
+
```
|
259
|
+
|
260
|
+
There are a few things that need to be kept in mind while using streaming:
|
261
|
+
|
262
|
+
* `:cache_rows` is ignored currently. (if you want to use `:cache_rows` you probably don't want to be using `:stream`)
|
263
|
+
* You must fetch all rows in the result set of your query before you can make new queries. (i.e. with `Mysql2::Result#each`)
|
264
|
+
|
265
|
+
Read more about the consequences of using `mysql_use_result` (what streaming is implemented with) here: http://dev.mysql.com/doc/refman/5.0/en/mysql-use-result.html.
|
266
|
+
|
215
267
|
## ActiveRecord
|
216
268
|
|
217
|
-
To use the ActiveRecord driver (with
|
269
|
+
To use the ActiveRecord driver (with or without rails), all you should need to do is have this gem installed and set the adapter in your database.yml to "mysql2".
|
218
270
|
That was easy right? :)
|
219
271
|
|
220
272
|
NOTE: as of 0.3.0, and ActiveRecord 3.1 - the ActiveRecord adapter has been pulled out of this gem and into ActiveRecord itself. If you need to use mysql2 with
|
@@ -222,8 +274,7 @@ Rails versions < 3.1 make sure and specify `gem "mysql2", "~> 0.2.7"` in your Ge
|
|
222
274
|
|
223
275
|
## Asynchronous ActiveRecord
|
224
276
|
|
225
|
-
|
226
|
-
setting the adapter in your database.yml to "em_mysql2". You must be running Ruby 1.9, thin and the rack-fiber_pool middleware for it to work.
|
277
|
+
Please see the [em-synchrony](https://github.com/igrigorik/em-synchrony) project for details about using EventMachine with mysql2 and Rails.
|
227
278
|
|
228
279
|
## Sequel
|
229
280
|
|
@@ -318,6 +369,21 @@ bundle install
|
|
318
369
|
rake
|
319
370
|
```
|
320
371
|
|
372
|
+
The tests require the "test" database to exist, and expect to connect
|
373
|
+
both as root and the running user, both with a blank password:
|
374
|
+
|
375
|
+
``` sql
|
376
|
+
CREATE DATABASE test;
|
377
|
+
CREATE USER '<user>'@'localhost' IDENTIFIED BY '';
|
378
|
+
GRANT ALL PRIVILEGES ON test.* TO '<user>'@'localhost';
|
379
|
+
```
|
380
|
+
|
381
|
+
You can change these defaults in the spec/configuration.yml which is generated
|
382
|
+
automatically when you run rake (or explicitly `rake spec/configuration.yml`).
|
383
|
+
|
384
|
+
For a normal installation on a Mac, you most likely do not need to do anything,
|
385
|
+
though.
|
386
|
+
|
321
387
|
## Special Thanks
|
322
388
|
|
323
389
|
* Eric Wong - for the contribution (and the informative explanations) of some thread-safety, non-blocking I/O and cleanup patches. You rock dude
|
data/ext/mysql2/client.c
CHANGED
@@ -9,16 +9,28 @@
|
|
9
9
|
VALUE cMysql2Client;
|
10
10
|
extern VALUE mMysql2, cMysql2Error;
|
11
11
|
static VALUE intern_encoding_from_charset;
|
12
|
-
static VALUE sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as, sym_array;
|
12
|
+
static VALUE sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as, sym_array, sym_stream;
|
13
13
|
static ID intern_merge, intern_error_number_eql, intern_sql_state_eql;
|
14
14
|
|
15
|
-
#define
|
16
|
-
if(!wrapper->
|
15
|
+
#define REQUIRE_INITIALIZED(wrapper) \
|
16
|
+
if (!wrapper->initialized) { \
|
17
|
+
rb_raise(cMysql2Error, "MySQL client is not initialized"); \
|
18
|
+
}
|
19
|
+
|
20
|
+
#define REQUIRE_CONNECTED(wrapper) \
|
21
|
+
REQUIRE_INITIALIZED(wrapper) \
|
22
|
+
if (!wrapper->connected && !wrapper->reconnect_enabled) { \
|
17
23
|
rb_raise(cMysql2Error, "closed MySQL connection"); \
|
18
24
|
}
|
19
25
|
|
26
|
+
#define REQUIRE_NOT_CONNECTED(wrapper) \
|
27
|
+
REQUIRE_INITIALIZED(wrapper) \
|
28
|
+
if (wrapper->connected) { \
|
29
|
+
rb_raise(cMysql2Error, "MySQL connection is already open"); \
|
30
|
+
}
|
31
|
+
|
20
32
|
#define MARK_CONN_INACTIVE(conn) \
|
21
|
-
wrapper->
|
33
|
+
wrapper->active_thread = Qnil;
|
22
34
|
|
23
35
|
#define GET_CLIENT(self) \
|
24
36
|
mysql_client_wrapper *wrapper; \
|
@@ -51,6 +63,15 @@ struct nogvl_send_query_args {
|
|
51
63
|
mysql_client_wrapper *wrapper;
|
52
64
|
};
|
53
65
|
|
66
|
+
/*
|
67
|
+
* used to pass all arguments to mysql_select_db while inside
|
68
|
+
* rb_thread_blocking_region
|
69
|
+
*/
|
70
|
+
struct nogvl_select_db_args {
|
71
|
+
MYSQL *mysql;
|
72
|
+
char *db;
|
73
|
+
};
|
74
|
+
|
54
75
|
/*
|
55
76
|
* non-blocking mysql_*() functions that we won't be wrapping since
|
56
77
|
* they do not appear to hit the network nor issue any interruptible
|
@@ -77,6 +98,7 @@ static void rb_mysql_client_mark(void * wrapper) {
|
|
77
98
|
mysql_client_wrapper * w = wrapper;
|
78
99
|
if (w) {
|
79
100
|
rb_gc_mark(w->encoding);
|
101
|
+
rb_gc_mark(w->active_thread);
|
80
102
|
}
|
81
103
|
}
|
82
104
|
|
@@ -128,9 +150,9 @@ static VALUE nogvl_close(void *ptr) {
|
|
128
150
|
int flags;
|
129
151
|
#endif
|
130
152
|
wrapper = ptr;
|
131
|
-
if (
|
132
|
-
wrapper->
|
133
|
-
wrapper->
|
153
|
+
if (wrapper->connected) {
|
154
|
+
wrapper->active_thread = Qnil;
|
155
|
+
wrapper->connected = 0;
|
134
156
|
/*
|
135
157
|
* we'll send a QUIT message to the server, but that message is more of a
|
136
158
|
* formality than a hard requirement since the socket is getting shutdown
|
@@ -166,9 +188,10 @@ static VALUE allocate(VALUE klass) {
|
|
166
188
|
mysql_client_wrapper * wrapper;
|
167
189
|
obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper);
|
168
190
|
wrapper->encoding = Qnil;
|
169
|
-
wrapper->
|
191
|
+
wrapper->active_thread = Qnil;
|
170
192
|
wrapper->reconnect_enabled = 0;
|
171
|
-
wrapper->
|
193
|
+
wrapper->connected = 0; // means that a database connection is open
|
194
|
+
wrapper->initialized = 0; // means that that the wrapper is initialized
|
172
195
|
wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL));
|
173
196
|
return obj;
|
174
197
|
}
|
@@ -214,7 +237,7 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
|
|
214
237
|
|
215
238
|
rv = rb_thread_blocking_region(nogvl_connect, &args, RUBY_UBF_IO, 0);
|
216
239
|
if (rv == Qfalse) {
|
217
|
-
while (rv == Qfalse && errno == EINTR) {
|
240
|
+
while (rv == Qfalse && errno == EINTR && !mysql_errno(wrapper->client)) {
|
218
241
|
errno = 0;
|
219
242
|
rv = rb_thread_blocking_region(nogvl_connect, &args, RUBY_UBF_IO, 0);
|
220
243
|
}
|
@@ -222,6 +245,7 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
|
|
222
245
|
return rb_raise_mysql2_error(wrapper);
|
223
246
|
}
|
224
247
|
|
248
|
+
wrapper->connected = 1;
|
225
249
|
return self;
|
226
250
|
}
|
227
251
|
|
@@ -234,7 +258,7 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
|
|
234
258
|
static VALUE rb_mysql_client_close(VALUE self) {
|
235
259
|
GET_CLIENT(self);
|
236
260
|
|
237
|
-
if (
|
261
|
+
if (wrapper->connected) {
|
238
262
|
rb_thread_blocking_region(nogvl_close, wrapper, RUBY_UBF_IO, 0);
|
239
263
|
}
|
240
264
|
|
@@ -278,21 +302,38 @@ static VALUE nogvl_read_query_result(void *ptr) {
|
|
278
302
|
return res == 0 ? Qtrue : Qfalse;
|
279
303
|
}
|
280
304
|
|
281
|
-
|
282
|
-
static VALUE nogvl_store_result(void *ptr) {
|
305
|
+
static VALUE nogvl_do_result(void *ptr, char use_result) {
|
283
306
|
mysql_client_wrapper *wrapper;
|
284
307
|
MYSQL_RES *result;
|
285
308
|
|
286
309
|
wrapper = (mysql_client_wrapper *)ptr;
|
287
|
-
|
310
|
+
if(use_result) {
|
311
|
+
result = mysql_use_result(wrapper->client);
|
312
|
+
} else {
|
313
|
+
result = mysql_store_result(wrapper->client);
|
314
|
+
}
|
288
315
|
|
289
316
|
// once our result is stored off, this connection is
|
290
317
|
// ready for another command to be issued
|
291
|
-
wrapper->
|
318
|
+
wrapper->active_thread = Qnil;
|
292
319
|
|
293
320
|
return (VALUE)result;
|
294
321
|
}
|
295
322
|
|
323
|
+
/* mysql_store_result may (unlikely) read rows off the socket */
|
324
|
+
static VALUE nogvl_store_result(void *ptr) {
|
325
|
+
return nogvl_do_result(ptr, 0);
|
326
|
+
}
|
327
|
+
|
328
|
+
static VALUE nogvl_use_result(void *ptr) {
|
329
|
+
return nogvl_do_result(ptr, 1);
|
330
|
+
}
|
331
|
+
|
332
|
+
/* call-seq:
|
333
|
+
* client.async_result
|
334
|
+
*
|
335
|
+
* Returns the result for the last async issued query.
|
336
|
+
*/
|
296
337
|
static VALUE rb_mysql_client_async_result(VALUE self) {
|
297
338
|
MYSQL_RES * result;
|
298
339
|
VALUE resultObj;
|
@@ -302,17 +343,22 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
|
|
302
343
|
GET_CLIENT(self);
|
303
344
|
|
304
345
|
// if we're not waiting on a result, do nothing
|
305
|
-
if (
|
346
|
+
if (NIL_P(wrapper->active_thread))
|
306
347
|
return Qnil;
|
307
348
|
|
308
|
-
|
349
|
+
REQUIRE_CONNECTED(wrapper);
|
309
350
|
if (rb_thread_blocking_region(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
|
310
351
|
// an error occurred, mark this connection inactive
|
311
352
|
MARK_CONN_INACTIVE(self);
|
312
353
|
return rb_raise_mysql2_error(wrapper);
|
313
354
|
}
|
314
355
|
|
315
|
-
|
356
|
+
VALUE is_streaming = rb_hash_aref(rb_iv_get(self, "@query_options"), sym_stream);
|
357
|
+
if(is_streaming == Qtrue) {
|
358
|
+
result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_use_result, wrapper, RUBY_UBF_IO, 0);
|
359
|
+
} else {
|
360
|
+
result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper, RUBY_UBF_IO, 0);
|
361
|
+
}
|
316
362
|
|
317
363
|
if (result == NULL) {
|
318
364
|
if (mysql_errno(wrapper->client) != 0) {
|
@@ -343,8 +389,8 @@ struct async_query_args {
|
|
343
389
|
static VALUE disconnect_and_raise(VALUE self, VALUE error) {
|
344
390
|
GET_CLIENT(self);
|
345
391
|
|
346
|
-
wrapper->
|
347
|
-
wrapper->
|
392
|
+
wrapper->active_thread = Qnil;
|
393
|
+
wrapper->connected = 0;
|
348
394
|
|
349
395
|
// manually close the socket for read/write
|
350
396
|
// this feels dirty, but is there another way?
|
@@ -408,20 +454,26 @@ static VALUE finish_and_mark_inactive(void *args) {
|
|
408
454
|
|
409
455
|
GET_CLIENT(self);
|
410
456
|
|
411
|
-
if (wrapper->
|
457
|
+
if (!NIL_P(wrapper->active_thread)) {
|
412
458
|
// if we got here, the result hasn't been read off the wire yet
|
413
459
|
// so lets do that and then throw it away because we have no way
|
414
460
|
// of getting it back up to the caller from here
|
415
461
|
result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper, RUBY_UBF_IO, 0);
|
416
462
|
mysql_free_result(result);
|
417
463
|
|
418
|
-
wrapper->
|
464
|
+
wrapper->active_thread = Qnil;
|
419
465
|
}
|
420
466
|
|
421
467
|
return Qnil;
|
422
468
|
}
|
423
469
|
#endif
|
424
470
|
|
471
|
+
/* call-seq:
|
472
|
+
* client.query(sql, options = {})
|
473
|
+
*
|
474
|
+
* Query the database with +sql+, with optional +options+. For the possible
|
475
|
+
* options, see @@default_query_options on the Mysql2::Client class.
|
476
|
+
*/
|
425
477
|
static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
|
426
478
|
#ifndef _WIN32
|
427
479
|
struct async_query_args async_args;
|
@@ -429,12 +481,13 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
|
|
429
481
|
struct nogvl_send_query_args args;
|
430
482
|
int async = 0;
|
431
483
|
VALUE opts, defaults;
|
484
|
+
VALUE thread_current = rb_thread_current();
|
432
485
|
#ifdef HAVE_RUBY_ENCODING_H
|
433
486
|
rb_encoding *conn_enc;
|
434
487
|
#endif
|
435
488
|
GET_CLIENT(self);
|
436
489
|
|
437
|
-
|
490
|
+
REQUIRE_CONNECTED(wrapper);
|
438
491
|
args.mysql = wrapper->client;
|
439
492
|
|
440
493
|
|
@@ -460,11 +513,17 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
|
|
460
513
|
args.sql_len = RSTRING_LEN(args.sql);
|
461
514
|
|
462
515
|
// see if this connection is still waiting on a result from a previous query
|
463
|
-
if (wrapper->
|
516
|
+
if (NIL_P(wrapper->active_thread)) {
|
464
517
|
// mark this connection active
|
465
|
-
wrapper->
|
466
|
-
} else {
|
518
|
+
wrapper->active_thread = thread_current;
|
519
|
+
} else if (wrapper->active_thread == thread_current) {
|
467
520
|
rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result");
|
521
|
+
} else {
|
522
|
+
VALUE inspect = rb_inspect(wrapper->active_thread);
|
523
|
+
const char *thr = StringValueCStr(inspect);
|
524
|
+
|
525
|
+
rb_raise(cMysql2Error, "This connection is in use by: %s", thr);
|
526
|
+
RB_GC_GUARD(inspect);
|
468
527
|
}
|
469
528
|
|
470
529
|
args.wrapper = wrapper;
|
@@ -490,6 +549,11 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
|
|
490
549
|
#endif
|
491
550
|
}
|
492
551
|
|
552
|
+
/* call-seq:
|
553
|
+
* client.escape(string)
|
554
|
+
*
|
555
|
+
* Escape +string+ so that it may be used in a SQL statement.
|
556
|
+
*/
|
493
557
|
static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) {
|
494
558
|
unsigned char *newStr;
|
495
559
|
VALUE rb_str;
|
@@ -500,7 +564,7 @@ static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) {
|
|
500
564
|
#endif
|
501
565
|
GET_CLIENT(self);
|
502
566
|
|
503
|
-
|
567
|
+
REQUIRE_CONNECTED(wrapper);
|
504
568
|
Check_Type(str, T_STRING);
|
505
569
|
#ifdef HAVE_RUBY_ENCODING_H
|
506
570
|
default_internal_enc = rb_default_internal_encoding();
|
@@ -530,6 +594,69 @@ static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) {
|
|
530
594
|
}
|
531
595
|
}
|
532
596
|
|
597
|
+
static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) {
|
598
|
+
int result;
|
599
|
+
void *retval = NULL;
|
600
|
+
unsigned int intval = 0;
|
601
|
+
my_bool boolval;
|
602
|
+
|
603
|
+
GET_CLIENT(self);
|
604
|
+
|
605
|
+
REQUIRE_NOT_CONNECTED(wrapper);
|
606
|
+
|
607
|
+
if (NIL_P(value))
|
608
|
+
return Qfalse;
|
609
|
+
|
610
|
+
switch(opt) {
|
611
|
+
case MYSQL_OPT_CONNECT_TIMEOUT:
|
612
|
+
intval = NUM2INT(value);
|
613
|
+
retval = &intval;
|
614
|
+
break;
|
615
|
+
|
616
|
+
case MYSQL_OPT_READ_TIMEOUT:
|
617
|
+
intval = NUM2INT(value);
|
618
|
+
retval = &intval;
|
619
|
+
break;
|
620
|
+
|
621
|
+
case MYSQL_OPT_LOCAL_INFILE:
|
622
|
+
intval = (value == Qfalse ? 0 : 1);
|
623
|
+
retval = &intval;
|
624
|
+
break;
|
625
|
+
|
626
|
+
case MYSQL_OPT_RECONNECT:
|
627
|
+
boolval = (value == Qfalse ? 0 : 1);
|
628
|
+
retval = &boolval;
|
629
|
+
break;
|
630
|
+
|
631
|
+
default:
|
632
|
+
return Qfalse;
|
633
|
+
}
|
634
|
+
|
635
|
+
result = mysql_options(wrapper->client, opt, retval);
|
636
|
+
|
637
|
+
// Zero means success
|
638
|
+
if (result != 0) {
|
639
|
+
rb_warn("%s\n", mysql_error(wrapper->client));
|
640
|
+
} else {
|
641
|
+
// Special case for reconnect, this option is also stored in the wrapper struct
|
642
|
+
if (opt == MYSQL_OPT_RECONNECT)
|
643
|
+
wrapper->reconnect_enabled = boolval;
|
644
|
+
}
|
645
|
+
|
646
|
+
return (result == 0) ? Qtrue : Qfalse;
|
647
|
+
}
|
648
|
+
|
649
|
+
static VALUE rb_mysql_client_options(VALUE self, VALUE option, VALUE value) {
|
650
|
+
Check_Type(option, T_FIXNUM);
|
651
|
+
int opt = NUM2INT(option);
|
652
|
+
return _mysql_client_options(self, opt, value);
|
653
|
+
}
|
654
|
+
|
655
|
+
/* call-seq:
|
656
|
+
* client.info
|
657
|
+
*
|
658
|
+
* Returns a string that represents the client library version.
|
659
|
+
*/
|
533
660
|
static VALUE rb_mysql_client_info(VALUE self) {
|
534
661
|
VALUE version, client_info;
|
535
662
|
#ifdef HAVE_RUBY_ENCODING_H
|
@@ -556,6 +683,11 @@ static VALUE rb_mysql_client_info(VALUE self) {
|
|
556
683
|
return version;
|
557
684
|
}
|
558
685
|
|
686
|
+
/* call-seq:
|
687
|
+
* client.server_info
|
688
|
+
*
|
689
|
+
* Returns a string that represents the server version number
|
690
|
+
*/
|
559
691
|
static VALUE rb_mysql_client_server_info(VALUE self) {
|
560
692
|
VALUE version, server_info;
|
561
693
|
#ifdef HAVE_RUBY_ENCODING_H
|
@@ -564,7 +696,7 @@ static VALUE rb_mysql_client_server_info(VALUE self) {
|
|
564
696
|
#endif
|
565
697
|
GET_CLIENT(self);
|
566
698
|
|
567
|
-
|
699
|
+
REQUIRE_CONNECTED(wrapper);
|
568
700
|
#ifdef HAVE_RUBY_ENCODING_H
|
569
701
|
default_internal_enc = rb_default_internal_encoding();
|
570
702
|
conn_enc = rb_to_encoding(wrapper->encoding);
|
@@ -583,10 +715,15 @@ static VALUE rb_mysql_client_server_info(VALUE self) {
|
|
583
715
|
return version;
|
584
716
|
}
|
585
717
|
|
718
|
+
/* call-seq:
|
719
|
+
* client.socket
|
720
|
+
*
|
721
|
+
* Return the file descriptor number for this client.
|
722
|
+
*/
|
586
723
|
static VALUE rb_mysql_client_socket(VALUE self) {
|
587
724
|
GET_CLIENT(self);
|
588
725
|
#ifndef _WIN32
|
589
|
-
|
726
|
+
REQUIRE_CONNECTED(wrapper);
|
590
727
|
int fd_set_fd = wrapper->client->net.fd;
|
591
728
|
return INT2NUM(fd_set_fd);
|
592
729
|
#else
|
@@ -594,17 +731,29 @@ static VALUE rb_mysql_client_socket(VALUE self) {
|
|
594
731
|
#endif
|
595
732
|
}
|
596
733
|
|
734
|
+
/* call-seq:
|
735
|
+
* client.last_id
|
736
|
+
*
|
737
|
+
* Returns the value generated for an AUTO_INCREMENT column by the previous INSERT or UPDATE
|
738
|
+
* statement.
|
739
|
+
*/
|
597
740
|
static VALUE rb_mysql_client_last_id(VALUE self) {
|
598
741
|
GET_CLIENT(self);
|
599
|
-
|
742
|
+
REQUIRE_CONNECTED(wrapper);
|
600
743
|
return ULL2NUM(mysql_insert_id(wrapper->client));
|
601
744
|
}
|
602
745
|
|
746
|
+
/* call-seq:
|
747
|
+
* client.affected_rows
|
748
|
+
*
|
749
|
+
* returns the number of rows changed, deleted, or inserted by the last statement
|
750
|
+
* if it was an UPDATE, DELETE, or INSERT.
|
751
|
+
*/
|
603
752
|
static VALUE rb_mysql_client_affected_rows(VALUE self) {
|
604
753
|
my_ulonglong retVal;
|
605
754
|
GET_CLIENT(self);
|
606
755
|
|
607
|
-
|
756
|
+
REQUIRE_CONNECTED(wrapper);
|
608
757
|
retVal = mysql_affected_rows(wrapper->client);
|
609
758
|
if (retVal == (my_ulonglong)-1) {
|
610
759
|
rb_raise_mysql2_error(wrapper);
|
@@ -612,32 +761,139 @@ static VALUE rb_mysql_client_affected_rows(VALUE self) {
|
|
612
761
|
return ULL2NUM(retVal);
|
613
762
|
}
|
614
763
|
|
764
|
+
/* call-seq:
|
765
|
+
* client.thread_id
|
766
|
+
*
|
767
|
+
* Returns the thread ID of the current connection.
|
768
|
+
*/
|
615
769
|
static VALUE rb_mysql_client_thread_id(VALUE self) {
|
616
770
|
unsigned long retVal;
|
617
771
|
GET_CLIENT(self);
|
618
772
|
|
619
|
-
|
773
|
+
REQUIRE_CONNECTED(wrapper);
|
620
774
|
retVal = mysql_thread_id(wrapper->client);
|
621
775
|
return ULL2NUM(retVal);
|
622
776
|
}
|
623
777
|
|
778
|
+
static VALUE nogvl_select_db(void *ptr) {
|
779
|
+
struct nogvl_select_db_args *args = ptr;
|
780
|
+
|
781
|
+
if (mysql_select_db(args->mysql, args->db) == 0)
|
782
|
+
return Qtrue;
|
783
|
+
else
|
784
|
+
return Qfalse;
|
785
|
+
}
|
786
|
+
|
787
|
+
/* call-seq:
|
788
|
+
* client.select_db(name)
|
789
|
+
*
|
790
|
+
* Causes the database specified by +name+ to become the default (current)
|
791
|
+
* database on the connection specified by mysql.
|
792
|
+
*/
|
793
|
+
static VALUE rb_mysql_client_select_db(VALUE self, VALUE db)
|
794
|
+
{
|
795
|
+
struct nogvl_select_db_args args;
|
796
|
+
|
797
|
+
GET_CLIENT(self);
|
798
|
+
REQUIRE_CONNECTED(wrapper);
|
799
|
+
|
800
|
+
args.mysql = wrapper->client;
|
801
|
+
args.db = StringValuePtr(db);
|
802
|
+
|
803
|
+
if (rb_thread_blocking_region(nogvl_select_db, &args, RUBY_UBF_IO, 0) == Qfalse)
|
804
|
+
rb_raise_mysql2_error(wrapper);
|
805
|
+
|
806
|
+
return db;
|
807
|
+
}
|
808
|
+
|
624
809
|
static VALUE nogvl_ping(void *ptr) {
|
625
810
|
MYSQL *client = ptr;
|
626
811
|
|
627
812
|
return mysql_ping(client) == 0 ? Qtrue : Qfalse;
|
628
813
|
}
|
629
814
|
|
815
|
+
/* call-seq:
|
816
|
+
* client.ping
|
817
|
+
*
|
818
|
+
* Checks whether the connection to the server is working. If the connection
|
819
|
+
* has gone down and auto-reconnect is enabled an attempt to reconnect is made.
|
820
|
+
* If the connection is down and auto-reconnect is disabled, ping returns an
|
821
|
+
* error.
|
822
|
+
*/
|
630
823
|
static VALUE rb_mysql_client_ping(VALUE self) {
|
631
824
|
GET_CLIENT(self);
|
632
825
|
|
633
|
-
if (wrapper->
|
826
|
+
if (!wrapper->connected) {
|
634
827
|
return Qfalse;
|
635
828
|
} else {
|
636
829
|
return rb_thread_blocking_region(nogvl_ping, wrapper->client, RUBY_UBF_IO, 0);
|
637
830
|
}
|
638
831
|
}
|
639
832
|
|
833
|
+
static VALUE rb_mysql_client_more_results(VALUE self)
|
834
|
+
{
|
835
|
+
GET_CLIENT(self);
|
836
|
+
if (mysql_more_results(wrapper->client) == 0)
|
837
|
+
return Qfalse;
|
838
|
+
else
|
839
|
+
return Qtrue;
|
840
|
+
}
|
841
|
+
|
842
|
+
static VALUE rb_mysql_client_next_result(VALUE self)
|
843
|
+
{
|
844
|
+
GET_CLIENT(self);
|
845
|
+
int ret;
|
846
|
+
ret = mysql_next_result(wrapper->client);
|
847
|
+
if (ret == 0)
|
848
|
+
return Qtrue;
|
849
|
+
else
|
850
|
+
return Qfalse;
|
851
|
+
}
|
852
|
+
|
853
|
+
|
854
|
+
static VALUE rb_mysql_client_store_result(VALUE self)
|
855
|
+
{
|
856
|
+
MYSQL_RES * result;
|
857
|
+
VALUE resultObj;
|
858
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
859
|
+
mysql2_result_wrapper * result_wrapper;
|
860
|
+
#endif
|
861
|
+
|
862
|
+
|
863
|
+
GET_CLIENT(self);
|
864
|
+
// MYSQL_RES* res = mysql_store_result(wrapper->client);
|
865
|
+
// if (res == NULL)
|
866
|
+
// mysql_raise(wrapper->client);
|
867
|
+
// return mysqlres2obj(res);
|
868
|
+
|
869
|
+
result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper, RUBY_UBF_IO, 0);
|
870
|
+
|
871
|
+
if (result == NULL) {
|
872
|
+
if (mysql_errno(wrapper->client) != 0) {
|
873
|
+
rb_raise_mysql2_error(wrapper);
|
874
|
+
}
|
875
|
+
// no data and no error, so query was not a SELECT
|
876
|
+
return Qnil;
|
877
|
+
}
|
878
|
+
|
879
|
+
resultObj = rb_mysql_result_to_obj(result);
|
880
|
+
// pass-through query options for result construction later
|
881
|
+
rb_iv_set(resultObj, "@query_options", rb_funcall(rb_iv_get(self, "@query_options"), rb_intern("dup"), 0));
|
882
|
+
|
883
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
884
|
+
GetMysql2Result(resultObj, result_wrapper);
|
885
|
+
result_wrapper->encoding = wrapper->encoding;
|
886
|
+
#endif
|
887
|
+
return resultObj;
|
888
|
+
|
889
|
+
}
|
890
|
+
|
640
891
|
#ifdef HAVE_RUBY_ENCODING_H
|
892
|
+
/* call-seq:
|
893
|
+
* client.encoding
|
894
|
+
*
|
895
|
+
* Returns the encoding set on the client.
|
896
|
+
*/
|
641
897
|
static VALUE rb_mysql_client_encoding(VALUE self) {
|
642
898
|
GET_CLIENT(self);
|
643
899
|
return wrapper->encoding;
|
@@ -645,37 +901,29 @@ static VALUE rb_mysql_client_encoding(VALUE self) {
|
|
645
901
|
#endif
|
646
902
|
|
647
903
|
static VALUE set_reconnect(VALUE self, VALUE value) {
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
if(!NIL_P(value)) {
|
652
|
-
reconnect = value == Qfalse ? 0 : 1;
|
904
|
+
return _mysql_client_options(self, MYSQL_OPT_RECONNECT, value);
|
905
|
+
}
|
653
906
|
|
654
|
-
|
655
|
-
|
656
|
-
if (mysql_options(wrapper->client, MYSQL_OPT_RECONNECT, &reconnect)) {
|
657
|
-
/* TODO: warning - unable to set reconnect behavior */
|
658
|
-
rb_warn("%s\n", mysql_error(wrapper->client));
|
659
|
-
}
|
660
|
-
}
|
661
|
-
return value;
|
907
|
+
static VALUE set_local_infile(VALUE self, VALUE value) {
|
908
|
+
return _mysql_client_options(self, MYSQL_OPT_LOCAL_INFILE, value);
|
662
909
|
}
|
663
910
|
|
664
911
|
static VALUE set_connect_timeout(VALUE self, VALUE value) {
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
if(!NIL_P(value)) {
|
669
|
-
connect_timeout = NUM2INT(value);
|
670
|
-
if(0 == connect_timeout) return value;
|
912
|
+
return _mysql_client_options(self, MYSQL_OPT_CONNECT_TIMEOUT, value);
|
913
|
+
}
|
671
914
|
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
915
|
+
static VALUE set_read_timeout(VALUE self, VALUE value) {
|
916
|
+
long int sec;
|
917
|
+
Check_Type(value, T_FIXNUM);
|
918
|
+
sec = FIX2INT(value);
|
919
|
+
if (sec < 0) {
|
920
|
+
rb_raise(cMysql2Error, "read_timeout must be a positive integer, you passed %ld", sec);
|
677
921
|
}
|
678
|
-
|
922
|
+
// Set the instance variable here even though _mysql_client_options
|
923
|
+
// might not succeed, because the timeout is used in other ways
|
924
|
+
// elsewhere
|
925
|
+
rb_iv_set(self, "@read_timeout", value);
|
926
|
+
return _mysql_client_options(self, MYSQL_OPT_READ_TIMEOUT, value);
|
679
927
|
}
|
680
928
|
|
681
929
|
static VALUE set_charset_name(VALUE self, VALUE value) {
|
@@ -722,7 +970,7 @@ static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE
|
|
722
970
|
return self;
|
723
971
|
}
|
724
972
|
|
725
|
-
static VALUE
|
973
|
+
static VALUE initialize_ext(VALUE self) {
|
726
974
|
GET_CLIENT(self);
|
727
975
|
|
728
976
|
if (rb_thread_blocking_region(nogvl_init, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
|
@@ -730,7 +978,7 @@ static VALUE init_connection(VALUE self) {
|
|
730
978
|
return rb_raise_mysql2_error(wrapper);
|
731
979
|
}
|
732
980
|
|
733
|
-
wrapper->
|
981
|
+
wrapper->initialized = 1;
|
734
982
|
return self;
|
735
983
|
}
|
736
984
|
|
@@ -752,6 +1000,9 @@ void init_mysql2_client() {
|
|
752
1000
|
}
|
753
1001
|
}
|
754
1002
|
|
1003
|
+
#if 0
|
1004
|
+
mMysql2 = rb_define_module("Mysql2"); Teach RDoc about Mysql2 constant.
|
1005
|
+
#endif
|
755
1006
|
cMysql2Client = rb_define_class_under(mMysql2, "Client", rb_cObject);
|
756
1007
|
|
757
1008
|
rb_define_alloc_func(cMysql2Client, allocate);
|
@@ -769,15 +1020,22 @@ void init_mysql2_client() {
|
|
769
1020
|
rb_define_method(cMysql2Client, "affected_rows", rb_mysql_client_affected_rows, 0);
|
770
1021
|
rb_define_method(cMysql2Client, "thread_id", rb_mysql_client_thread_id, 0);
|
771
1022
|
rb_define_method(cMysql2Client, "ping", rb_mysql_client_ping, 0);
|
1023
|
+
rb_define_method(cMysql2Client, "select_db", rb_mysql_client_select_db, 1);
|
1024
|
+
rb_define_method(cMysql2Client, "more_results", rb_mysql_client_more_results, 0);
|
1025
|
+
rb_define_method(cMysql2Client, "next_result", rb_mysql_client_next_result, 0);
|
1026
|
+
rb_define_method(cMysql2Client, "store_result", rb_mysql_client_store_result, 0);
|
1027
|
+
rb_define_method(cMysql2Client, "options", rb_mysql_client_options, 2);
|
772
1028
|
#ifdef HAVE_RUBY_ENCODING_H
|
773
1029
|
rb_define_method(cMysql2Client, "encoding", rb_mysql_client_encoding, 0);
|
774
1030
|
#endif
|
775
1031
|
|
776
1032
|
rb_define_private_method(cMysql2Client, "reconnect=", set_reconnect, 1);
|
777
1033
|
rb_define_private_method(cMysql2Client, "connect_timeout=", set_connect_timeout, 1);
|
1034
|
+
rb_define_private_method(cMysql2Client, "read_timeout=", set_read_timeout, 1);
|
1035
|
+
rb_define_private_method(cMysql2Client, "local_infile=", set_local_infile, 1);
|
778
1036
|
rb_define_private_method(cMysql2Client, "charset_name=", set_charset_name, 1);
|
779
1037
|
rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5);
|
780
|
-
rb_define_private_method(cMysql2Client, "
|
1038
|
+
rb_define_private_method(cMysql2Client, "initialize_ext", initialize_ext, 0);
|
781
1039
|
rb_define_private_method(cMysql2Client, "connect", rb_connect, 7);
|
782
1040
|
|
783
1041
|
intern_encoding_from_charset = rb_intern("encoding_from_charset");
|
@@ -788,6 +1046,7 @@ void init_mysql2_client() {
|
|
788
1046
|
sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys"));
|
789
1047
|
sym_as = ID2SYM(rb_intern("as"));
|
790
1048
|
sym_array = ID2SYM(rb_intern("array"));
|
1049
|
+
sym_stream = ID2SYM(rb_intern("stream"));
|
791
1050
|
|
792
1051
|
intern_merge = rb_intern("merge");
|
793
1052
|
intern_error_number_eql = rb_intern("error_number=");
|