mysql2 0.2.18 → 0.2.19b1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
+ bin/
1
2
  Makefile
2
3
  *.dSYM
3
4
  *.o
@@ -10,3 +11,4 @@ pkg/
10
11
  tmp
11
12
  vendor
12
13
  lib/mysql2/mysql2.rb
14
+ spec/configuration.yml
@@ -0,0 +1 @@
1
+ 1.9.3
@@ -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 clases:
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 our without rails), all you should need to do is have this gem installed and set the adapter in your database.yml to "mysql2".
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
- You can also use Mysql2 with asynchronous Rails (first introduced at http://www.mikeperham.com/2010/04/03/introducing-phat-an-asynchronous-rails-app/) by
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
@@ -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 REQUIRE_OPEN_DB(wrapper) \
16
- if(!wrapper->reconnect_enabled && wrapper->closed) { \
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->active = 0
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 (!wrapper->closed) {
132
- wrapper->closed = 1;
133
- wrapper->active = 0;
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->active = 0;
191
+ wrapper->active_thread = Qnil;
170
192
  wrapper->reconnect_enabled = 0;
171
- wrapper->closed = 1;
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 (!wrapper->closed) {
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
- /* mysql_store_result may (unlikely) read rows off the socket */
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
- result = mysql_store_result(wrapper->client);
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->active = 0;
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 (!wrapper->active)
346
+ if (NIL_P(wrapper->active_thread))
306
347
  return Qnil;
307
348
 
308
- REQUIRE_OPEN_DB(wrapper);
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
- result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper, RUBY_UBF_IO, 0);
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->closed = 1;
347
- wrapper->active = 0;
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->active) {
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->active = 0;
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
- REQUIRE_OPEN_DB(wrapper);
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->active == 0) {
516
+ if (NIL_P(wrapper->active_thread)) {
464
517
  // mark this connection active
465
- wrapper->active = 1;
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
- REQUIRE_OPEN_DB(wrapper);
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
- REQUIRE_OPEN_DB(wrapper);
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
- REQUIRE_OPEN_DB(wrapper);
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
- REQUIRE_OPEN_DB(wrapper);
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
- REQUIRE_OPEN_DB(wrapper);
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
- REQUIRE_OPEN_DB(wrapper);
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->closed) {
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
- my_bool reconnect;
649
- GET_CLIENT(self);
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
- wrapper->reconnect_enabled = reconnect;
655
- /* set default reconnect behavior */
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
- unsigned int connect_timeout = 0;
666
- GET_CLIENT(self);
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
- /* set default connection timeout behavior */
673
- if (mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout)) {
674
- /* TODO: warning - unable to set connection timeout */
675
- rb_warn("%s\n", mysql_error(wrapper->client));
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
- return value;
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 init_connection(VALUE self) {
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->closed = 0;
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, "init_connection", init_connection, 0);
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=");