mysql2 0.3.2 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.3 (June 14th, 2011)
4
+ * disable async support, and access to the underlying file descriptor under Windows. It's never worked reliably and ruby-core has a lot of work to do in order to make it possible.
5
+ * added support for turning eager-casting off. This is especially useful in ORMs that will lazily cast values upon access.
6
+ * added a warning if a 0.2.x release is being used with ActiveRecord 3.1 since both the 0.2.x releases and AR 3.1 have mysql2 adapters, we want you to use the one in AR 3.1
7
+ * added Mysql2::Client.escape (class-level method)
8
+ * disabled eager-casting in the bundled ActiveRecord adapter (for Rails 3.0 or less)
9
+
3
10
  ## 0.3.2 (April 26th, 2011)
4
11
  * Fix typo in initialization for older ActiveRecord versions
5
12
 
@@ -12,6 +19,13 @@
12
19
  * BREAKING CHANGE: the ActiveRecord adapter has been pulled into Rails 3.1 and is no longer part of the gem
13
20
  * added Mysql2::Client.escape (class-level) for raw one-off non-encoding-aware escaping
14
21
 
22
+ ## 0.2.8 (June 14th, 2011)
23
+ * disable async support, and access to the underlying file descriptor under Windows. It's never worked reliably and ruby-core has a lot of work to do in order to make it possible.
24
+ * added support for turning eager-casting off. This is especially useful in ORMs that will lazily cast values upon access.
25
+ * added a warning if a 0.2.x release is being used with ActiveRecord 3.1 since both the 0.2.x releases and AR 3.1 have mysql2 adapters, we want you to use the one in AR 3.1
26
+ * added Mysql2::Client.escape (class-level method)
27
+ * disabled eager-casting in the bundled ActiveRecord adapter (for Rails 3.0 or less)
28
+
15
29
  ## 0.2.7 (March 28th, 2011)
16
30
  * various fixes for em_mysql2 and fiber usage
17
31
  * use our own Mysql2IndexDefinition class for better compatibility across ActiveRecord versions
@@ -151,4 +165,4 @@
151
165
  * updated extconf (thanks to the mysqlplus project) for easier gem building
152
166
 
153
167
  ## 0.1.0 (April 6th, 2010)
