mysql2 0.2.3 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -9,3 +9,4 @@ mkmf.log
9
9
  pkg/
10
10
  tmp
11
11
  vendor
12
+ lib/mysql2/mysql2.rb
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --colour
data/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.5 (October 19th, 2010)
4
+ * fixes for easier Win32 binary gem deployment for targeting 1.8 and 1.9 in the same gem
5
+ * refactor of connection checks and management to avoid race conditions with the GC/threading to prevent the unexpected loss of connections
6
+ * update the default flags during connection
7
+ * add support for setting wait_timeout on AR adapter
8
+ * upgrade to rspec2
9
+ * bugfix for an edge case where the GC would clean up a Mysql2::Client object before the underlying MYSQL pointer had been initialized
10
+ * fix to CFLAGS to allow compilation on SPARC with sunstudio compiler - Anko painting <anko.com+github@gmail.com>
11
+
12
+ ## 0.2.4 (September 17th, 2010)
13
+ * a few patches for win32 support from Luis Lavena - thanks man!
14
+ * bugfix from Eric Wong to avoid a potential stack overflow during Mysql2::Client#escape
15
+ * added the ability to turn internal row caching on/off via the :cache_rows => true/false option
16
+ * a couple of small patches for rbx compatibility
17
+ * set IndexDefinition#length in AR adapter - Kouhei Yanagita <yanagi@shakenbu.org>
18
+ * fix a long-standing data corruption bug - thank you thank you thank you to @joedamato (http://github.com/ice799)
19
+ * bugfix from calling mysql_close on a closed/freed connection surfaced by the above fix
20
+
3
21
  ## 0.2.3 (August 20th, 2010)
4
22
  * connection flags can now be passed to the constructor via the :flags key
5
23
  * switch AR adapter connection over to use FOUND_ROWS option
data/README.rdoc CHANGED
@@ -89,18 +89,18 @@ or
89
89
 
90
90
  === Array of Arrays
91
91
 
92
- Pass the {: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
@@ -224,7 +234,7 @@ then iterating over every row using an #each like method yielding a block:
224
234
 
225
235
  == Special Thanks
226
236
 
227
- * Eric Wong - for the contribution (and informative explanations of) of some thread-safety, non-blocking I/O and cleanup patches. You rock dude
237
+ * Eric Wong - for the contribution (and the informative explanations) of some thread-safety, non-blocking I/O and cleanup patches. You rock dude
228
238
  * Yury Korolev (http://github.com/yury) - for TONS of help testing the ActiveRecord adapter
229
239
  * Aaron Patterson (http://github.com/tenderlove) - tons of contributions, suggestions and general badassness
230
240
  * Mike Perham (http://github.com/mperham) - Async ActiveRecord adapter (uses Fibers and EventMachine)
data/Rakefile CHANGED
@@ -1,42 +1,5 @@
1
1
  # encoding: UTF-8
2
- begin
3
- require 'jeweler'
4
- JEWELER = Jeweler::Tasks.new do |gem|
5
- gem.name = "mysql2"
6
- gem.summary = "A simple, fast Mysql library for Ruby, binding to libmysql"
7
- gem.email = "seniorlopez@gmail.com"
8
- gem.homepage = "http://github.com/brianmario/mysql2"
9
- gem.authors = ["Brian Lopez"]
10
- gem.require_paths = ["lib", "ext"]
11
- gem.extra_rdoc_files = `git ls-files *.rdoc`.split("\n")
12
- gem.files = `git ls-files`.split("\n")
13
- gem.extensions = ["ext/mysql2/extconf.rb"]
14
- gem.files.include %w(lib/jeweler/templates/.document lib/jeweler/templates/.gitignore)
15
- # gem.rubyforge_project = "mysql2"
16
- end
17
- rescue LoadError
18
- puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install jeweler -s http://gems.github.com"
19
- end
20
-
21
2
  require 'rake'
22
- require 'spec/rake/spectask'
23
-
24
- desc "Run all examples with RCov"
25
- Spec::Rake::SpecTask.new('spec:rcov') do |t|
26
- t.spec_files = FileList['spec/']
27
- t.rcov = true
28
- t.rcov_opts = lambda do
29
- IO.readlines("spec/rcov.opts").map {|l| l.chomp.split " "}.flatten
30
- end
31
- end
32
- Spec::Rake::SpecTask.new('spec') do |t|
33
- t.spec_files = FileList['spec/']
34
- t.spec_opts << '--options' << 'spec/spec.opts'
35
- t.verbose = true
36
- t.warning = true
37
- end
38
-
39
- task :default => :spec
40
3
 
41
4
  # Load custom tasks
42
5
  Dir['tasks/*.rake'].sort.each { |f| load f }
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.3
1
+ 0.2.5
@@ -70,7 +70,7 @@ Benchmark.bmbm do |x|
70
70
  end
71
71
 
72
72
  do_mysql = DataObjects::Connection.new("mysql://localhost/#{database}")
73
- command = 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
data/ext/mysql2/client.c CHANGED
@@ -8,20 +8,18 @@ static VALUE intern_encoding_from_charset;
8
8
  static ID sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as, sym_array;
9
9
  static ID intern_merge, intern_error_number_eql, intern_sql_state_eql;
10
10
 
11
- #define REQUIRE_OPEN_DB(_ctxt) \
12
- if(!_ctxt->net.vio) { \
11
+ #define REQUIRE_OPEN_DB(wrapper) \
12
+ if(wrapper->closed) { \
13
13
  rb_raise(cMysql2Error, "closed MySQL connection"); \
14
14
  return Qnil; \
15
15
  }
16
16
 
17
17
  #define MARK_CONN_INACTIVE(conn) \
18
- wrapper->active = 0;
18
+ wrapper->active = 0
19
19
 
20
20
  #define GET_CLIENT(self) \
21
21
  mysql_client_wrapper *wrapper; \
22
- 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,11 +83,10 @@ static VALUE rb_raise_mysql2_error(MYSQL *client) {
85
83
  }
86
84
 
87
85
  static VALUE nogvl_init(void *ptr) {
88
- MYSQL * client = (MYSQL *)ptr;
86
+ MYSQL *client;
89
87
 
90
88
  /* may initialize embedded server and read /etc/services off disk */
91
- client = mysql_init(NULL);
92
-
89
+ client = mysql_init((MYSQL *)ptr);
93
90
  return client ? Qtrue : Qfalse;
94
91
  }
95
92
 
@@ -107,38 +104,42 @@ static VALUE nogvl_connect(void *ptr) {
107
104
  return client ? Qtrue : Qfalse;
108
105
  }
109
106
 
110
- static void rb_mysql_client_free(void * ptr) {
111
- mysql_client_wrapper * wrapper = (mysql_client_wrapper *)ptr;
112
- MYSQL * client = &wrapper->client;
113
-
114
- /*
115
- * we'll send a QUIT message to the server, but that message is more of a
116
- * formality than a hard requirement since the socket is getting shutdown
117
- * anyways, so ensure the socket write does not block our interpreter
118
- */
119
- int fd = client->net.fd;
120
- int flags;
121
-
122
- if (fd >= 0) {
107
+ static VALUE nogvl_close(void *ptr) {
108
+ mysql_client_wrapper *wrapper = ptr;
109
+ if (!wrapper->closed) {
110
+ wrapper->closed = 1;
111
+
123
112
  /*
113
+ * we'll send a QUIT message to the server, but that message is more of a
114
+ * formality than a hard requirement since the socket is getting shutdown
115
+ * anyways, so ensure the socket write does not block our interpreter
116
+ *
117
+ *
124
118
  * if the socket is dead we have no chance of blocking,
125
119
  * so ignore any potential fcntl errors since they don't matter
126
120
  */
127
- flags = fcntl(fd, F_GETFL);
121
+ #ifndef _WIN32
122
+ int flags = fcntl(wrapper->client->net.fd, F_GETFL);
128
123
  if (flags > 0 && !(flags & O_NONBLOCK))
129
- fcntl(fd, F_SETFL, flags | O_NONBLOCK);
124
+ fcntl(wrapper->client->net.fd, F_SETFL, flags | O_NONBLOCK);
125
+ #else
126
+ u_long iMode = 1;
127
+ ioctlsocket(wrapper->client->net.fd, FIONBIO, &iMode);
128
+ #endif
129
+
130
+ mysql_close(wrapper->client);
131
+ free(wrapper->client);
130
132
  }
131
133
 
132
- /* It's safe to call mysql_close() on an already closed connection. */
133
- mysql_close(client);
134
- xfree(ptr);
134
+ return Qnil;
135
135
  }
136
136
 
137
- static VALUE nogvl_close(void * ptr) {
138
- MYSQL *client = (MYSQL *)ptr;
139
- mysql_close(client);
140
- client->net.fd = -1;
141
- return Qnil;
137
+ static void rb_mysql_client_free(void * ptr) {
138
+ mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr;
139
+
140
+ nogvl_close(wrapper);
141
+
142
+ xfree(ptr);
142
143
  }
143
144
 
144
145
  static VALUE allocate(VALUE klass) {
@@ -147,12 +148,14 @@ static VALUE allocate(VALUE klass) {
147
148
  obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper);
148
149
  wrapper->encoding = Qnil;
149
150
  wrapper->active = 0;
151
+ wrapper->closed = 1;
152
+ wrapper->client = (MYSQL*)malloc(sizeof(MYSQL));
150
153
  return obj;
151
154
  }
152
155
 
153
156
  static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags) {
154
157
  struct nogvl_connect_args args;
155
- GET_CLIENT(self)
158
+ GET_CLIENT(self);
156
159
 
157
160
  args.host = NIL_P(host) ? "localhost" : StringValuePtr(host);
158
161
  args.unix_socket = NIL_P(socket) ? NULL : StringValuePtr(socket);
@@ -160,12 +163,12 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
160
163
  args.user = NIL_P(user) ? NULL : StringValuePtr(user);
161
164
  args.passwd = NIL_P(pass) ? NULL : StringValuePtr(pass);
162
165
  args.db = NIL_P(database) ? NULL : StringValuePtr(database);
163
- args.mysql = client;
164
- args.client_flag = NUM2INT(flags);
166
+ args.mysql = wrapper->client;
167
+ args.client_flag = NUM2ULONG(flags);
165
168
 
166
169
  if (rb_thread_blocking_region(nogvl_connect, &args, RUBY_UBF_IO, 0) == Qfalse) {
167
170
  // unable to connect
168
- return rb_raise_mysql2_error(client);
171
+ return rb_raise_mysql2_error(wrapper->client);
169
172
  }
170
173
 
171
174
  return self;
@@ -178,9 +181,11 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
178
181
  * for the garbage collector.
179
182
  */
180
183
  static VALUE rb_mysql_client_close(VALUE self) {
181
- GET_CLIENT(self)
184
+ GET_CLIENT(self);
182
185
 
183
- rb_thread_blocking_region(nogvl_close, client, RUBY_UBF_IO, 0);
186
+ if (!wrapper->closed) {
187
+ rb_thread_blocking_region(nogvl_close, wrapper, RUBY_UBF_IO, 0);
188
+ }
184
189
 
185
190
  return Qnil;
186
191
  }
@@ -221,30 +226,30 @@ static VALUE nogvl_store_result(void *ptr) {
221
226
 
222
227
  static VALUE rb_mysql_client_async_result(VALUE self) {
223
228
  MYSQL_RES * result;
224
- GET_CLIENT(self)
229
+ GET_CLIENT(self);
225
230
 
226
- REQUIRE_OPEN_DB(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,8 +332,9 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
327
332
  static VALUE rb_mysql_client_escape(VALUE self, VALUE str) {
328
333
  VALUE newStr;
329
334
  unsigned long newLen, oldLen;
330
- GET_CLIENT(self)
335
+ GET_CLIENT(self);
331
336
 
337
+ REQUIRE_OPEN_DB(wrapper);
332
338
  Check_Type(str, T_STRING);
333
339
  #ifdef HAVE_RUBY_ENCODING_H
334
340
  rb_encoding *default_internal_enc = rb_default_internal_encoding();
@@ -338,15 +344,14 @@ static VALUE rb_mysql_client_escape(VALUE self, VALUE str) {
338
344
  #endif
339
345
 
340
346
  oldLen = RSTRING_LEN(str);
341
- char escaped[(oldLen*2)+1];
347
+ newStr = rb_str_new(0, oldLen*2+1);
342
348
 
343
- REQUIRE_OPEN_DB(client);
344
- newLen = mysql_real_escape_string(client, escaped, StringValuePtr(str), oldLen);
349
+ newLen = mysql_real_escape_string(wrapper->client, RSTRING_PTR(newStr), StringValuePtr(str), oldLen);
345
350
  if (newLen == oldLen) {
346
351
  // no need to return a new ruby string if nothing changed
347
352
  return str;
348
353
  } else {
349
- newStr = 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) {
@@ -360,6 +365,7 @@ static VALUE rb_mysql_client_escape(VALUE self, VALUE str) {
360
365
  static VALUE rb_mysql_client_info(VALUE self) {
361
366
  VALUE version = rb_hash_new(), client_info;
362
367
  GET_CLIENT(self);
368
+
363
369
  #ifdef HAVE_RUBY_ENCODING_H
364
370
  rb_encoding *default_internal_enc = rb_default_internal_encoding();
365
371
  rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
@@ -379,17 +385,17 @@ static VALUE rb_mysql_client_info(VALUE self) {
379
385
 
380
386
  static VALUE rb_mysql_client_server_info(VALUE self) {
381
387
  VALUE version, server_info;
382
- GET_CLIENT(self)
388
+ GET_CLIENT(self);
389
+
390
+ REQUIRE_OPEN_DB(wrapper);
383
391
  #ifdef HAVE_RUBY_ENCODING_H
384
392
  rb_encoding *default_internal_enc = rb_default_internal_encoding();
385
393
  rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
386
394
  #endif
387
395
 
388
- REQUIRE_OPEN_DB(client);
389
-
390
396
  version = rb_hash_new();
391
- rb_hash_aset(version, sym_id, LONG2FIX(mysql_get_server_version(client)));
392
- server_info = rb_str_new2(mysql_get_server_info(client));
397
+ rb_hash_aset(version, sym_id, LONG2FIX(mysql_get_server_version(wrapper->client)));
398
+ server_info = rb_str_new2(mysql_get_server_info(wrapper->client));
393
399
  #ifdef HAVE_RUBY_ENCODING_H
394
400
  rb_enc_associate(server_info, conn_enc);
395
401
  if (default_internal_enc) {
@@ -401,34 +407,40 @@ static VALUE rb_mysql_client_server_info(VALUE self) {
401
407
  }
402
408
 
403
409
  static VALUE rb_mysql_client_socket(VALUE self) {
404
- GET_CLIENT(self)
405
- REQUIRE_OPEN_DB(client);
406
- return INT2NUM(client->net.fd);
410
+ GET_CLIENT(self);
411
+ REQUIRE_OPEN_DB(wrapper);
412
+ return INT2NUM(wrapper->client->net.fd);
407
413
  }
408
414
 
409
415
  static VALUE rb_mysql_client_last_id(VALUE self) {
410
- GET_CLIENT(self)
411
- REQUIRE_OPEN_DB(client);
412
- return ULL2NUM(mysql_insert_id(client));
416
+ GET_CLIENT(self);
417
+ REQUIRE_OPEN_DB(wrapper);
418
+ return ULL2NUM(mysql_insert_id(wrapper->client));
413
419
  }
414
420
 
415
421
  static VALUE rb_mysql_client_affected_rows(VALUE self) {
416
- GET_CLIENT(self)
417
- REQUIRE_OPEN_DB(client);
418
- return ULL2NUM(mysql_affected_rows(client));
422
+ GET_CLIENT(self);
423
+ my_ulonglong retVal;
424
+
425
+ REQUIRE_OPEN_DB(wrapper);
426
+ retVal = mysql_affected_rows(wrapper->client);
427
+ if (retVal == (my_ulonglong)-1) {
428
+ rb_raise_mysql2_error(wrapper->client);
429
+ }
430
+ return ULL2NUM(retVal);
419
431
  }
420
432
 
421
433
  static VALUE set_reconnect(VALUE self, VALUE value) {
422
434
  my_bool reconnect;
423
- GET_CLIENT(self)
435
+ GET_CLIENT(self);
424
436
 
425
437
  if(!NIL_P(value)) {
426
438
  reconnect = value == Qfalse ? 0 : 1;
427
439
 
428
440
  /* set default reconnect behavior */
429
- if (mysql_options(client, MYSQL_OPT_RECONNECT, &reconnect)) {
441
+ if (mysql_options(wrapper->client, MYSQL_OPT_RECONNECT, &reconnect)) {
430
442
  /* TODO: warning - unable to set reconnect behavior */
431
- rb_warn("%s\n", mysql_error(client));
443
+ rb_warn("%s\n", mysql_error(wrapper->client));
432
444
  }
433
445
  }
434
446
  return value;
@@ -436,16 +448,16 @@ static VALUE set_reconnect(VALUE self, VALUE value) {
436
448
 
437
449
  static VALUE set_connect_timeout(VALUE self, VALUE value) {
438
450
  unsigned int connect_timeout = 0;
439
- GET_CLIENT(self)
451
+ GET_CLIENT(self);
440
452
 
441
453
  if(!NIL_P(value)) {
442
454
  connect_timeout = NUM2INT(value);
443
455
  if(0 == connect_timeout) return value;
444
456
 
445
457
  /* set default connection timeout behavior */
446
- if (mysql_options(client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout)) {
458
+ if (mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout)) {
447
459
  /* TODO: warning - unable to set connection timeout */
448
- rb_warn("%s\n", mysql_error(client));
460
+ rb_warn("%s\n", mysql_error(wrapper->client));
449
461
  }
450
462
  }
451
463
  return value;
@@ -453,7 +465,7 @@ static VALUE set_connect_timeout(VALUE self, VALUE value) {
453
465
 
454
466
  static VALUE set_charset_name(VALUE self, VALUE value) {
455
467
  char * charset_name;
456
- GET_CLIENT(self)
468
+ GET_CLIENT(self);
457
469
 
458
470
  #ifdef HAVE_RUBY_ENCODING_H
459
471
  VALUE new_encoding;
@@ -469,19 +481,19 @@ static VALUE set_charset_name(VALUE self, VALUE value) {
469
481
 
470
482
  charset_name = StringValuePtr(value);
471
483
 
472
- if (mysql_options(client, MYSQL_SET_CHARSET_NAME, charset_name)) {
484
+ if (mysql_options(wrapper->client, MYSQL_SET_CHARSET_NAME, charset_name)) {
473
485
  /* TODO: warning - unable to set charset */
474
- rb_warn("%s\n", mysql_error(client));
486
+ rb_warn("%s\n", mysql_error(wrapper->client));
475
487
  }
476
488
 
477
489
  return value;
478
490
  }
479
491
 
480
492
  static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE capath, VALUE cipher) {
481
- GET_CLIENT(self)
493
+ GET_CLIENT(self);
482
494
 
483
495
  if(!NIL_P(ca) || !NIL_P(key)) {
484
- mysql_ssl_set(client,
496
+ mysql_ssl_set(wrapper->client,
485
497
  NIL_P(key) ? NULL : StringValuePtr(key),
486
498
  NIL_P(cert) ? NULL : StringValuePtr(cert),
487
499
  NIL_P(ca) ? NULL : StringValuePtr(ca),
@@ -493,13 +505,14 @@ static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE
493
505
  }
494
506
 
495
507
  static VALUE init_connection(VALUE self) {
496
- GET_CLIENT(self)
508
+ GET_CLIENT(self);
497
509
 
498
- if (rb_thread_blocking_region(nogvl_init, client, RUBY_UBF_IO, 0) == Qfalse) {
510
+ if (rb_thread_blocking_region(nogvl_init, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
499
511
  /* TODO: warning - not enough memory? */
500
- return rb_raise_mysql2_error(client);
512
+ return rb_raise_mysql2_error(wrapper->client);
501
513
  }
502
514
 
515
+ wrapper->closed = 0;
503
516
  return self;
504
517
  }
505
518
 
data/ext/mysql2/client.h CHANGED
@@ -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
@@ -57,7 +57,7 @@ end
57
57
  asplode h unless have_header h
58
58
  end
59
59
 
60
- unless RUBY_PLATFORM =~ /mswin/
60
+ unless RUBY_PLATFORM =~ /mswin/ or RUBY_PLATFORM =~ /sparc/
61
61
  $CFLAGS << ' -Wall -funroll-loops'
62
62
  end
63
63
  # $CFLAGS << ' -O0 -ggdb3 -Wextra'
data/ext/mysql2/result.c CHANGED
@@ -12,7 +12,7 @@ static VALUE intern_encoding_from_charset;
12
12
  static ID intern_new, intern_utc, intern_local, intern_encoding_from_charset_code,
13
13
  intern_localtime, intern_local_offset, intern_civil, intern_new_offset;
14
14
  static ID sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, sym_application_timezone,
15
- sym_local, sym_utc, sym_cast_booleans;
15
+ sym_local, sym_utc, sym_cast_booleans, sym_cache_rows;
16
16
  static ID intern_merge;
17
17
 
18
18
  static void rb_mysql_result_mark(void * wrapper) {
@@ -316,7 +316,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
316
316
  ID db_timezone, app_timezone, dbTz, appTz;
317
317
  mysql2_result_wrapper * wrapper;
318
318
  unsigned long i;
319
- int symbolizeKeys = 0, asArray = 0, castBool = 0;
319
+ int symbolizeKeys = 0, asArray = 0, castBool = 0, cacheRows = 1;
320
320
 
321
321
  GetMysql2Result(self, wrapper);
322
322
 
@@ -339,6 +339,10 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
339
339
  castBool = 1;
340
340
  }
341
341
 
342
+ if (rb_hash_aref(opts, sym_cache_rows) == Qfalse) {
343
+ cacheRows = 0;
344
+ }
345
+
342
346
  dbTz = rb_hash_aref(opts, sym_database_timezone);
343
347
  if (dbTz == sym_local) {
344
348
  db_timezone = intern_local;
@@ -369,7 +373,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
369
373
  wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
370
374
  }
371
375
 
372
- if (wrapper->lastRowProcessed == wrapper->numberOfRows) {
376
+ if (cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) {
373
377
  // we've already read the entire dataset from the C result into our
374
378
  // internal array. Lets hand that over to the user since it's ready to go
375
379
  for (i = 0; i < wrapper->numberOfRows; i++) {
@@ -380,11 +384,13 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
380
384
  rowsProcessed = RARRAY_LEN(wrapper->rows);
381
385
  for (i = 0; i < wrapper->numberOfRows; i++) {
382
386
  VALUE row;
383
- if (i < rowsProcessed) {
387
+ if (cacheRows && i < rowsProcessed) {
384
388
  row = rb_ary_entry(wrapper->rows, i);
385
389
  } else {
386
390
  row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool);
387
- 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);
data/ext/mysql2/result.h CHANGED
@@ -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
@@ -616,8 +617,15 @@ module ActiveRecord
616
617
  # Turn this off. http://dev.rubyonrails.org/ticket/6778
617
618
  variable_assignments = ['SQL_AUTO_IS_NULL=0']
618
619
  encoding = @config[:encoding]
620
+
621
+ # make sure we set the encoding
619
622
  variable_assignments << "NAMES '#{encoding}'" if encoding
620
623
 
624
+ # increase timeout so mysql server doesn't disconnect us
625
+ wait_timeout = @config[:wait_timeout]
626
+ wait_timeout = 2592000 unless wait_timeout.is_a?(Fixnum)
627
+ variable_assignments << "@@wait_timeout = #{wait_timeout}"
628
+
621
629
  execute("SET #{variable_assignments.join(', ')}", :skip_logging)
622
630
  end
623
631
 
data/lib/mysql2/client.rb CHANGED
@@ -2,12 +2,14 @@ module Mysql2
2
2
  class Client
3
3
  attr_reader :query_options
4
4
  @@default_query_options = {
5
- :as => :hash,
6
- :async => false,
7
- :cast_booleans => false,
8
- :symbolize_keys => false,
9
- :database_timezone => :local, # 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
12
+ :connect_flags => REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION
11
13
  }
12
14
 
13
15
  def initialize(opts = {})
@@ -30,7 +32,7 @@ module Mysql2
30
32
  port = opts[:port] || 3306
31
33
  database = opts[:database]
32
34
  socket = opts[:socket]
33
- flags = opts[:flags] || 0
35
+ flags = opts[:flags] ? opts[:flags] | @query_options[:connect_flags] : @query_options[:connect_flags]
34
36
 
35
37
  connect user, pass, host, port, database, socket, flags
36
38
  end
data/lib/mysql2/em.rb CHANGED
@@ -13,12 +13,12 @@ module Mysql2
13
13
  end
14
14
 
15
15
  def notify_readable
16
+ detach
16
17
  begin
17
18
  @deferable.succeed(@client.async_result)
18
19
  rescue Exception => e
19
20
  @deferable.fail(e)
20
21
  end
21
- detach
22
22
  end
23
23
  end
24
24
 
data/lib/mysql2/error.rb CHANGED
@@ -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
data/lib/mysql2.rb CHANGED
@@ -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.5"
16
16
  end
data/mysql2.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{mysql2}
8
- s.version = "0.2.3"
8
+ s.version = "0.2.5"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Brian Lopez"]
12
- s.date = %q{2010-08-20}
12
+ s.date = %q{2010-10-19}
13
13
  s.email = %q{seniorlopez@gmail.com}
14
14
  s.extensions = ["ext/mysql2/extconf.rb"]
15
15
  s.extra_rdoc_files = [
@@ -17,6 +17,7 @@ Gem::Specification.new do |s|
17
17
  ]
18
18
  s.files = [
19
19
  ".gitignore",
20
+ ".rspec",
20
21
  "CHANGELOG.md",
21
22
  "MIT-LICENSE",
22
23
  "README.rdoc",
@@ -29,7 +30,6 @@ Gem::Specification.new do |s|
29
30
  "benchmark/query_without_mysql_casting.rb",
30
31
  "benchmark/sequel.rb",
31
32
  "benchmark/setup_db.rb",
32
- "benchmark/thread_alone.rb",
33
33
  "examples/eventmachine.rb",
34
34
  "examples/threaded.rb",
35
35
  "ext/mysql2/client.c",
@@ -54,10 +54,11 @@ Gem::Specification.new do |s|
54
54
  "spec/mysql2/error_spec.rb",
55
55
  "spec/mysql2/result_spec.rb",
56
56
  "spec/rcov.opts",
57
- "spec/spec.opts",
58
57
  "spec/spec_helper.rb",
59
58
  "tasks/benchmarks.rake",
60
59
  "tasks/compile.rake",
60
+ "tasks/jeweler.rake",
61
+ "tasks/rspec.rake",
61
62
  "tasks/vendor_mysql.rake"
62
63
  ]
63
64
  s.homepage = %q{http://github.com/brianmario/mysql2}
data/spec/em/em_spec.rb CHANGED
@@ -1,26 +1,49 @@
1
1
  # encoding: UTF-8
2
- require 'spec_helper'
3
- require 'mysql2/em'
2
+ if defined? EventMachine
3
+ require 'spec_helper'
4
+ require 'mysql2/em'
4
5
 
5
- describe Mysql2::EM::Client do
6
- it "should support async queries" do
7
- results = []
8
- EM.run do
9
- client1 = Mysql2::EM::Client.new
10
- defer1 = client1.query "SELECT sleep(0.05) as first_query"
11
- defer1.callback do |result|
12
- results << result.first
13
- EM.stop_event_loop
6
+ describe Mysql2::EM::Client do
7
+ it "should support async queries" do
8
+ results = []
9
+ EM.run do
10
+ client1 = Mysql2::EM::Client.new
11
+ defer1 = client1.query "SELECT sleep(0.1) as first_query"
12
+ defer1.callback do |result|
13
+ results << result.first
14
+ EM.stop_event_loop
15
+ end
16
+
17
+ client2 = Mysql2::EM::Client.new
18
+ defer2 = client2.query "SELECT sleep(0.025) second_query"
19
+ defer2.callback do |result|
20
+ results << result.first
21
+ end
14
22
  end
15
23
 
16
- client2 = Mysql2::EM::Client.new
17
- defer2 = client2.query "SELECT sleep(0.025) second_query"
18
- defer2.callback do |result|
19
- results << result.first
24
+ results[0].keys.should include("second_query")
25
+ results[1].keys.should include("first_query")
26
+ end
27
+
28
+ it "should support queries in callbacks" do
29
+ results = []
30
+ EM.run do
31
+ client = Mysql2::EM::Client.new
32
+ defer1 = client.query "SELECT sleep(0.025) as first_query"
33
+ defer1.callback do |result|
34
+ results << result.first
35
+ defer2 = client.query "SELECT sleep(0.025) as second_query"
36
+ defer2.callback do |result|
37
+ results << result.first
38
+ EM.stop_event_loop
39
+ end
40
+ end
20
41
  end
42
+
43
+ results[0].keys.should include("first_query")
44
+ results[1].keys.should include("second_query")
21
45
  end
22
-
23
- results[0].keys.should include("second_query")
24
- results[1].keys.should include("first_query")
25
46
  end
26
- end
47
+ else
48
+ puts "EventMachine not installed, skipping the specs that use it"
49
+ end
@@ -23,10 +23,10 @@ describe Mysql2::Client do
23
23
  end
24
24
  end
25
25
  client = klient.new :flags => Mysql2::Client::FOUND_ROWS
26
- client.connect_args.last.last.should == Mysql2::Client::FOUND_ROWS
26
+ (client.connect_args.last.last & Mysql2::Client::FOUND_ROWS).should be_true
27
27
  end
28
28
 
29
- it "should default flags to 0" do
29
+ it "should default flags to (REMEMBER_OPTIONS, LONG_PASSWORD, LONG_FLAG, TRANSACTIONS, PROTOCOL_41, SECURE_CONNECTION)" do
30
30
  klient = Class.new(Mysql2::Client) do
31
31
  attr_reader :connect_args
32
32
  def connect *args
@@ -35,7 +35,12 @@ describe Mysql2::Client do
35
35
  end
36
36
  end
37
37
  client = klient.new
38
- client.connect_args.last.last.should == 0
38
+ (client.connect_args.last.last & (Mysql2::Client::REMEMBER_OPTIONS |
39
+ Mysql2::Client::LONG_PASSWORD |
40
+ Mysql2::Client::LONG_FLAG |
41
+ Mysql2::Client::TRANSACTIONS |
42
+ Mysql2::Client::PROTOCOL_41 |
43
+ Mysql2::Client::SECURE_CONNECTION)).should be_true
39
44
  end
40
45
 
41
46
  it "should have a global default_query_options hash" do
@@ -71,6 +76,9 @@ describe Mysql2::Client do
71
76
 
72
77
  it "should be able to close properly" do
73
78
  @client.close.should be_nil
79
+ lambda {
80
+ @client.query "SELECT 1"
81
+ }.should raise_error(Mysql2::Error)
74
82
  end
75
83
 
76
84
  it "should respond to #query" do
@@ -103,45 +111,75 @@ describe Mysql2::Client do
103
111
  }.should raise_error(Mysql2::Error)
104
112
  end
105
113
 
114
+ it "should require an open connection" do
115
+ @client.close
116
+ lambda {
117
+ @client.query "SELECT 1"
118
+ }.should raise_error(Mysql2::Error)
119
+ end
120
+
106
121
  # XXX this test is not deterministic (because Unix signal handling is not)
107
122
  # and may fail on a loaded system
108
- 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
123
+ if RUBY_PLATFORM !~ /mingw|mswin/
124
+ it "should run signal handlers while waiting for a response" do
125
+ mark = {}
126
+ trap(:USR1) { mark[:USR1] = Time.now }
127
+ begin
128
+ mark[:START] = Time.now
129
+ pid = fork do
130
+ sleep 1 # wait for client "SELECT sleep(2)" query to start
131
+ Process.kill(:USR1, Process.ppid)
132
+ sleep # wait for explicit kill to prevent GC disconnect
133
+ end
134
+ @client.query("SELECT sleep(2)")
135
+ mark[:END] = Time.now
136
+ mark.include?(:USR1).should be_true
137
+ (mark[:USR1] - mark[:START]).should >= 1
138
+ (mark[:USR1] - mark[:START]).should < 1.1
139
+ (mark[:END] - mark[:USR1]).should > 0.9
140
+ (mark[:END] - mark[:START]).should >= 2
141
+ (mark[:END] - mark[:START]).should < 2.1
142
+ Process.kill(:TERM, pid)
143
+ Process.waitpid2(pid)
144
+ ensure
145
+ trap(:USR1, 'DEFAULT')
117
146
  end
118
- @client.query("SELECT sleep(2)")
119
- mark[:END] = Time.now
120
- mark.include?(:USR1).should be_true
121
- (mark[:USR1] - mark[:START]).should >= 1
122
- (mark[:USR1] - mark[:START]).should < 1.1
123
- (mark[:END] - mark[:USR1]).should > 0.9
124
- (mark[:END] - mark[:START]).should >= 2
125
- (mark[:END] - mark[:START]).should < 2.1
126
- Process.kill(:TERM, pid)
127
- Process.waitpid2(pid)
128
- ensure
129
- trap(:USR1, 'DEFAULT')
130
147
  end
131
- end if RUBY_PLATFORM !~ /mingw|mswin/
148
+ end
132
149
  end
133
150
 
134
151
  it "should respond to #escape" do
135
152
  @client.should respond_to(:escape)
136
153
  end
137
154
 
138
- it "#escape should return a new SQL-escape version of the passed string" do
139
- @client.escape("abc'def\"ghi\0jkl%mno").should eql("abc\\'def\\\"ghi\\0jkl%mno")
140
- end
155
+ context "#escape" do
156
+ it "should return a new SQL-escape version of the passed string" do
157
+ @client.escape("abc'def\"ghi\0jkl%mno").should eql("abc\\'def\\\"ghi\\0jkl%mno")
158
+ end
159
+
160
+ it "should return the passed string if nothing was escaped" do
161
+ str = "plain"
162
+ @client.escape(str).object_id.should eql(str.object_id)
163
+ end
164
+
165
+ it "should not overflow the thread stack" do
166
+ lambda {
167
+ Thread.new { @client.escape("'" * 256 * 1024) }.join
168
+ }.should_not raise_error(SystemStackError)
169
+ end
141
170
 
142
- it "#escape should return the passed string if nothing was escaped" do
143
- str = "plain"
144
- @client.escape(str).object_id.should eql(str.object_id)
171
+ it "should not overflow the process stack" do
172
+ lambda {
173
+ Thread.new { @client.escape("'" * 1024 * 1024 * 4) }.join
174
+ }.should_not raise_error(SystemStackError)
175
+ end
176
+
177
+ it "should require an open connection" do
178
+ @client.close
179
+ lambda {
180
+ @client.escape ""
181
+ }.should raise_error(Mysql2::Error)
182
+ end
145
183
  end
146
184
 
147
185
  it "should respond to #info" do
@@ -189,6 +227,13 @@ describe Mysql2::Client do
189
227
  server_info[:version].class.should eql(String)
190
228
  end
191
229
 
230
+ it "#server_info should require an open connection" do
231
+ @client.close
232
+ lambda {
233
+ @client.server_info
234
+ }.should raise_error(Mysql2::Error)
235
+ end
236
+
192
237
  if defined? Encoding
193
238
  context "strings returned by #server_info" do
194
239
  it "should default to the connection's encoding if Encoding.default_internal is nil" do
@@ -217,6 +262,13 @@ describe Mysql2::Client do
217
262
  @client.socket.should_not eql(0)
218
263
  end
219
264
 
265
+ it "#socket should require an open connection" do
266
+ @client.close
267
+ lambda {
268
+ @client.socket
269
+ }.should raise_error(Mysql2::Error)
270
+ end
271
+
220
272
  it "should raise a Mysql2::Error exception upon connection failure" do
221
273
  lambda {
222
274
  bad_client = Mysql2::Client.new :host => "dfjhdi9wrhw", :username => 'asdfasdf8d2h'
@@ -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
 
data/spec/spec_helper.rb CHANGED
@@ -1,10 +1,11 @@
1
1
  # encoding: UTF-8
2
2
 
3
- require 'rubygems'
3
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
4
+ require 'rspec'
4
5
  require 'mysql2'
5
6
  require 'timeout'
6
7
 
7
- Spec::Runner.configure do |config|
8
+ RSpec.configure do |config|
8
9
  config.before(:all) do
9
10
  client = Mysql2::Client.new :database => 'test'
10
11
  client.query %[
data/tasks/compile.rake CHANGED
@@ -1,19 +1,54 @@
1
1
  gem 'rake-compiler', '~> 0.7.1'
2
2
  require "rake/extensiontask"
3
3
 
4
- MYSQL_VERSION = "5.1.49"
5
- MYSQL_MIRROR = ENV['MYSQL_MIRROR'] || "http://mysql.localhost.net.ar"
4
+ MYSQL_VERSION = "5.1.51"
5
+ MYSQL_MIRROR = ENV['MYSQL_MIRROR'] || "http://mysql.he.net/"
6
6
 
7
- Rake::ExtensionTask.new("mysql2", JEWELER.gemspec) do |ext|
7
+ def gemspec
8
+ @clean_gemspec ||= eval(File.read(File.expand_path('../../mysql2.gemspec', __FILE__)))
9
+ end
10
+
11
+ Rake::ExtensionTask.new("mysql2", gemspec) do |ext|
8
12
  # reference where the vendored MySQL got extracted
9
13
  mysql_lib = File.expand_path(File.join(File.dirname(__FILE__), '..', 'vendor', "mysql-#{MYSQL_VERSION}-win32"))
10
14
 
15
+ # DRY options feed into compile or cross-compile process
16
+ windows_options = [
17
+ "--with-mysql-include=#{mysql_lib}/include",
18
+ "--with-mysql-lib=#{mysql_lib}/lib/opt"
19
+ ]
20
+
11
21
  # automatically add build options to avoid need of manual input
12
22
  if RUBY_PLATFORM =~ /mswin|mingw/ then
13
- ext.config_options << "--with-mysql-include=#{mysql_lib}/include"
14
- ext.config_options << "--with-mysql-lib=#{mysql_lib}/lib/opt"
23
+ ext.config_options = windows_options
24
+ else
25
+ ext.cross_compile = true
26
+ ext.cross_platform = ['x86-mingw32', 'x86-mswin32-60']
27
+ ext.cross_config_options = windows_options
28
+
29
+ # inject 1.8/1.9 pure-ruby entry point when cross compiling only
30
+ ext.cross_compiling do |spec|
31
+ spec.files << 'lib/mysql2/mysql2.rb'
32
+ end
15
33
  end
16
34
 
17
35
  ext.lib_dir = File.join 'lib', 'mysql2'
36
+
37
+ # clean compiled extension
38
+ CLEAN.include "#{ext.lib_dir}/*.#{RbConfig::CONFIG['DLEXT']}"
18
39
  end
19
40
  Rake::Task[:spec].prerequisites << :compile
41
+
42
+ file 'lib/mysql2/mysql2.rb' do |t|
43
+ name = gemspec.name
44
+ File.open(t.name, 'wb') do |f|
45
+ f.write <<-eoruby
46
+ RUBY_VERSION =~ /(\\d+.\\d+)/
47
+ require "#{name}/\#{$1}/#{name}"
48
+ eoruby
49
+ end
50
+ end
51
+
52
+ if Rake::Task.task_defined?(:cross)
53
+ Rake::Task[:cross].prerequisites << "lib/mysql2/mysql2.rb"
54
+ end
@@ -0,0 +1,17 @@
1
+ begin
2
+ require 'jeweler'
3
+ JEWELER = Jeweler::Tasks.new do |gem|
4
+ gem.name = "mysql2"
5
+ gem.summary = "A simple, fast Mysql library for Ruby, binding to libmysql"
6
+ gem.email = "seniorlopez@gmail.com"
7
+ gem.homepage = "http://github.com/brianmario/mysql2"
8
+ gem.authors = ["Brian Lopez"]
9
+ gem.require_paths = ["lib", "ext"]
10
+ gem.extra_rdoc_files = `git ls-files *.rdoc`.split("\n")
11
+ gem.files = `git ls-files`.split("\n")
12
+ gem.extensions = ["ext/mysql2/extconf.rb"]
13
+ gem.files.include %w(lib/jeweler/templates/.document lib/jeweler/templates/.gitignore)
14
+ end
15
+ rescue LoadError
16
+ puts "jeweler, or one of its dependencies, is not available. Install it with: sudo gem install jeweler"
17
+ end
data/tasks/rspec.rake ADDED
@@ -0,0 +1,16 @@
1
+ begin
2
+ require 'rspec'
3
+ require 'rspec/core/rake_task'
4
+
5
+ desc "Run all examples with RCov"
6
+ RSpec::Core::RakeTask.new('spec:rcov') do |t|
7
+ t.rcov = true
8
+ end
9
+ RSpec::Core::RakeTask.new('spec') do |t|
10
+ t.verbose = true
11
+ end
12
+
13
+ task :default => :spec
14
+ rescue LoadError
15
+ puts "rspec, or one of its dependencies, is not available. Install it with: sudo gem install rspec"
16
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mysql2
3
3
  version: !ruby/object:Gem::Version
4
- hash: 17
4
+ hash: 29
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 2
9
- - 3
10
- version: 0.2.3
9
+ - 5
10
+ version: 0.2.5
11
11
  platform: ruby
12
12
  authors:
13
13
  - Brian Lopez
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-08-20 00:00:00 -07:00
18
+ date: 2010-10-19 00:00:00 -07:00
19
19
  default_executable:
20
20
  dependencies: []
21
21
 
@@ -29,6 +29,7 @@ extra_rdoc_files:
29
29
  - README.rdoc
30
30
  files:
31
31
  - .gitignore
32
+ - .rspec
32
33
  - CHANGELOG.md
33
34
  - MIT-LICENSE
34
35
  - README.rdoc
@@ -41,7 +42,6 @@ files:
41
42
  - benchmark/query_without_mysql_casting.rb
42
43
  - benchmark/sequel.rb
43
44
  - benchmark/setup_db.rb
44
- - benchmark/thread_alone.rb
45
45
  - examples/eventmachine.rb
46
46
  - examples/threaded.rb
47
47
  - ext/mysql2/client.c
@@ -66,10 +66,11 @@ files:
66
66
  - spec/mysql2/error_spec.rb
67
67
  - spec/mysql2/result_spec.rb
68
68
  - spec/rcov.opts
69
- - spec/spec.opts
70
69
  - spec/spec_helper.rb
71
70
  - tasks/benchmarks.rake
72
71
  - tasks/compile.rake
72
+ - tasks/jeweler.rake
73
+ - tasks/rspec.rake
73
74
  - tasks/vendor_mysql.rake
74
75
  has_rdoc: true
75
76
  homepage: http://github.com/brianmario/mysql2
@@ -1,20 +0,0 @@
1
- # encoding: UTF-8
2
- $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
3
-
4
- require 'rubygems'
5
- require 'benchmark'
6
- require 'mysql2'
7
-
8
- iterations = 1000
9
- client = Mysql2::Client.new(:host => "localhost", :username => "root", :database => "test")
10
- query = lambda{ iterations.times{ client.query("SELECT mysql2_test.* FROM mysql2_test") } }
11
- Benchmark.bmbm do |x|
12
- x.report('select') do
13
- query.call
14
- end
15
- x.report('rb_thread_select') do
16
- thread = Thread.new{ sleep(10) }
17
- query.call
18
- thread.kill
19
- end
20
- end
data/spec/spec.opts DELETED
@@ -1,2 +0,0 @@
1
- --format specdoc
2
- --colour