Permalink
Browse files

Merge branch 'master' into issue-233a

Conflicts:
	ext/mysql2/client.c
	ext/mysql2/client.h
  • Loading branch information...
2 parents eebce9b + 84ca9ff commit 39947e1e8a364b5f6b33478f482ca04ce510dc49 @sodabrew committed Aug 1, 2012
Showing with 104 additions and 23 deletions.
  1. +21 −12 ext/mysql2/client.c
  2. +1 −0 ext/mysql2/client.h
  3. +7 −3 ext/mysql2/result.c
  4. +14 −1 lib/mysql2.rb
  5. +13 −5 lib/mysql2/client.rb
  6. +9 −1 lib/mysql2/em.rb
  7. +13 −0 spec/mysql2/client_spec.rb
  8. +26 −1 spec/mysql2/result_spec.rb
View
33 ext/mysql2/client.c
@@ -30,7 +30,7 @@ static ID intern_merge, intern_error_number_eql, intern_sql_state_eql;
}
#define MARK_CONN_INACTIVE(conn) \
- wrapper->active = 0
+ wrapper->active_thread = Qnil;
#define GET_CLIENT(self) \
mysql_client_wrapper *wrapper; \
@@ -98,6 +98,7 @@ static void rb_mysql_client_mark(void * wrapper) {
mysql_client_wrapper * w = wrapper;
if (w) {
rb_gc_mark(w->encoding);
+ rb_gc_mark(w->active_thread);
}
}
@@ -150,8 +151,8 @@ static VALUE nogvl_close(void *ptr) {
#endif
wrapper = ptr;
if (wrapper->connected) {
+ wrapper->active_thread = Qnil;
wrapper->connected = 0;
- wrapper->active = 0;
/*
* we'll send a QUIT message to the server, but that message is more of a
* formality than a hard requirement since the socket is getting shutdown
@@ -187,8 +188,8 @@ static VALUE allocate(VALUE klass) {
mysql_client_wrapper * wrapper;
obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper);
wrapper->encoding = Qnil;
+ wrapper->active_thread = Qnil;
wrapper->reconnect_enabled = 0;
- wrapper->active = 0; // active: means that a query is active
wrapper->connected = 0; // means that a database connection is open
wrapper->initialized = 0; // means that that the wrapper is initialized
wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL));
@@ -236,7 +237,7 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
rv = rb_thread_blocking_region(nogvl_connect, &args, RUBY_UBF_IO, 0);
if (rv == Qfalse) {
- while (rv == Qfalse && errno == EINTR) {
+ while (rv == Qfalse && errno == EINTR && !mysql_errno(wrapper->client)) {
errno = 0;
rv = rb_thread_blocking_region(nogvl_connect, &args, RUBY_UBF_IO, 0);
}
@@ -314,7 +315,8 @@ static VALUE nogvl_do_result(void *ptr, char use_result) {
// once our result is stored off, this connection is
// ready for another command to be issued
- wrapper->active = 0;
+ wrapper->active_thread = Qnil;
+
return (VALUE)result;
}
@@ -341,7 +343,7 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
GET_CLIENT(self);
// if we're not waiting on a result, do nothing
- if (!wrapper->active)
+ if (NIL_P(wrapper->active_thread))
return Qnil;
REQUIRE_CONNECTED(wrapper);
@@ -387,8 +389,8 @@ struct async_query_args {
static VALUE disconnect_and_raise(VALUE self, VALUE error) {
GET_CLIENT(self);
+ wrapper->active_thread = Qnil;
wrapper->connected = 0;
- wrapper->active = 0;
// manually close the socket for read/write
// this feels dirty, but is there another way?
@@ -452,14 +454,14 @@ static VALUE finish_and_mark_inactive(void *args) {
GET_CLIENT(self);
- if (wrapper->active) {
+ if (!NIL_P(wrapper->active_thread)) {
// if we got here, the result hasn't been read off the wire yet
// so lets do that and then throw it away because we have no way
// of getting it back up to the caller from here
result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper, RUBY_UBF_IO, 0);
mysql_free_result(result);
- wrapper->active = 0;
+ wrapper->active_thread = Qnil;
}
return Qnil;
@@ -479,6 +481,7 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
struct nogvl_send_query_args args;
int async = 0;
VALUE opts, defaults;
+ VALUE thread_current = rb_thread_current();
#ifdef HAVE_RUBY_ENCODING_H
rb_encoding *conn_enc;
#endif
@@ -510,11 +513,17 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
args.sql_len = RSTRING_LEN(args.sql);
// see if this connection is still waiting on a result from a previous query
- if (wrapper->active == 0) {
+ if (NIL_P(wrapper->active_thread)) {
// mark this connection active
- wrapper->active = 1;
- } else {
+ wrapper->active_thread = thread_current;
+ } else if (wrapper->active_thread == thread_current) {
rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result");
+ } else {
+ VALUE inspect = rb_inspect(wrapper->active_thread);
+ const char *thr = StringValueCStr(inspect);
+
+ rb_raise(cMysql2Error, "This connection is in use by: %s", thr);
+ RB_GC_GUARD(inspect);
}
args.wrapper = wrapper;
View
1 ext/mysql2/client.h
@@ -33,6 +33,7 @@ void init_mysql2_client();
typedef struct {
VALUE encoding;
+ VALUE active_thread; /* rb_thread_current() or Qnil */
int reconnect_enabled;
int active;
int connected;
View
10 ext/mysql2/result.c
@@ -223,7 +223,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
break;
case MYSQL_TYPE_TINY: // TINYINT field
if (castBool && fields[i].length == 1) {
- val = *row[i] == '1' ? Qtrue : Qfalse;
+ val = *row[i] != '0' ? Qtrue : Qfalse;
break;
}
case MYSQL_TYPE_SHORT: // SMALLINT field
@@ -478,7 +478,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
do {
row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast, fields);
- if (block != Qnil) {
+ if (block != Qnil && row != Qnil) {
rb_yield(row);
wrapper->lastRowProcessed++;
}
@@ -540,7 +540,11 @@ static VALUE rb_mysql_result_count(VALUE self) {
GetMysql2Result(self, wrapper);
if(wrapper->resultFreed) {
- return LONG2NUM(RARRAY_LEN(wrapper->rows));
+ if (wrapper->streamingComplete){
+ return LONG2NUM(wrapper->numberOfRows);
+ } else {
+ return LONG2NUM(RARRAY_LEN(wrapper->rows));
+ }
} else {
return INT2FIX(mysql_num_rows(wrapper->result));
}
View
15 lib/mysql2.rb
@@ -19,6 +19,19 @@ module Mysql2
warn "============= WARNING FROM mysql2 ============="
warn "This version of mysql2 (#{Mysql2::VERSION}) doesn't ship with the ActiveRecord adapter."
warn "In Rails version 3.1.0 and up, the mysql2 ActiveRecord adapter is included with rails."
- warn "If you want to use the mysql2 gem with Rails <= 3.0.x, please use the latest mysql2 in the 0.2.x series".
+ warn "If you want to use the mysql2 gem with Rails <= 3.0.x, please use the latest mysql2 in the 0.2.x series."
warn "============= END WARNING FROM mysql2 ============="
end
+
+# For holding utility methods
+module Mysql2::Util
+
+ #
+ # Rekey a string-keyed hash with equivalent symbols.
+ #
+ def self.key_hash_as_symbols(hash)
+ return nil unless hash
+ Hash[hash.map { |k,v| [k.to_sym, v] }]
+ end
+
+end
View
18 lib/mysql2/client.rb
@@ -14,6 +14,7 @@ class Client
}
def initialize(opts = {})
+ opts = Mysql2::Util.key_hash_as_symbols( opts )
@query_options = @@default_query_options.dup
@query_options.merge! opts
@@ -34,13 +35,20 @@ def initialize(opts = {})
end
ssl_set(*opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslcipher))
+
+ if [:user,:pass,:hostname,:dbname,:db,:sock].any?{|k| @query_options.has_key?(k) }
+ warn "============= WARNING FROM mysql2 ============="
+ warn "The options :user, :pass, :hostname, :dbname, :db, and :sock will be deprecated at some point in the future."
+ warn "Instead, please use :username, :password, :host, 'localhost', :port, :database, :socket, :flags for the options."
+ warn "============= END WARNING FROM mysql2 ========="
+ end
- user = opts[:username]
- pass = opts[:password]
- host = opts[:host] || 'localhost'
+ user = opts[:username] || opts[:user]
+ pass = opts[:password] || opts[:pass]
+ host = opts[:host] || opts[:hostname] || 'localhost'
port = opts[:port] || 3306
- database = opts[:database]
- socket = opts[:socket]
+ database = opts[:database] || opts[:dbname] || opts[:db]
+ socket = opts[:socket] || opts[:sock]
flags = opts[:flags] ? opts[:flags] | @query_options[:connect_flags] : @query_options[:connect_flags]
connect user, pass, host, port, database, socket, flags
View
10 lib/mysql2/em.rb
@@ -24,11 +24,19 @@ def notify_readable
end
end
+ def close(*args)
+ if @watch
+ @watch.detach
+ end
+ super(*args)
+ end
+
def query(sql, opts={})
if ::EM.reactor_running?
super(sql, opts.merge(:async => true))
deferable = ::EM::DefaultDeferrable.new
- ::EM.watch(self.socket, Watcher, self, deferable).notify_readable = true
+ @watch = ::EM.watch(self.socket, Watcher, self, deferable)
+ @watch.notify_readable = true
deferable
else
super(sql, opts)
View
13 spec/mysql2/client_spec.rb
@@ -147,6 +147,19 @@ def connect *args
}.should raise_error(Mysql2::Error)
end
+ it "should describe the thread holding the active query" do
+ thr = Thread.new { @client.query("SELECT 1", :async => true) }
+
+ thr.join
+ begin
+ @client.query("SELECT 1")
+ rescue Mysql2::Error => e
+ message = e.message
+ end
+ re = Regexp.escape(thr.inspect)
+ message.should match(Regexp.new(re))
+ end
+
it "should timeout if we wait longer than :read_timeout" do
client = Mysql2::Client.new(:read_timeout => 1)
lambda {
View
27 spec/mysql2/result_spec.rb
@@ -18,6 +18,27 @@
result.count.should eql(1)
end
+ it "should set the actual count of rows after streaming" do
+ @client.query "USE test"
+ result = @client.query("SELECT * FROM mysql2_test", :stream => true, :cache_rows => false)
+ result.count.should eql(0)
+ result.each {|r| }
+ result.count.should eql(1)
+ end
+
+ it "should not yield nil at the end of streaming" do
+ result = @client.query('SELECT * FROM mysql2_test', :stream => true)
+ result.each { |r| r.should_not be_nil}
+ end
+
+ it "#count should be zero for rows after streaming when there were no results " do
+ @client.query "USE test"
+ result = @client.query("SELECT * FROM mysql2_test WHERE null_test IS NOT NULL", :stream => true, :cache_rows => false)
+ result.count.should eql(0)
+ result.each {|r| }
+ result.count.should eql(0)
+ end
+
it "should have included Enumerable" do
Mysql2::Result.ancestors.include?(Enumerable).should be_true
end
@@ -154,13 +175,17 @@
id1 = @client.last_id
@client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES (0)'
id2 = @client.last_id
+ @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES (-1)'
+ id3 = @client.last_id
result1 = @client.query 'SELECT bool_cast_test FROM mysql2_test WHERE bool_cast_test = 1 LIMIT 1', :cast_booleans => true
result2 = @client.query 'SELECT bool_cast_test FROM mysql2_test WHERE bool_cast_test = 0 LIMIT 1', :cast_booleans => true
+ result3 = @client.query 'SELECT bool_cast_test FROM mysql2_test WHERE bool_cast_test = -1 LIMIT 1', :cast_booleans => true
result1.first['bool_cast_test'].should be_true
result2.first['bool_cast_test'].should be_false
+ result3.first['bool_cast_test'].should be_true
- @client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2})"
+ @client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2},#{id3})"
end
it "should return Fixnum for a SMALLINT value" do

0 comments on commit 39947e1

Please sign in to comment.