154
- * initial release
168
+ * initial release
data/README.md ADDED
@@ -0,0 +1,324 @@
1
+ # Mysql2 - A modern, simple and very fast Mysql library for Ruby - binding to libmysql
2
+
3
+ The Mysql2 gem is meant to serve the extremely common use-case of connecting, querying and iterating on results.
4
+ Some database libraries out there serve as direct 1:1 mappings of the already complex C API's available.
5
+ This one is not.
6
+
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
+
9
+ The API consists of two clases:
10
+
11
+ Mysql2::Client - your connection to the database
12
+
13
+ Mysql2::Result - returned from issuing a #query on the connection. It includes Enumerable.
14
+
15
+ ## Installing
16
+
17
+ ``` sh
18
+ gem install mysql2
19
+ ```
20
+
21
+ You may have to specify --with-mysql-config=/some/random/path/bin/mysql_config
22
+
23
+ ## Usage
24
+
25
+ Connect to a database:
26
+
27
+ ``` ruby
28
+ # this takes a hash of options, almost all of which map directly
29
+ # to the familiar database.yml in rails
30
+ # See http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/MysqlAdapter.html
31
+ client = Mysql2::Client.new(:host => "localhost", :username => "root")
32
+ ```
33
+
34
+ Then query it:
35
+
36
+ ``` ruby
37
+ results = client.query("SELECT * FROM users WHERE group='githubbers'")
38
+ ```
39
+
40
+ Need to escape something first?
41
+
42
+ ``` ruby
43
+ escaped = client.escape("gi'thu\"bbe\0r's")
44
+ results = client.query("SELECT * FROM users WHERE group='#{escaped}'")
45
+ ```
46
+
47
+ Finally, iterate over the results:
48
+
49
+ ``` ruby
50
+ results.each do |row|
51
+ # conveniently, row is a hash
52
+ # the keys are the fields, as you'd expect
53
+ # the values are pre-built ruby primitives mapped from their corresponding field types in MySQL
54
+ # Here's an otter: http://farm1.static.flickr.com/130/398077070_b8795d0ef3_b.jpg
55
+ end
56
+ ```
57
+
58
+ Or, you might just keep it simple:
59
+
60
+ ``` ruby
61
+ client.query("SELECT * FROM users WHERE group='githubbers'").each do |row|
62
+ # do something with row, it's ready to rock
63
+ end
64
+ ```
65
+
66
+ How about with symbolized keys?
67
+
68
+ ``` ruby
69
+ # NOTE: the :symbolize_keys and future options will likely move to the #query method soon
70
+ client.query("SELECT * FROM users WHERE group='githubbers'").each(:symbolize_keys => true) do |row|
71
+ # do something with row, it's ready to rock
72
+ end
73
+ ```
74
+
75
+ You can get the headers and the columns in the order that they were returned
76
+ by the query like this:
77
+
78
+ ``` ruby
79
+ headers = results.fields # <= that's an array of field names, in order
80
+ results.each(:as => :array) do |row|
81
+ # Each row is an array, ordered the same as the query results
82
+ # An otter's den is called a "holt" or "couch"
83
+ end
84
+ ```
85
+
86
+ ## Cascading config
87
+
88
+ The default config hash is at:
89
+
90
+ ``` ruby
91
+ Mysql2::Client.default_query_options
92
+ ```
93
+
94
+ which defaults to:
95
+
96
+ ``` ruby
97
+ {:async => false, :as => :hash, :symbolize_keys => false}
98
+ ```
99
+
100
+ that can be used as so:
101
+
102
+ ``` ruby
103
+ # these are the defaults all Mysql2::Client instances inherit
104
+ Mysql2::Client.default_query_options.merge!(:as => :array)
105
+ ```
106
+
107
+ or
108
+
109
+ ``` ruby
110
+ # this will change the defaults for all future results returned by the #query method _for this connection only_
111
+ c = Mysql2::Client.new
112
+ c.query_options.merge!(:symbolize_keys => true)
113
+ ```
114
+
115
+ or
116
+
117
+ ``` ruby
118
+ # this will set the options for the Mysql2::Result instance returned from the #query method
119
+ c = Mysql2::Client.new
120
+ c.query(sql, :symbolize_keys => true)
121
+ ```
122
+
123
+ ## Result types
124
+
125
+ ### Array of Arrays
126
+
127
+ Pass the `:as => :array` option to any of the above methods of configuration
128
+
129
+ ### Array of Hashes
130
+
131
+ The default result type is set to :hash, but you can override a previous setting to something else with :as => :hash
132
+
133
+ ### Others...
134
+
135
+ 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.
136
+ If you'd like to see either of these (or others), open an issue and start bugging me about it ;)
137
+
138
+ ### Timezones
139
+
140
+ Mysql2 now supports two timezone options:
141
+
142
+ ``` ruby
143
+ :database_timezone # this is the timezone Mysql2 will assume fields are already stored as, and will use this when creating the initial Time objects in ruby
144
+ :application_timezone # this is the timezone Mysql2 will convert to before finally handing back to the caller
145
+ ```
146
+
147
+ In other words, if `:database_timezone` is set to `:utc` - Mysql2 will create the Time objects using `Time.utc(...)` from the raw value libmysql hands over initially.
148
+ Then, if `:application_timezone` is set to say - `:local` - Mysql2 will then convert the just-created UTC Time object to local time.
149
+
150
+ Both options only allow two values - `:local` or `:utc` - with the exception that `:application_timezone` can be [and defaults to] nil
151
+
152
+ ### Casting "boolean" columns
153
+
154
+ You can now tell Mysql2 to cast `tinyint(1)` fields to boolean values in Ruby with the `:cast_booleans` option.
155
+
156
+ ``` ruby
157
+ client = Mysql2::Client.new
158
+ result = client.query("SELECT * FROM table_with_boolean_field", :cast_booleans => true)
159
+ ```
160
+
161
+ ### Skipping casting
162
+
163
+ Mysql2 casting is fast, but not as fast as not casting data. In rare cases where typecasting is not needed, it will be faster to disable it by providing :cast => false.
164
+
165
+ ``` ruby
166
+ client = Mysql2::Client.new
167
+ result = client.query("SELECT * FROM table", :cast => false)
168
+ ```
169
+
170
+ Here are the results from the `query_without_mysql_casting.rb` script in the benchmarks folder:
171
+
172
+ ``` sh
173
+ user system total real
174
+ Mysql2 (cast: true) 0.340000 0.000000 0.340000 ( 0.405018)
175
+ Mysql2 (cast: false) 0.160000 0.010000 0.170000 ( 0.209937)
176
+ Mysql 0.080000 0.000000 0.080000 ( 0.129355)
177
+ do_mysql 0.520000 0.010000 0.530000 ( 0.574619)
178
+ ```
179
+
180
+ Although Mysql2 performs reasonably well at retrieving uncasted data, it (currently) is not as fast as the Mysql gem. In spite of this small disadvantage, Mysql2 still sports a friendlier interface and doesn't block the entire ruby process when querying.
181
+
182
+ ### Async
183
+
184
+ NOTE: Not supported on Windows.
185
+
186
+ `Mysql2::Client` takes advantage of the MySQL C API's (undocumented) non-blocking function mysql_send_query for *all* queries.
187
+ But, in order to take full advantage of it in your Ruby code, you can do:
188
+
189
+ ``` ruby
190
+ client.query("SELECT sleep(5)", :async => true)
191
+ ```
192
+
193
+ Which will return nil immediately. At this point you'll probably want to use some socket monitoring mechanism
194
+ like EventMachine or even IO.select. Once the socket becomes readable, you can do:
195
+
196
+ ``` ruby
197
+ # result will be a Mysql2::Result instance
198
+ result = client.async_result
199
+ ```
200
+
201
+ NOTE: Because of the way MySQL's query API works, this method will block until the result is ready.
202
+ So if you really need things to stay async, it's best to just monitor the socket with something like EventMachine.
203
+ If you need multiple query concurrency take a look at using a connection pool.
204
+
205
+ ### Row Caching
206
+
207
+ By default, Mysql2 will cache rows that have been created in Ruby (since this happens lazily).
208
+ This is especially helpful since it saves the cost of creating the row in Ruby if you were to iterate over the collection again.
209
+
210
+ 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.
211
+ 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.
212
+
213
+ ## ActiveRecord
214
+
215
+ 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".
216
+ That was easy right? :)
217
+
218
+ 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
219
+ Rails versions < 3.1 make sure and specify `gem "mysql2", "~> 0.2.7"` in your Gemfile
220
+
221
+ ## Asynchronous ActiveRecord
222
+
223
+ 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
224
+ 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.
225
+
226
+ ## Sequel
227
+
228
+ The Sequel adapter was pulled out into Sequel core (will be part of the next release) and can be used by specifying the "mysql2://" prefix to your connection specification.
229
+
230
+ ## EventMachine
231
+
232
+ The mysql2 EventMachine deferrable api allows you to make async queries using EventMachine,
233
+ while specifying callbacks for success for failure. Here's a simple example:
234
+
235
+ ``` ruby
236
+ require 'mysql2/em'
237
+
238
+ EM.run do
239
+ client1 = Mysql2::EM::Client.new
240
+ defer1 = client1.query "SELECT sleep(3) as first_query"
241
+ defer1.callback do |result|
242
+ puts "Result: #{result.to_a.inspect}"
243
+ end
244
+
245
+ client2 = Mysql2::EM::Client.new
246
+ defer2 = client2.query "SELECT sleep(1) second_query"
247
+ defer2.callback do |result|
248
+ puts "Result: #{result.to_a.inspect}"
249
+ end
250
+ end
251
+ ```
252
+
253
+ ## Lazy Everything
254
+
255
+ Well... almost ;)
256
+
257
+ Field name strings/symbols are shared across all the rows so only one object is ever created to represent the field name for an entire dataset.
258
+
259
+ Rows themselves are lazily created in ruby-land when an attempt to yield it is made via #each.
260
+ For example, if you were to yield 4 rows from a 100 row dataset, only 4 hashes will be created. The rest will sit and wait in C-land until you want them (or when the GC goes to cleanup your `Mysql2::Result` instance).
261
+ 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.
262
+ 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.
263
+
264
+ This caching behavior can be disabled by setting the :cache_rows option to false.
265
+
266
+ As for field values themselves, I'm workin on it - but expect that soon.
267
+
268
+ ## Compatibility
269
+
270
+ The specs pass on my system (SL 10.6.3, x86_64) in these rubies:
271
+
272
+ * 1.8.7-p249
273
+ * ree-1.8.7-2010.01
274
+ * 1.9.1-p378
275
+ * ruby-trunk
276
+ * rbx-head - broken at the moment, working with the rbx team for a solution
277
+
278
+ The ActiveRecord driver should work on 2.3.5 and 3.0
279
+
280
+ ## Yeah... but why?
281
+
282
+ Someone: Dude, the Mysql gem works fiiiiiine.
283
+
284
+ Me: It sure does, but it only hands you nil and strings for field values. Leaving you to convert
285
+ them into proper Ruby types in Ruby-land - which is slow as balls.
286
+
287
+
288
+ Someone: OK fine, but do_mysql can already give me back values with Ruby objects mapped to MySQL types.
289
+
290
+ Me: Yep, but it's API is considerably more complex *and* can be ~2x slower.
291
+
292
+ ## Benchmarks
293
+
294
+ Performing a basic "SELECT * FROM" query on a table with 30k rows and fields of nearly every Ruby-representable data type,
295
+ then iterating over every row using an #each like method yielding a block:
296
+
297
+ These results are from the `query_with_mysql_casting.rb` script in the benchmarks folder
298
+
299
+ ``` sh
300
+ user system total real
301
+ Mysql2
302
+ 0.750000 0.180000 0.930000 ( 1.821655)
303
+ do_mysql
304
+ 1.650000 0.200000 1.850000 ( 2.811357)
305
+ Mysql
306
+ 7.500000 0.210000 7.710000 ( 8.065871)
307
+ ```
308
+
309
+ ## Development
310
+
311
+ To run the tests, you can use RVM and Bundler to create a pristine environment for mysql2 development/hacking.
312
+ Use 'bundle install' to install the necessary development and testing gems:
313
+
314
+ ``` sh
315
+ bundle install
316
+ rake
317
+ ```
318
+
319
+ ## Special Thanks
320
+
321
+ * Eric Wong - for the contribution (and the informative explanations) of some thread-safety, non-blocking I/O and cleanup patches. You rock dude
322
+ * Yury Korolev (http://github.com/yury) - for TONS of help testing the ActiveRecord adapter
323
+ * Aaron Patterson (http://github.com/tenderlove) - tons of contributions, suggestions and general badassness
324
+ * Mike Perham (http://github.com/mperham) - Async ActiveRecord adapter (uses Fibers and EventMachine)
@@ -14,9 +14,18 @@ sql = "SELECT * FROM mysql2_test LIMIT 100"
14
14
  Benchmark.bmbm do |x|
15
15
  mysql2 = Mysql2::Client.new(:host => "localhost", :username => "root")
16
16
  mysql2.query "USE #{database}"
17
- x.report "Mysql2" do
17
+ x.report "Mysql2 (cast: true)" do
18
18
  number_of.times do
19
- mysql2_result = mysql2.query sql, :symbolize_keys => true
19
+ mysql2_result = mysql2.query sql, :symbolize_keys => true, :cast => true
20
+ mysql2_result.each do |res|
21
+ # puts res.inspect
22
+ end
23
+ end
24
+ end
25
+
26
+ x.report "Mysql2 (cast: false)" do
27
+ number_of.times do
28
+ mysql2_result = mysql2.query sql, :symbolize_keys => true, :cast => false
20
29
  mysql2_result.each do |res|
21
30
  # puts res.inspect
22
31
  end
data/ext/mysql2/client.c CHANGED
@@ -139,10 +139,6 @@ static VALUE nogvl_close(void *ptr) {
139
139
  flags = fcntl(wrapper->client->net.fd, F_GETFL);
140
140
  if (flags > 0 && !(flags & O_NONBLOCK))
141
141
  fcntl(wrapper->client->net.fd, F_SETFL, flags | O_NONBLOCK);
142
- #else
143
- u_long iMode;
144
- iMode = 1;
145
- ioctlsocket(wrapper->client->net.fd, FIONBIO, &iMode);
146
142
  #endif
147
143
 
148
144
  mysql_close(wrapper->client);
@@ -356,6 +352,7 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
356
352
  return rb_raise_mysql2_error(wrapper);
357
353
  }
358
354
 
355
+ #ifndef _WIN32
359
356
  read_timeout = rb_iv_get(self, "@read_timeout");
360
357
 
361
358
  tvp = NULL;
@@ -380,27 +377,11 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
380
377
  for(;;) {
381
378
  int fd_set_fd = fd;
382
379
 
383
- #ifdef _WIN32
384
- WSAPROTOCOL_INFO wsa_pi;
385
- // dupicate the SOCKET from libmysql
386
- int r = WSADuplicateSocket(fd, GetCurrentProcessId(), &wsa_pi);
387
- SOCKET s = WSASocket(wsa_pi.iAddressFamily, wsa_pi.iSocketType, wsa_pi.iProtocol, &wsa_pi, 0, 0);
388
- // create the CRT fd so ruby can get back to the SOCKET
389
- fd_set_fd = _open_osfhandle(s, O_RDWR|O_BINARY);
390
- #endif
391
-
392
380
  FD_ZERO(&fdset);
393
381
  FD_SET(fd_set_fd, &fdset);
394
382
 
395
383
  retval = rb_thread_select(fd_set_fd + 1, &fdset, NULL, NULL, tvp);
396
384
 
397
- #ifdef _WIN32
398
- // cleanup the CRT fd
399
- _close(fd_set_fd);
400
- // cleanup the duplicated SOCKET
401
- closesocket(s);
402
- #endif
403
-
404
385
  if (retval == 0) {
405
386
  rb_raise(cMysql2Error, "Timeout waiting for a response from the last query. (waited %d seconds)", FIX2INT(read_timeout));
406
387
  }
@@ -420,6 +401,10 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
420
401
  } else {
421
402
  return Qnil;
422
403
  }
404
+ #else
405
+ // this will just block until the result is ready
406
+ return rb_mysql_client_async_result(self);
407
+ #endif
423
408
  }
424
409
 
425
410
  static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) {
@@ -517,18 +502,12 @@ static VALUE rb_mysql_client_server_info(VALUE self) {
517
502
 
518
503
  static VALUE rb_mysql_client_socket(VALUE self) {
519
504
  GET_CLIENT(self);
505
+ #ifndef _WIN32
520
506
  REQUIRE_OPEN_DB(wrapper);
521
507
  int fd_set_fd = wrapper->client->net.fd;
522
- #ifdef _WIN32
523
- WSAPROTOCOL_INFO wsa_pi;
524
- // dupicate the SOCKET from libmysql
525
- int r = WSADuplicateSocket(wrapper->client->net.fd, GetCurrentProcessId(), &wsa_pi);
526
- SOCKET s = WSASocket(wsa_pi.iAddressFamily, wsa_pi.iSocketType, wsa_pi.iProtocol, &wsa_pi, 0, 0);
527
- // create the CRT fd so ruby can get back to the SOCKET
528
- fd_set_fd = _open_osfhandle(s, O_RDWR|O_BINARY);
529
508
  return INT2NUM(fd_set_fd);
530
509
  #else
531
- return INT2NUM(fd_set_fd);
510
+ rb_raise(cMysql2Error, "Raw access to the mysql file descriptor isn't supported on Windows");
532
511
  #endif
533
512
  }
534
513
 
@@ -559,8 +538,7 @@ static VALUE rb_mysql_client_thread_id(VALUE self) {
559
538
  return ULL2NUM(retVal);
560
539
  }
561
540
 
562
- static VALUE nogvl_ping(void *ptr)
563
- {
541
+ static VALUE nogvl_ping(void *ptr) {
564
542
  MYSQL *client = ptr;
565
543
 
566
544
  return mysql_ping(client) == 0 ? Qtrue : Qfalse;
data/ext/mysql2/result.c CHANGED
@@ -4,7 +4,7 @@
4
4
  static rb_encoding *binaryEncoding;
5
5
  #endif
6
6
 
7
- #define MYSQL2_MAX_YEAR 2058
7
+ #define MYSQL2_MAX_YEAR 2038
8
8
 
9
9
  #ifdef NEGATIVE_TIME_T
10
10
  /* 1901-12-13 20:45:52 UTC : The oldest time in 32-bit signed time_t. */
@@ -22,7 +22,7 @@ static VALUE intern_encoding_from_charset;
22
22
  static ID intern_new, intern_utc, intern_local, intern_encoding_from_charset_code,
23
23
  intern_localtime, intern_local_offset, intern_civil, intern_new_offset;
24
24
  static VALUE sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, sym_application_timezone,
25
- sym_local, sym_utc, sym_cast_booleans, sym_cache_rows;
25
+ sym_local, sym_utc, sym_cast_booleans, sym_cache_rows, sym_cast;
26
26
  static ID intern_merge;
27
27
 
28
28
  static void rb_mysql_result_mark(void * wrapper) {
@@ -100,7 +100,32 @@ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int
100
100
  return rb_field;
101
101
  }
102
102
 
103
- static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezone, int symbolizeKeys, int asArray, int castBool) {
103
+ #ifdef HAVE_RUBY_ENCODING_H
104
+ inline VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_encoding *default_internal_enc, rb_encoding *conn_enc) {
105
+ // if binary flag is set, respect it's wishes
106
+ if (field.flags & BINARY_FLAG && field.charsetnr == 63) {
107
+ rb_enc_associate(val, binaryEncoding);
108
+ } else {
109
+ // lookup the encoding configured on this field
110
+ VALUE new_encoding = rb_funcall(cMysql2Client, intern_encoding_from_charset_code, 1, INT2NUM(field.charsetnr));
111
+ if (new_encoding != Qnil) {
112
+ // use the field encoding we were able to match
113
+ rb_encoding *enc = rb_to_encoding(new_encoding);
114
+ rb_enc_associate(val, enc);
115
+ } else {
116
+ // otherwise fall-back to the connection's encoding
117
+ rb_enc_associate(val, conn_enc);
118
+ }
119
+ if (default_internal_enc) {
120
+ val = rb_str_export_to_enc(val, default_internal_enc);
121
+ }
122
+ }
123
+ return val;
124
+ }
125
+ #endif
126
+
127
+
128
+ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezone, int symbolizeKeys, int asArray, int castBool, int cast) {
104
129
  VALUE rowVal;
105
130
  mysql2_result_wrapper * wrapper;
106
131
  MYSQL_ROW row;
@@ -141,7 +166,19 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
141
166
  VALUE field = rb_mysql_result_fetch_field(self, i, symbolizeKeys);
142
167
  if (row[i]) {
143
168
  VALUE val = Qnil;
144
- switch(fields[i].type) {
169
+ enum enum_field_types type = fields[i].type;
170
+
171
+ if(!cast) {
172
+ if (type == MYSQL_TYPE_NULL) {
173
+ val = Qnil;
174
+ } else {
175
+ val = rb_str_new(row[i], fieldLengths[i]);
176
+ #ifdef HAVE_RUBY_ENCODING_H
177
+ val = mysql2_set_field_string_encoding(val, fields[i], default_internal_enc, conn_enc);
178
+ #endif
179
+ }
180
+ } else {
181
+ switch(type) {
145
182
  case MYSQL_TYPE_NULL: // NULL-type field
146
183
  val = Qnil;
147
184
  break;
@@ -260,26 +297,10 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
260
297
  default:
261
298
  val = rb_str_new(row[i], fieldLengths[i]);
262
299
  #ifdef HAVE_RUBY_ENCODING_H
263
- // if binary flag is set, respect it's wishes
264
- if (fields[i].flags & BINARY_FLAG && fields[i].charsetnr == 63) {
265
- rb_enc_associate(val, binaryEncoding);
266
- } else {
267
- // lookup the encoding configured on this field
268
- VALUE new_encoding = rb_funcall(cMysql2Client, intern_encoding_from_charset_code, 1, INT2NUM(fields[i].charsetnr));
269
- if (new_encoding != Qnil) {
270
- // use the field encoding we were able to match
271
- rb_encoding *enc = rb_to_encoding(new_encoding);
272
- rb_enc_associate(val, enc);
273
- } else {
274
- // otherwise fall-back to the connection's encoding
275
- rb_enc_associate(val, conn_enc);
276
- }
277
- if (default_internal_enc) {
278
- val = rb_str_export_to_enc(val, default_internal_enc);
279
- }
280
- }
300
+ val = mysql2_set_field_string_encoding(val, fields[i], default_internal_enc, conn_enc);
281
301
  #endif
282
302
  break;
303
+ }
283
304
  }
284
305
  if (asArray) {
285
306
  rb_ary_push(rowVal, val);
@@ -329,7 +350,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
329
350
  ID db_timezone, app_timezone, dbTz, appTz;
330
351
  mysql2_result_wrapper * wrapper;
331
352
  unsigned long i;
332
- int symbolizeKeys = 0, asArray = 0, castBool = 0, cacheRows = 1;
353
+ int symbolizeKeys = 0, asArray = 0, castBool = 0, cacheRows = 1, cast = 1;
333
354
 
334
355
  GetMysql2Result(self, wrapper);
335
356
 
@@ -356,6 +377,10 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
356
377
  cacheRows = 0;
357
378
  }
358
379
 
380
+ if (rb_hash_aref(opts, sym_cast) == Qfalse) {
381
+ cast = 0;
382
+ }
383
+
359
384
  dbTz = rb_hash_aref(opts, sym_database_timezone);
360
385
  if (dbTz == sym_local) {
361
386
  db_timezone = intern_local;
@@ -400,7 +425,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
400
425
  if (cacheRows && i < rowsProcessed) {
401
426
  row = rb_ary_entry(wrapper->rows, i);
402
427
  } else {
403
- row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool);
428
+ row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast);
404
429
  if (cacheRows) {
405
430
  rb_ary_store(wrapper->rows, i, row);
406
431
  }
@@ -473,6 +498,7 @@ void init_mysql2_result() {
473
498
  sym_database_timezone = ID2SYM(rb_intern("database_timezone"));
474
499
  sym_application_timezone = ID2SYM(rb_intern("application_timezone"));
475
500
  sym_cache_rows = ID2SYM(rb_intern("cache_rows"));
501
+ sym_cast = ID2SYM(rb_intern("cast"));
476
502
 
477
503
  opt_decimal_zero = rb_str_new2("0.0");
478
504
  rb_global_variable(&opt_decimal_zero); //never GC
@@ -67,6 +67,34 @@ module ActiveRecord
67
67
 
68
68
  @connections = []
69
69
  @checked_out = []
70
+ @automatic_reconnect = true
71
+ @tables = {}
72
+
73
+ @columns = Hash.new do |h, table_name|
74
+ h[table_name] = with_connection do |conn|
75
+
76
+ # Fetch a list of columns
77
+ conn.columns(table_name, "#{table_name} Columns").tap do |columns|
78
+
79
+ # set primary key information
80
+ columns.each do |column|
81
+ column.primary = column.name == primary_keys[table_name]
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ @columns_hash = Hash.new do |h, table_name|
88
+ h[table_name] = Hash[columns[table_name].map { |col|
89
+ [col.name, col]
90
+ }]
91
+ end
92
+
93
+ @primary_keys = Hash.new do |h, table_name|
94
+ h[table_name] = with_connection do |conn|
95
+ table_exists?(table_name) ? conn.primary_key(table_name) : 'id'
96
+ end
97
+ end
70
98
  end
71
99
 
72
100
  def clear_stale_cached_connections!
@@ -101,4 +129,4 @@ module ActiveRecord
101
129
  end
102
130
 
103
131
  end
104
- end
132
+ end
data/lib/mysql2.rb CHANGED
@@ -18,4 +18,4 @@ end
18
18
  if defined?(ActiveRecord::VERSION::STRING) && ActiveRecord::VERSION::STRING < "3.1"
19
19
  puts "WARNING: This version of mysql2 (#{Mysql2::VERSION}) doesn't ship with the ActiveRecord adapter bundled anymore as it's now part of Rails 3.1"
20
20
  puts "WARNING: Please use the 0.2.x releases if you plan on using it in Rails <= 3.0.x"
21
- end
21
+ end
data/lib/mysql2/client.rb CHANGED
@@ -9,7 +9,8 @@ module Mysql2
9
9
  :database_timezone => :local, # timezone Mysql2 will assume datetime objects are stored in
10
10
  :application_timezone => nil, # timezone Mysql2 will convert to before handing the object back to the caller
11
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
12
+ :connect_flags => REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION,
13
+ :cast => true
13
14
  }
14
15
 
15
16
  def initialize(opts = {})
@@ -1,3 +1,3 @@
1
1
  module Mysql2
2
- VERSION = "0.3.2"
2
+ VERSION = "0.3.3"
3
3
  end
data/mysql2.gemspec CHANGED
@@ -7,9 +7,6 @@ Gem::Specification.new do |s|
7
7
  s.date = Time.now.utc.strftime("%Y-%m-%d")
8
8
  s.email = %q{seniorlopez@gmail.com}
9
9
  s.extensions = ["ext/mysql2/extconf.rb"]
10
- s.extra_rdoc_files = [
11
- "README.rdoc"
12
- ]
13
10
  s.files = `git ls-files`.split("\n")
14
11
  s.homepage = %q{http://github.com/brianmario/mysql2}
15
12
  s.rdoc_options = ["--charset=UTF-8"]
@@ -79,6 +79,16 @@ describe Mysql2::Result do
79
79
  @test_result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first
80
80
  end
81
81
 
82
+ it "should return nil values for NULL and strings for everything else when :cast is false" do
83
+ result = @client.query('SELECT null_test, tiny_int_test, bool_cast_test, int_test, date_test, enum_test FROM mysql2_test WHERE bool_cast_test = 1 LIMIT 1', :cast => false).first
84
+ result["null_test"].should be_nil
85
+ result["tiny_int_test"].should == "1"
86
+ result["bool_cast_test"].should == "1"
87
+ result["int_test"].should == "10"
88
+ result["date_test"].should == "2010-04-04"
89
+ result["enum_test"].should == "val1"
90
+ end
91
+
82
92
  it "should return nil for a NULL value" do
83
93
  @test_result['null_test'].class.should eql(NilClass)
84
94
  @test_result['null_test'].should eql(nil)
@@ -173,6 +183,11 @@ describe Mysql2::Result do
173
183
  r.first['test'].class.should eql(DateTime)
174
184
  end
175
185
 
186
+ it "should return DateTime when time > year 2038" do
187
+ r = @client.query("SELECT CAST('2039-01-01 01:01:01' AS DATETIME) as test")
188
+ r.first['test'].class.should eql(DateTime)
189
+ end
190
+
176
191
  it "should return Time for a TIMESTAMP value when within the supported range" do
177
192
  @test_result['timestamp_test'].class.should eql(Time)
178
193
  @test_result['timestamp_test'].strftime("%F %T").should eql('2010-04-04 11:44:00')
data/spec/spec_helper.rb CHANGED
@@ -45,6 +45,7 @@ RSpec.configure do |config|
45
45
  PRIMARY KEY (id)
46
46
  )
47
47
  ]
48
+ client.query "DELETE FROM mysql2_test;"
48
49
  client.query %[
49
50
  INSERT INTO mysql2_test (
50
51
  null_test, bit_test, tiny_int_test, bool_cast_test, small_int_test, medium_int_test, int_test, big_int_test,
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: 23
4
+ hash: 21
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 3
9
- - 2
10
- version: 0.3.2
9
+ - 3
10
+ version: 0.3.3
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: 2011-04-27 00:00:00 -07:00
18
+ date: 2011-06-14 00:00:00 -07:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -138,8 +138,8 @@ executables: []
138
138
 
139
139
  extensions:
140
140
  - ext/mysql2/extconf.rb
141
- extra_rdoc_files:
142
- - README.rdoc
141
+ extra_rdoc_files: []
142
+
143
143
  files:
144
144
  - .gitignore
145
145
  - .rspec
@@ -147,7 +147,7 @@ files:
147
147
  - CHANGELOG.md
148
148
  - Gemfile
149
149
  - MIT-LICENSE
150
- - README.rdoc
150
+ - README.md
151
151
  - Rakefile
152
152
  - benchmark/active_record.rb
153
153
  - benchmark/active_record_threaded.rb
@@ -219,7 +219,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
219
219
  requirements: []
220
220
 
221
221
  rubyforge_project:
222
- rubygems_version: 1.6.2
222
+ rubygems_version: 1.3.10
223
223
  signing_key:
224
224
  specification_version: 3
225
225
  summary: A simple, fast Mysql library for Ruby, binding to libmysql
data/README.rdoc DELETED
@@ -1,257 +0,0 @@
1
- = Mysql2 - A modern, simple and very fast Mysql library for Ruby - binding to libmysql
2
-
3
- The Mysql2 gem is meant to serve the extremely common use-case of connecting, querying and iterating on results.
4
- Some database libraries out there serve as direct 1:1 mappings of the already complex C API's available.
5
- This one is not.
6
-
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
-
9
- The API consists of two clases:
10
-
11
- Mysql2::Client - your connection to the database
12
-
13
- Mysql2::Result - returned from issuing a #query on the connection. It includes Enumerable.
14
-
15
- == Installing
16
-
17
- gem install mysql2
18
-
19
- You may have to specify --with-mysql-config=/some/random/path/bin/mysql_config
20
-
21
- == Usage
22
-
23
- Connect to a database:
24
-
25
- # this takes a hash of options, almost all of which map directly
26
- # to the familiar database.yml in rails
27
- # See http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/MysqlAdapter.html
28
- client = Mysql2::Client.new(:host => "localhost", :username => "root")
29
-
30
- Then query it:
31
-
32
- results = client.query("SELECT * FROM users WHERE group='githubbers'")
33
-
34
- Need to escape something first?
35
-
36
- escaped = client.escape("gi'thu\"bbe\0r's")
37
- results = client.query("SELECT * FROM users WHERE group='#{escaped}'")
38
-
39
- Finally, iterate over the results:
40
-
41
- results.each do |row|
42
- # conveniently, row is a hash
43
- # the keys are the fields, as you'd expect
44
- # the values are pre-built ruby primitives mapped from their corresponding field types in MySQL
45
- # Here's an otter: http://farm1.static.flickr.com/130/398077070_b8795d0ef3_b.jpg
46
- end
47
-
48
- Or, you might just keep it simple:
49
-
50
- client.query("SELECT * FROM users WHERE group='githubbers'").each do |row|
51
- # do something with row, it's ready to rock
52
- end
53
-
54
- How about with symbolized keys?
55
-
56
- # NOTE: the :symbolize_keys and future options will likely move to the #query method soon
57
- client.query("SELECT * FROM users WHERE group='githubbers'").each(:symbolize_keys => true) do |row|
58
- # do something with row, it's ready to rock
59
- end
60
-
61
- You can get the headers and the columns in the order that they were returned
62
- by the query like this:
63
-
64
- headers = results.fields # <= that's an array of field names, in order
65
- results.each(:as => :array) do |row|
66
- # Each row is an array, ordered the same as the query results
67
- # An otter's den is called a "holt" or "couch"
68
- end
69
-
70
- == Cascading config
71
-
72
- The default config hash is at:
73
-
74
- Mysql2::Client.default_query_options
75
-
76
- which defaults to:
77
-
78
- {:async => false, :as => :hash, :symbolize_keys => false}
79
-
80
- that can be used as so:
81
-
82
- # these are the defaults all Mysql2::Client instances inherit
83
- Mysql2::Client.default_query_options.merge!(:as => :array)
84
-
85
- or
86
-
87
- # this will change the defaults for all future results returned by the #query method _for this connection only_
88
- c = Mysql2::Client.new
89
- c.query_options.merge!(:symbolize_keys => true)
90
-
91
- or
92
-
93
- # this will set the options for the Mysql2::Result instance returned from the #query method
94
- c = Mysql2::Client.new
95
- c.query(sql, :symbolize_keys => true)
96
-
97
- == Result types
98
-
99
- === Array of Arrays
100
-
101
- Pass the :as => :array option to any of the above methods of configuration
102
-
103
- === Array of Hashes
104
-
105
- The default result type is set to :hash, but you can override a previous setting to something else with :as => :hash
106
-
107
- === Others...
108
-
109
- 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.
110
- If you'd like to see either of these (or others), open an issue and start bugging me about it ;)
111
-
112
- === Timezones
113
-
114
- Mysql2 now supports two timezone options:
115
-
116
- :database_timezone - this is the timezone Mysql2 will assume fields are already stored as, and will use this when creating the initial Time objects in ruby
117
- :application_timezone - this is the timezone Mysql2 will convert to before finally handing back to the caller
118
-
119
- In other words, if :database_timezone is set to :utc - Mysql2 will create the Time objects using Time.utc(...) from the raw value libmysql hands over initially.
120
- Then, if :application_timezone is set to say - :local - Mysql2 will then convert the just-created UTC Time object to local time.
121
-
122
- Both options only allow two values - :local or :utc - with the exception that :application_timezone can be [and defaults to] nil
123
-
124
- === Casting "boolean" columns
125
-
126
- You can now tell Mysql2 to cast tinyint(1) fields to boolean values in Ruby with the :cast_booleans option.
127
-
128
- client = Mysql2::Client.new
129
- result = client.query("SELECT * FROM table_with_boolean_field", :cast_booleans => true)
130
-
131
- === Async
132
-
133
- Mysql2::Client takes advantage of the MySQL C API's (undocumented) non-blocking function mysql_send_query for *all* queries.
134
- But, in order to take full advantage of it in your Ruby code, you can do:
135
-
136
- client.query("SELECT sleep(5)", :async => true)
137
-
138
- Which will return nil immediately. At this point you'll probably want to use some socket monitoring mechanism
139
- like EventMachine or even IO.select. Once the socket becomes readable, you can do:
140
-
141
- # result will be a Mysql2::Result instance
142
- result = client.async_result
143
-
144
- NOTE: Because of the way MySQL's query API works, this method will block until the result is ready.
145
- So if you really need things to stay async, it's best to just monitor the socket with something like EventMachine.
146
- If you need multiple query concurrency take a look at using a connection pool.
147
-
148
- === Row Caching
149
-
150
- By default, Mysql2 will cache rows that have been created in Ruby (since this happens lazily).
151
- This is especially helpful since it saves the cost of creating the row in Ruby if you were to iterate over the collection again.
152
-
153
- 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.
154
- 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.
155
-
156
- == ActiveRecord
157
-
158
- 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".
159
- That was easy right? :)
160
-
161
- == Asynchronous ActiveRecord
162
-
163
- 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
164
- 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.
165
-
166
- == Sequel
167
-
168
- The Sequel adapter was pulled out into Sequel core (will be part of the next release) and can be used by specifying the "mysql2://" prefix to your connection specification.
169
-
170
- == EventMachine
171
-
172
- The mysql2 EventMachine deferrable api allows you to make async queries using EventMachine,
173
- while specifying callbacks for success for failure. Here's a simple example:
174
-
175
- require 'mysql2/em'
176
-
177
- EM.run do
178
- client1 = Mysql2::EM::Client.new
179
- defer1 = client1.query "SELECT sleep(3) as first_query"
180
- defer1.callback do |result|
181
- puts "Result: #{result.to_a.inspect}"
182
- end
183
-
184
- client2 = Mysql2::EM::Client.new
185
- defer2 = client2.query "SELECT sleep(1) second_query"
186
- defer2.callback do |result|
187
- puts "Result: #{result.to_a.inspect}"
188
- end
189
- end
190
-
191
- == Lazy Everything
192
-
193
- Well... almost ;)
194
-
195
- Field name strings/symbols are shared across all the rows so only one object is ever created to represent the field name for an entire dataset.
196
-
197
- Rows themselves are lazily created in ruby-land when an attempt to yield it is made via #each.
198
- For example, if you were to yield 4 rows from a 100 row dataset, only 4 hashes will be created. The rest will sit and wait in C-land until you want them (or when the GC goes to cleanup your Mysql2::Result instance).
199
- 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.
200
- 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.
201
-
202
- This caching behavior can be disabled by setting the :cache_rows option to false.
203
-
204
- As for field values themselves, I'm workin on it - but expect that soon.
205
-
206
- == Compatibility
207
-
208
- The specs pass on my system (SL 10.6.3, x86_64) in these rubies:
209
-
210
- * 1.8.7-p249
211
- * ree-1.8.7-2010.01
212
- * 1.9.1-p378
213
- * ruby-trunk
214
- * rbx-head - broken at the moment, working with the rbx team for a solution
215
-
216
- The ActiveRecord driver should work on 2.3.5 and 3.0
217
-
218
- == Yeah... but why?
219
-
220
- Someone: Dude, the Mysql gem works fiiiiiine.
221
-
222
- Me: It sure does, but it only hands you nil and strings for field values. Leaving you to convert
223
- them into proper Ruby types in Ruby-land - which is slow as balls.
224
-
225
-
226
- Someone: OK fine, but do_mysql can already give me back values with Ruby objects mapped to MySQL types.
227
-
228
- Me: Yep, but it's API is considerably more complex *and* can be ~2x slower.
229
-
230
- == Benchmarks
231
-
232
- Performing a basic "SELECT * FROM" query on a table with 30k rows and fields of nearly every Ruby-representable data type,
233
- then iterating over every row using an #each like method yielding a block:
234
-
235
- # These results are from the query_with_mysql_casting.rb script in the benchmarks folder
236
- user system total real
237
- Mysql2
238
- 0.750000 0.180000 0.930000 ( 1.821655)
239
- do_mysql
240
- 1.650000 0.200000 1.850000 ( 2.811357)
241
- Mysql
242
- 7.500000 0.210000 7.710000 ( 8.065871)
243
-
244
- == Development
245
-
246
- To run the tests, you can use RVM and Bundler to create a pristine environment for mysql2 development/hacking.
247
- Use 'bundle install' to install the necessary development and testing gems:
248
-
249
- bundle install
250
- rake
251
-
252
- == Special Thanks
253
-
254
- * Eric Wong - for the contribution (and the informative explanations) of some thread-safety, non-blocking I/O and cleanup patches. You rock dude
255
- * Yury Korolev (http://github.com/yury) - for TONS of help testing the ActiveRecord adapter
256
- * Aaron Patterson (http://github.com/tenderlove) - tons of contributions, suggestions and general badassness
257
- * Mike Perham (http://github.com/mperham) - Async ActiveRecord adapter (uses Fibers and EventMachine)