mysql2 0.2.3 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.4 (September 17th, 2010)
4
+ * a few patches for win32 support from Luis Lavena - thanks man!
5
+ * bugfix from Eric Wong to avoid a potential stack overflow during Mysql2::Client#escape
6
+ * added the ability to turn internal row caching on/off via the :cache_rows => true/false option
7
+ * a couple of small patches for rbx compatibility
8
+ * set IndexDefinition#length in AR adapter - Kouhei Yanagita <yanagi@shakenbu.org>
9
+ * fix a long-standing data corruption bug - thank you thank you thank you to @joedamato (http://github.com/ice799)
10
+ * bugfix from calling mysql_close on a closed/freed connection surfaced by the above fix
11
+
3
12
  ## 0.2.3 (August 20th, 2010)
4
13
  * connection flags can now be passed to the constructor via the :flags key
5
14
  * switch AR adapter connection over to use FOUND_ROWS option
@@ -89,18 +89,18 @@ or
89
89
 
90
90
  === Array of Arrays
91
91
 
92
- Pass the {:as => :array} option to any of the above methods of configuration
92
+ Pass the :as => :array option to any of the above methods of configuration
93
93
 
94
94
  === Array of Hashes
95
95
 
96
- The default result type is set to :hash, but you can override a previous setting to something else with {:as => :hash}
96
+ The default result type is set to :hash, but you can override a previous setting to something else with :as => :hash
97
97
 
98
98
  === Others...
99
99
 
100
- I may add support for {:as => :csv} or even {:as => :json} to allow for *much* more efficient generation of those data types from result sets.
100
+ I may add support for :as => :csv or even :as => :json to allow for *much* more efficient generation of those data types from result sets.
101
101
  If you'd like to see either of these (or others), open an issue and start bugging me about it ;)
102
102
 
103
- == Timezones
103
+ === Timezones
104
104
 
105
105
  Mysql2 now supports two timezone options:
106
106
 
@@ -112,14 +112,14 @@ Then, if :application_timezone is set to say - :local - Mysql2 will then convert
112
112
 
113
113
  Both options only allow two values - :local or :utc - with the exception that :application_timezone can be [and defaults to] nil
114
114
 
115
- == Casting "boolean" columns
115
+ === Casting "boolean" columns
116
116
 
117
117
  You can now tell Mysql2 to cast tinyint(1) fields to boolean values in Ruby with the :cast_booleans option.
118
118
 
119
119
  client = Mysql2::Client.new
120
120
  result = client.query("SELECT * FROM table_with_boolean_field", :cast_booleans => true)
121
121
 
122
- == Async
122
+ === Async
123
123
 
124
124
  Mysql2::Client takes advantage of the MySQL C API's (undocumented) non-blocking function mysql_send_query for *all* queries.
125
125
  But, in order to take full advantage of it in your Ruby code, you can do:
@@ -136,6 +136,14 @@ NOTE: Because of the way MySQL's query API works, this method will block until t
136
136
  So if you really need things to stay async, it's best to just monitor the socket with something like EventMachine.
137
137
  If you need multiple query concurrency take a look at using a connection pool.
138
138
 
139
+ === Row Caching
140
+
141
+ By default, Mysql2 will cache rows that have been created in Ruby (since this happens lazily).
142
+ This is especially helpful since it saves the cost of creating the row in Ruby if you were to iterate over the collection again.
143
+
144
+ If you only plan on using each row once, then it's much more efficient to disable this behavior by setting the :cache_rows option to false.
145
+ This would be helpful if you wanted to iterate over the results in a streaming manner. Meaning the GC would cleanup rows you don't need anymore as you're iterating over the result set.
146
+
139
147
  == ActiveRecord
140
148
 
141
149
  To use the ActiveRecord driver, all you should need to do is have this gem installed and set the adapter in your database.yml to "mysql2".
@@ -182,6 +190,8 @@ For example, if you were to yield 4 rows from a 100 row dataset, only 4 hashes w
182
190
  Now say you were to iterate over that same collection again, this time yielding 15 rows - the 4 previous rows that had already been turned into ruby hashes would be pulled from an internal cache, then 11 more would be created and stored in that cache.
183
191
  Once the entire dataset has been converted into ruby objects, Mysql2::Result will free the Mysql C result object as it's no longer needed.
184
192
 
193
+ This caching behavior can be disabled by setting the :cache_rows option to false.
194
+
185
195
  As for field values themselves, I'm workin on it - but expect that soon.
186
196
 
187
197
  == Compatibility
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.3
1
+ 0.2.4
@@ -70,7 +70,7 @@ Benchmark.bmbm do |x|
70
70
  end
71
71
 
72
72
  do_mysql = DataObjects::Connection.new("mysql://localhost/#{database}")
73
- command = DataObjects::Mysql::Command.new do_mysql, sql
73
+ command = do_mysql.create_command sql
74
74
  x.report do
75
75
  puts "do_mysql"
76
76
  number_of.times do
@@ -8,20 +8,18 @@ static VALUE intern_encoding_from_charset;
8
8
  static ID sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as, sym_array;
9
9
  static ID intern_merge, intern_error_number_eql, intern_sql_state_eql;
10
10
 
11
- #define REQUIRE_OPEN_DB(_ctxt) \
12
- if(!_ctxt->net.vio) { \
11
+ #define REQUIRE_OPEN_DB(wrapper) \
12
+ if(wrapper->closed || !wrapper->client->net.vio) { \
13
13
  rb_raise(cMysql2Error, "closed MySQL connection"); \
14
14
  return Qnil; \
15
15
  }
16
16
 
17
17
  #define MARK_CONN_INACTIVE(conn) \
18
- wrapper->active = 0;
18
+ wrapper->active = 0
19
19
 
20
20
  #define GET_CLIENT(self) \
21
21
  mysql_client_wrapper *wrapper; \
22
- MYSQL *client; \
23
- Data_Get_Struct(self, mysql_client_wrapper, wrapper); \
24
- client = &wrapper->client;
22
+ Data_Get_Struct(self, mysql_client_wrapper, wrapper)
25
23
 
26
24
  /*
27
25
  * used to pass all arguments to mysql_real_connect while inside
@@ -85,12 +83,11 @@ static VALUE rb_raise_mysql2_error(MYSQL *client) {
85
83
  }
86
84
 
87
85
  static VALUE nogvl_init(void *ptr) {
88
- MYSQL * client = (MYSQL *)ptr;
86
+ MYSQL **client = (MYSQL **)ptr;
89
87
 
90
88
  /* may initialize embedded server and read /etc/services off disk */
91
- client = mysql_init(NULL);
92
-
93
- return client ? Qtrue : Qfalse;
89
+ *client = mysql_init(NULL);
90
+ return *client ? Qtrue : Qfalse;
94
91
  }
95
92
 
96
93
  static VALUE nogvl_connect(void *ptr) {
@@ -108,36 +105,43 @@ static VALUE nogvl_connect(void *ptr) {
108
105
  }
109
106
 
110
107
  static void rb_mysql_client_free(void * ptr) {
111
- mysql_client_wrapper * wrapper = (mysql_client_wrapper *)ptr;
112
- MYSQL * client = &wrapper->client;
108
+ mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr;
113
109
 
114
110
  /*
115
111
  * we'll send a QUIT message to the server, but that message is more of a
116
112
  * formality than a hard requirement since the socket is getting shutdown
117
113
  * anyways, so ensure the socket write does not block our interpreter
118
114
  */
119
- int fd = client->net.fd;
120
- int flags;
115
+ int fd = wrapper->client->net.fd;
121
116
 
122
117
  if (fd >= 0) {
123
118
  /*
124
119
  * if the socket is dead we have no chance of blocking,
125
120
  * so ignore any potential fcntl errors since they don't matter
126
121
  */
127
- flags = fcntl(fd, F_GETFL);
122
+ #ifndef _WIN32
123
+ int flags = fcntl(fd, F_GETFL);
128
124
  if (flags > 0 && !(flags & O_NONBLOCK))
129
125
  fcntl(fd, F_SETFL, flags | O_NONBLOCK);
126
+ #else
127
+ u_long iMode = 1;
128
+ ioctlsocket(fd, FIONBIO, &iMode);
129
+ #endif
130
130
  }
131
131
 
132
132
  /* It's safe to call mysql_close() on an already closed connection. */
133
- mysql_close(client);
133
+ if (!wrapper->closed) {
134
+ mysql_close(wrapper->client);
135
+ }
134
136
  xfree(ptr);
135
137
  }
136
138
 
137
139
  static VALUE nogvl_close(void * ptr) {
138
- MYSQL *client = (MYSQL *)ptr;
139
- mysql_close(client);
140
- client->net.fd = -1;
140
+ mysql_client_wrapper *wrapper = ptr;
141
+ if (!wrapper->closed) {
142
+ mysql_close(wrapper->client);
143
+ wrapper->closed = 1;
144
+ }
141
145
  return Qnil;
142
146
  }
143
147
 
@@ -147,12 +151,13 @@ static VALUE allocate(VALUE klass) {
147
151
  obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper);
148
152
  wrapper->encoding = Qnil;
149
153
  wrapper->active = 0;
154
+ wrapper->closed = 0;
150
155
  return obj;
151
156
  }
152
157
 
153
158
  static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags) {
154
159
  struct nogvl_connect_args args;
155
- GET_CLIENT(self)
160
+ GET_CLIENT(self);
156
161
 
157
162
  args.host = NIL_P(host) ? "localhost" : StringValuePtr(host);
158
163
  args.unix_socket = NIL_P(socket) ? NULL : StringValuePtr(socket);
@@ -160,12 +165,12 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
160
165
  args.user = NIL_P(user) ? NULL : StringValuePtr(user);
161
166
  args.passwd = NIL_P(pass) ? NULL : StringValuePtr(pass);
162
167
  args.db = NIL_P(database) ? NULL : StringValuePtr(database);
163
- args.mysql = client;
168
+ args.mysql = wrapper->client;
164
169
  args.client_flag = NUM2INT(flags);
165
170
 
166
171
  if (rb_thread_blocking_region(nogvl_connect, &args, RUBY_UBF_IO, 0) == Qfalse) {
167
172
  // unable to connect
168
- return rb_raise_mysql2_error(client);
173
+ return rb_raise_mysql2_error(wrapper->client);
169
174
  }
170
175
 
171
176
  return self;
@@ -178,9 +183,9 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
178
183
  * for the garbage collector.
179
184
  */
180
185
  static VALUE rb_mysql_client_close(VALUE self) {
181
- GET_CLIENT(self)
186
+ GET_CLIENT(self);
182
187
 
183
- rb_thread_blocking_region(nogvl_close, client, RUBY_UBF_IO, 0);
188
+ rb_thread_blocking_region(nogvl_close, wrapper, RUBY_UBF_IO, 0);
184
189
 
185
190
  return Qnil;
186
191
  }
@@ -221,30 +226,30 @@ static VALUE nogvl_store_result(void *ptr) {
221
226
 
222
227
  static VALUE rb_mysql_client_async_result(VALUE self) {
223
228
  MYSQL_RES * result;
224
- GET_CLIENT(self)
229
+ GET_CLIENT(self);
225
230
 
226
- REQUIRE_OPEN_DB(client);
227
- if (rb_thread_blocking_region(nogvl_read_query_result, client, RUBY_UBF_IO, 0) == Qfalse) {
231
+ REQUIRE_OPEN_DB(wrapper);
232
+ if (rb_thread_blocking_region(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
228
233
  // an error occurred, mark this connection inactive
229
234
  MARK_CONN_INACTIVE(self);
230
- return rb_raise_mysql2_error(client);
235
+ return rb_raise_mysql2_error(wrapper->client);
231
236
  }
232
237
 
233
- result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, client, RUBY_UBF_IO, 0);
238
+ result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper->client, RUBY_UBF_IO, 0);
234
239
 
235
240
  // we have our result, mark this connection inactive
236
241
  MARK_CONN_INACTIVE(self);
237
242
 
238
243
  if (result == NULL) {
239
- if (mysql_field_count(client) != 0) {
240
- rb_raise_mysql2_error(client);
244
+ if (mysql_field_count(wrapper->client) != 0) {
245
+ rb_raise_mysql2_error(wrapper->client);
241
246
  }
242
247
  return Qnil;
243
248
  }
244
249
 
245
250
  VALUE resultObj = rb_mysql_result_to_obj(result);
246
251
  // pass-through query options for result construction later
247
- rb_iv_set(resultObj, "@query_options", rb_obj_dup(rb_iv_get(self, "@query_options")));
252
+ rb_iv_set(resultObj, "@query_options", rb_funcall(rb_iv_get(self, "@query_options"), rb_intern("dup"), 0));
248
253
 
249
254
  #ifdef HAVE_RUBY_ENCODING_H
250
255
  mysql2_result_wrapper * result_wrapper;
@@ -260,10 +265,10 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
260
265
  int fd, retval;
261
266
  int async = 0;
262
267
  VALUE opts, defaults;
263
- GET_CLIENT(self)
268
+ GET_CLIENT(self);
264
269
 
265
- REQUIRE_OPEN_DB(client);
266
- args.mysql = client;
270
+ REQUIRE_OPEN_DB(wrapper);
271
+ args.mysql = wrapper->client;
267
272
 
268
273
  // see if this connection is still waiting on a result from a previous query
269
274
  if (wrapper->active == 0) {
@@ -294,13 +299,13 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
294
299
  if (rb_thread_blocking_region(nogvl_send_query, &args, RUBY_UBF_IO, 0) == Qfalse) {
295
300
  // an error occurred, we're not active anymore
296
301
  MARK_CONN_INACTIVE(self);
297
- return rb_raise_mysql2_error(client);
302
+ return rb_raise_mysql2_error(wrapper->client);
298
303
  }
299
304
 
300
305
  if (!async) {
301
306
  // the below code is largely from do_mysql
302
307
  // http://github.com/datamapper/do
303
- fd = client->net.fd;
308
+ fd = wrapper->client->net.fd;
304
309
  for(;;) {
305
310
  FD_ZERO(&fdset);
306
311
  FD_SET(fd, &fdset);
@@ -327,7 +332,7 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
327
332
  static VALUE rb_mysql_client_escape(VALUE self, VALUE str) {
328
333
  VALUE newStr;
329
334
  unsigned long newLen, oldLen;
330
- GET_CLIENT(self)
335
+ GET_CLIENT(self);
331
336
 
332
337
  Check_Type(str, T_STRING);
333
338
  #ifdef HAVE_RUBY_ENCODING_H
@@ -338,15 +343,15 @@ static VALUE rb_mysql_client_escape(VALUE self, VALUE str) {
338
343
  #endif
339
344
 
340
345
  oldLen = RSTRING_LEN(str);
341
- char escaped[(oldLen*2)+1];
346
+ newStr = rb_str_new(0, oldLen*2+1);
342
347
 
343
- REQUIRE_OPEN_DB(client);
344
- newLen = mysql_real_escape_string(client, escaped, StringValuePtr(str), oldLen);
348
+ REQUIRE_OPEN_DB(wrapper);
349
+ newLen = mysql_real_escape_string(wrapper->client, RSTRING_PTR(newStr), StringValuePtr(str), oldLen);
345
350
  if (newLen == oldLen) {
346
351
  // no need to return a new ruby string if nothing changed
347
352
  return str;
348
353
  } else {
349
- newStr = rb_str_new(escaped, newLen);
354
+ rb_str_resize(newStr, newLen);
350
355
  #ifdef HAVE_RUBY_ENCODING_H
351
356
  rb_enc_associate(newStr, conn_enc);
352
357
  if (default_internal_enc) {
@@ -379,17 +384,17 @@ static VALUE rb_mysql_client_info(VALUE self) {
379
384
 
380
385
  static VALUE rb_mysql_client_server_info(VALUE self) {
381
386
  VALUE version, server_info;
382
- GET_CLIENT(self)
387
+ GET_CLIENT(self);
383
388
  #ifdef HAVE_RUBY_ENCODING_H
384
389
  rb_encoding *default_internal_enc = rb_default_internal_encoding();
385
390
  rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
386
391
  #endif
387
392
 
388
- REQUIRE_OPEN_DB(client);
393
+ REQUIRE_OPEN_DB(wrapper);
389
394
 
390
395
  version = rb_hash_new();
391
- rb_hash_aset(version, sym_id, LONG2FIX(mysql_get_server_version(client)));
392
- server_info = rb_str_new2(mysql_get_server_info(client));
396
+ rb_hash_aset(version, sym_id, LONG2FIX(mysql_get_server_version(wrapper->client)));
397
+ server_info = rb_str_new2(mysql_get_server_info(wrapper->client));
393
398
  #ifdef HAVE_RUBY_ENCODING_H
394
399
  rb_enc_associate(server_info, conn_enc);
395
400
  if (default_internal_enc) {
@@ -401,34 +406,34 @@ static VALUE rb_mysql_client_server_info(VALUE self) {
401
406
  }
402
407
 
403
408
  static VALUE rb_mysql_client_socket(VALUE self) {
404
- GET_CLIENT(self)
405
- REQUIRE_OPEN_DB(client);
406
- return INT2NUM(client->net.fd);
409
+ GET_CLIENT(self);
410
+ REQUIRE_OPEN_DB(wrapper);
411
+ return INT2NUM(wrapper->client->net.fd);
407
412
  }
408
413
 
409
414
  static VALUE rb_mysql_client_last_id(VALUE self) {
410
- GET_CLIENT(self)
411
- REQUIRE_OPEN_DB(client);
412
- return ULL2NUM(mysql_insert_id(client));
415
+ GET_CLIENT(self);
416
+ REQUIRE_OPEN_DB(wrapper);
417
+ return ULL2NUM(mysql_insert_id(wrapper->client));
413
418
  }
414
419
 
415
420
  static VALUE rb_mysql_client_affected_rows(VALUE self) {
416
- GET_CLIENT(self)
417
- REQUIRE_OPEN_DB(client);
418
- return ULL2NUM(mysql_affected_rows(client));
421
+ GET_CLIENT(self);
422
+ REQUIRE_OPEN_DB(wrapper);
423
+ return ULL2NUM(mysql_affected_rows(wrapper->client));
419
424
  }
420
425
 
421
426
  static VALUE set_reconnect(VALUE self, VALUE value) {
422
427
  my_bool reconnect;
423
- GET_CLIENT(self)
428
+ GET_CLIENT(self);
424
429
 
425
430
  if(!NIL_P(value)) {
426
431
  reconnect = value == Qfalse ? 0 : 1;
427
432
 
428
433
  /* set default reconnect behavior */
429
- if (mysql_options(client, MYSQL_OPT_RECONNECT, &reconnect)) {
434
+ if (mysql_options(wrapper->client, MYSQL_OPT_RECONNECT, &reconnect)) {
430
435
  /* TODO: warning - unable to set reconnect behavior */
431
- rb_warn("%s\n", mysql_error(client));
436
+ rb_warn("%s\n", mysql_error(wrapper->client));
432
437
  }
433
438
  }
434
439
  return value;
@@ -436,16 +441,16 @@ static VALUE set_reconnect(VALUE self, VALUE value) {
436
441
 
437
442
  static VALUE set_connect_timeout(VALUE self, VALUE value) {
438
443
  unsigned int connect_timeout = 0;
439
- GET_CLIENT(self)
444
+ GET_CLIENT(self);
440
445
 
441
446
  if(!NIL_P(value)) {
442
447
  connect_timeout = NUM2INT(value);
443
448
  if(0 == connect_timeout) return value;
444
449
 
445
450
  /* set default connection timeout behavior */
446
- if (mysql_options(client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout)) {
451
+ if (mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout)) {
447
452
  /* TODO: warning - unable to set connection timeout */
448
- rb_warn("%s\n", mysql_error(client));
453
+ rb_warn("%s\n", mysql_error(wrapper->client));
449
454
  }
450
455
  }
451
456
  return value;
@@ -453,7 +458,7 @@ static VALUE set_connect_timeout(VALUE self, VALUE value) {
453
458
 
454
459
  static VALUE set_charset_name(VALUE self, VALUE value) {
455
460
  char * charset_name;
456
- GET_CLIENT(self)
461
+ GET_CLIENT(self);
457
462
 
458
463
  #ifdef HAVE_RUBY_ENCODING_H
459
464
  VALUE new_encoding;
@@ -469,19 +474,19 @@ static VALUE set_charset_name(VALUE self, VALUE value) {
469
474
 
470
475
  charset_name = StringValuePtr(value);
471
476
 
472
- if (mysql_options(client, MYSQL_SET_CHARSET_NAME, charset_name)) {
477
+ if (mysql_options(wrapper->client, MYSQL_SET_CHARSET_NAME, charset_name)) {
473
478
  /* TODO: warning - unable to set charset */
474
- rb_warn("%s\n", mysql_error(client));
479
+ rb_warn("%s\n", mysql_error(wrapper->client));
475
480
  }
476
481
 
477
482
  return value;
478
483
  }
479
484
 
480
485
  static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE capath, VALUE cipher) {
481
- GET_CLIENT(self)
486
+ GET_CLIENT(self);
482
487
 
483
488
  if(!NIL_P(ca) || !NIL_P(key)) {
484
- mysql_ssl_set(client,
489
+ mysql_ssl_set(wrapper->client,
485
490
  NIL_P(key) ? NULL : StringValuePtr(key),
486
491
  NIL_P(cert) ? NULL : StringValuePtr(cert),
487
492
  NIL_P(ca) ? NULL : StringValuePtr(ca),
@@ -493,11 +498,11 @@ static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE
493
498
  }
494
499
 
495
500
  static VALUE init_connection(VALUE self) {
496
- GET_CLIENT(self)
501
+ GET_CLIENT(self);
497
502
 
498
- if (rb_thread_blocking_region(nogvl_init, client, RUBY_UBF_IO, 0) == Qfalse) {
503
+ if (rb_thread_blocking_region(nogvl_init, ((void *) &wrapper->client), RUBY_UBF_IO, 0) == Qfalse) {
499
504
  /* TODO: warning - not enough memory? */
500
- return rb_raise_mysql2_error(client);
505
+ return rb_raise_mysql2_error(wrapper->client);
501
506
  }
502
507
 
503
508
  return self;
@@ -34,7 +34,8 @@ void init_mysql2_client();
34
34
  typedef struct {
35
35
  VALUE encoding;
36
36
  short int active;
37
- MYSQL client;
37
+ short int closed;
38
+ MYSQL *client;
38
39
  } mysql_client_wrapper;
39
40
 
40
41
  #endif
@@ -12,7 +12,7 @@ static VALUE intern_encoding_from_charset;
12
12
  static ID intern_new, intern_utc, intern_local, intern_encoding_from_charset_code,
13
13
  intern_localtime, intern_local_offset, intern_civil, intern_new_offset;
14
14
  static ID sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, sym_application_timezone,
15
- sym_local, sym_utc, sym_cast_booleans;
15
+ sym_local, sym_utc, sym_cast_booleans, sym_cache_rows;
16
16
  static ID intern_merge;
17
17
 
18
18
  static void rb_mysql_result_mark(void * wrapper) {
@@ -316,7 +316,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
316
316
  ID db_timezone, app_timezone, dbTz, appTz;
317
317
  mysql2_result_wrapper * wrapper;
318
318
  unsigned long i;
319
- int symbolizeKeys = 0, asArray = 0, castBool = 0;
319
+ int symbolizeKeys = 0, asArray = 0, castBool = 0, cacheRows = 1;
320
320
 
321
321
  GetMysql2Result(self, wrapper);
322
322
 
@@ -339,6 +339,10 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
339
339
  castBool = 1;
340
340
  }
341
341
 
342
+ if (rb_hash_aref(opts, sym_cache_rows) == Qfalse) {
343
+ cacheRows = 0;
344
+ }
345
+
342
346
  dbTz = rb_hash_aref(opts, sym_database_timezone);
343
347
  if (dbTz == sym_local) {
344
348
  db_timezone = intern_local;
@@ -369,7 +373,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
369
373
  wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
370
374
  }
371
375
 
372
- if (wrapper->lastRowProcessed == wrapper->numberOfRows) {
376
+ if (cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) {
373
377
  // we've already read the entire dataset from the C result into our
374
378
  // internal array. Lets hand that over to the user since it's ready to go
375
379
  for (i = 0; i < wrapper->numberOfRows; i++) {
@@ -380,11 +384,13 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
380
384
  rowsProcessed = RARRAY_LEN(wrapper->rows);
381
385
  for (i = 0; i < wrapper->numberOfRows; i++) {
382
386
  VALUE row;
383
- if (i < rowsProcessed) {
387
+ if (cacheRows && i < rowsProcessed) {
384
388
  row = rb_ary_entry(wrapper->rows, i);
385
389
  } else {
386
390
  row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool);
387
- rb_ary_store(wrapper->rows, i, row);
391
+ if (cacheRows) {
392
+ rb_ary_store(wrapper->rows, i, row);
393
+ }
388
394
  wrapper->lastRowProcessed++;
389
395
  }
390
396
 
@@ -453,11 +459,12 @@ void init_mysql2_result() {
453
459
  sym_cast_booleans = ID2SYM(rb_intern("cast_booleans"));
454
460
  sym_database_timezone = ID2SYM(rb_intern("database_timezone"));
455
461
  sym_application_timezone = ID2SYM(rb_intern("application_timezone"));
462
+ sym_cache_rows = ID2SYM(rb_intern("cache_rows"));
456
463
 
457
- rb_global_variable(&opt_decimal_zero); //never GC
458
464
  opt_decimal_zero = rb_str_new2("0.0");
459
- rb_global_variable(&opt_float_zero);
465
+ rb_global_variable(&opt_decimal_zero); //never GC
460
466
  opt_float_zero = rb_float_new((double)0);
467
+ rb_global_variable(&opt_float_zero);
461
468
  opt_time_year = INT2NUM(2000);
462
469
  opt_time_month = INT2NUM(1);
463
470
  opt_utc_offset = INT2NUM(0);
@@ -8,7 +8,7 @@ typedef struct {
8
8
  VALUE fields;
9
9
  VALUE rows;
10
10
  VALUE encoding;
11
- unsigned int numberOfFields;
11
+ long numberOfFields;
12
12
  unsigned long numberOfRows;
13
13
  unsigned long lastRowProcessed;
14
14
  short int resultFreed;
@@ -447,10 +447,11 @@ module ActiveRecord
447
447
  if current_index != row[:Key_name]
448
448
  next if row[:Key_name] == PRIMARY # skip the primary key
449
449
  current_index = row[:Key_name]
450
- indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique] == 0, [])
450
+ indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique] == 0, [], [])
451
451
  end
452
452
 
453
453
  indexes.last.columns << row[:Column_name]
454
+ indexes.last.lengths << row[:Sub_part]
454
455
  end
455
456
  indexes
456
457
  end
@@ -12,5 +12,5 @@ require 'mysql2/result'
12
12
  #
13
13
  # A modern, simple and very fast Mysql library for Ruby - binding to libmysql
14
14
  module Mysql2
15
- VERSION = "0.2.3"
15
+ VERSION = "0.2.4"
16
16
  end
@@ -2,12 +2,13 @@ module Mysql2
2
2
  class Client
3
3
  attr_reader :query_options
4
4
  @@default_query_options = {
5
- :as => :hash,
6
- :async => false,
7
- :cast_booleans => false,
8
- :symbolize_keys => false,
9
- :database_timezone => :local, # timezone Mysql2 will assume datetime objects are stored in
10
- :application_timezone => nil # timezone Mysql2 will convert to before handing the object back to the caller
5
+ :as => :hash, # the type of object you want each row back as; also supports :array (an array of values)
6
+ :async => false, # don't wait for a result after sending the query, you'll have to monitor the socket yourself then eventually call Mysql2::Client#async_result
7
+ :cast_booleans => false, # cast tinyint(1) fields as true/false in ruby
8
+ :symbolize_keys => false, # return field names as symbols instead of strings
9
+ :database_timezone => :local, # timezone Mysql2 will assume datetime objects are stored in
10
+ :application_timezone => nil, # timezone Mysql2 will convert to before handing the object back to the caller
11
+ :cache_rows => true # tells Mysql2 to use it's internal row cache for results
11
12
  }
12
13
 
13
14
  def initialize(opts = {})
@@ -7,5 +7,9 @@ module Mysql2
7
7
  @error_number = nil
8
8
  @sql_state = nil
9
9
  end
10
+
11
+ # Mysql gem compatibility
12
+ alias_method :errno, :error_number
13
+ alias_method :error, :message
10
14
  end
11
15
  end
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{mysql2}
8
- s.version = "0.2.3"
8
+ s.version = "0.2.4"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Brian Lopez"]
12
- s.date = %q{2010-08-20}
12
+ s.date = %q{2010-09-17}
13
13
  s.email = %q{seniorlopez@gmail.com}
14
14
  s.extensions = ["ext/mysql2/extconf.rb"]
15
15
  s.extra_rdoc_files = [
@@ -105,30 +105,32 @@ describe Mysql2::Client do
105
105
 
106
106
  # XXX this test is not deterministic (because Unix signal handling is not)
107
107
  # and may fail on a loaded system
108
- it "should run signal handlers while waiting for a response" do
109
- mark = {}
110
- trap(:USR1) { mark[:USR1] = Time.now }
111
- begin
112
- mark[:START] = Time.now
113
- pid = fork do
114
- sleep 1 # wait for client "SELECT sleep(2)" query to start
115
- Process.kill(:USR1, Process.ppid)
116
- sleep # wait for explicit kill to prevent GC disconnect
108
+ if RUBY_PLATFORM !~ /mingw|mswin/
109
+ it "should run signal handlers while waiting for a response" do
110
+ mark = {}
111
+ trap(:USR1) { mark[:USR1] = Time.now }
112
+ begin
113
+ mark[:START] = Time.now
114
+ pid = fork do
115
+ sleep 1 # wait for client "SELECT sleep(2)" query to start
116
+ Process.kill(:USR1, Process.ppid)
117
+ sleep # wait for explicit kill to prevent GC disconnect
118
+ end
119
+ @client.query("SELECT sleep(2)")
120
+ mark[:END] = Time.now
121
+ mark.include?(:USR1).should be_true
122
+ (mark[:USR1] - mark[:START]).should >= 1
123
+ (mark[:USR1] - mark[:START]).should < 1.1
124
+ (mark[:END] - mark[:USR1]).should > 0.9
125
+ (mark[:END] - mark[:START]).should >= 2
126
+ (mark[:END] - mark[:START]).should < 2.1
127
+ Process.kill(:TERM, pid)
128
+ Process.waitpid2(pid)
129
+ ensure
130
+ trap(:USR1, 'DEFAULT')
117
131
  end
118
- @client.query("SELECT sleep(2)")
119
- mark[:END] = Time.now
120
- mark.include?(:USR1).should be_true
121
- (mark[:USR1] - mark[:START]).should >= 1
122
- (mark[:USR1] - mark[:START]).should < 1.1
123
- (mark[:END] - mark[:USR1]).should > 0.9
124
- (mark[:END] - mark[:START]).should >= 2
125
- (mark[:END] - mark[:START]).should < 2.1
126
- Process.kill(:TERM, pid)
127
- Process.waitpid2(pid)
128
- ensure
129
- trap(:USR1, 'DEFAULT')
130
132
  end
131
- end if RUBY_PLATFORM !~ /mingw|mswin/
133
+ end
132
134
  end
133
135
 
134
136
  it "should respond to #escape" do
@@ -144,6 +146,18 @@ describe Mysql2::Client do
144
146
  @client.escape(str).object_id.should eql(str.object_id)
145
147
  end
146
148
 
149
+ it "#escape should not overflow the thread stack" do
150
+ lambda {
151
+ Thread.new { @client.escape("'" * 256 * 1024) }.join
152
+ }.should_not raise_error(SystemStackError)
153
+ end
154
+
155
+ it "#escape should not overflow the process stack" do
156
+ lambda {
157
+ Thread.new { @client.escape("'" * 1024 * 1024 * 4) }.join
158
+ }.should_not raise_error(SystemStackError)
159
+ end
160
+
147
161
  it "should respond to #info" do
148
162
  @client.should respond_to(:info)
149
163
  end
@@ -13,4 +13,13 @@ describe Mysql2::Error do
13
13
  it "should respond to #sql_state" do
14
14
  @error.should respond_to(:sql_state)
15
15
  end
16
+
17
+ # Mysql gem compatibility
18
+ it "should alias #error_number to #errno" do
19
+ @error.should respond_to(:errno)
20
+ end
21
+
22
+ it "should alias #message to #error" do
23
+ @error.should respond_to(:error)
24
+ end
16
25
  end
@@ -47,8 +47,13 @@ describe Mysql2::Result do
47
47
  end
48
48
  end
49
49
 
50
- it "should cache previously yielded results" do
51
- @result.first.should eql(@result.first)
50
+ it "should cache previously yielded results by default" do
51
+ @result.first.object_id.should eql(@result.first.object_id)
52
+ end
53
+
54
+ it "should not cache previously yielded results if cache_rows is disabled" do
55
+ result = @client.query "SELECT 1", :cache_rows => false
56
+ result.first.object_id.should_not eql(result.first.object_id)
52
57
  end
53
58
  end
54
59
 
@@ -1,10 +1,15 @@
1
1
  gem 'rake-compiler', '~> 0.7.1'
2
2
  require "rake/extensiontask"
3
3
 
4
- MYSQL_VERSION = "5.1.49"
5
- MYSQL_MIRROR = ENV['MYSQL_MIRROR'] || "http://mysql.localhost.net.ar"
4
+ MYSQL_VERSION = "5.1.50"
5
+ MYSQL_MIRROR = ENV['MYSQL_MIRROR'] || "http://mysql.mirrors.pair.com"
6
6
 
7
- Rake::ExtensionTask.new("mysql2", JEWELER.gemspec) do |ext|
7
+
8
+ def gemspec
9
+ @clean_gemspec ||= eval(File.read(File.expand_path('../../mysql2.gemspec', __FILE__)))
10
+ end
11
+
12
+ Rake::ExtensionTask.new("mysql2", gemspec) do |ext|
8
13
  # reference where the vendored MySQL got extracted
9
14
  mysql_lib = File.expand_path(File.join(File.dirname(__FILE__), '..', 'vendor', "mysql-#{MYSQL_VERSION}-win32"))
10
15
 
@@ -12,8 +17,16 @@ Rake::ExtensionTask.new("mysql2", JEWELER.gemspec) do |ext|
12
17
  if RUBY_PLATFORM =~ /mswin|mingw/ then
13
18
  ext.config_options << "--with-mysql-include=#{mysql_lib}/include"
14
19
  ext.config_options << "--with-mysql-lib=#{mysql_lib}/lib/opt"
20
+ else
21
+ ext.cross_compile = true
22
+ ext.cross_platform = ['x86-mingw32', 'x86-mswin32-60']
23
+ ext.cross_config_options << "--with-mysql-include=#{mysql_lib}/include"
24
+ ext.cross_config_options << "--with-mysql-lib=#{mysql_lib}/lib/opt"
15
25
  end
16
26
 
17
27
  ext.lib_dir = File.join 'lib', 'mysql2'
28
+
29
+ # clean compiled extension
30
+ CLEAN.include "#{ext.lib_dir}/*.#{RbConfig::CONFIG['DLEXT']}"
18
31
  end
19
32
  Rake::Task[:spec].prerequisites << :compile
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: 17
4
+ hash: 31
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 2
9
- - 3
10
- version: 0.2.3
9
+ - 4
10
+ version: 0.2.4
11
11
  platform: ruby
12
12
  authors:
13
13
  - Brian Lopez
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-08-20 00:00:00 -07:00
18
+ date: 2010-09-17 00:00:00 -07:00
19
19
  default_executable:
20
20
  dependencies: []
21
21