diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 94148e4..f294735 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -2,9 +2,10 @@ #include VALUE cMysql2Client; -extern VALUE mMysql2, cMysql2Error, intern_encoding_from_charset; -extern ID sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as, sym_array; -extern ID intern_merge; +extern VALUE mMysql2, cMysql2Error; +static VALUE intern_encoding_from_charset; +static ID sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as, sym_array; +static ID intern_merge; #define REQUIRE_OPEN_DB(_ctxt) \ if(!_ctxt->net.vio) { \ @@ -12,6 +13,9 @@ extern ID intern_merge; return Qnil; \ } +#define MARK_CONN_INACTIVE(conn) \ + rb_iv_set(conn, "@active", Qfalse); + /* * used to pass all arguments to mysql_real_connect while inside * rb_thread_blocking_region @@ -147,8 +151,7 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po args.mysql = client; args.client_flag = 0; - if (rb_thread_blocking_region(nogvl_connect, &args, RUBY_UBF_IO, 0) == Qfalse) - { + if (rb_thread_blocking_region(nogvl_connect, &args, RUBY_UBF_IO, 0) == Qfalse) { // unable to connect return rb_raise_mysql2_error(client); } @@ -214,10 +217,16 @@ static VALUE rb_mysql_client_async_result(VALUE self) { REQUIRE_OPEN_DB(client); if (rb_thread_blocking_region(nogvl_read_query_result, client, RUBY_UBF_IO, 0) == Qfalse) { + // an error occurred, mark this connection inactive + MARK_CONN_INACTIVE(self); return rb_raise_mysql2_error(client); } result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, client, RUBY_UBF_IO, 0); + + // we have our result, mark this connection inactive + MARK_CONN_INACTIVE(self); + if (result == NULL) { if (mysql_field_count(client) != 0) { rb_raise_mysql2_error(client); @@ -240,13 +249,22 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) { fd_set fdset; int fd, retval; int async = 0; - VALUE opts, defaults; + VALUE opts, defaults, active; MYSQL *client; Data_Get_Struct(self, MYSQL, client); REQUIRE_OPEN_DB(client); args.mysql = client; + active = rb_iv_get(self, "@active"); + // see if this connection is still waiting on a result from a previous query + if (NIL_P(active) || active == Qfalse) { + // mark this connection active + rb_iv_set(self, "@active", Qtrue); + } else { + rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result"); + } + defaults = rb_iv_get(self, "@query_options"); if (rb_scan_args(argc, argv, "11", &args.sql, &opts) == 2) { opts = rb_funcall(defaults, intern_merge, 1, opts); @@ -266,6 +284,8 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) { #endif if (rb_thread_blocking_region(nogvl_send_query, &args, RUBY_UBF_IO, 0) == Qfalse) { + // an error occurred, we're not active anymore + MARK_CONN_INACTIVE(self); return rb_raise_mysql2_error(client); } @@ -290,9 +310,6 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) { VALUE result = rb_mysql_client_async_result(self); - // pass-through query options for result construction later - rb_iv_set(result, "@query_options", rb_obj_dup(opts)); - return result; } else { return Qnil; @@ -528,4 +545,15 @@ void init_mysql2_client() { rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5); rb_define_private_method(cMysql2Client, "init_connection", init_connection, 0); rb_define_private_method(cMysql2Client, "connect", rb_connect, 6); + + intern_encoding_from_charset = rb_intern("encoding_from_charset"); + + sym_id = ID2SYM(rb_intern("id")); + sym_version = ID2SYM(rb_intern("version")); + sym_async = ID2SYM(rb_intern("async")); + sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys")); + sym_as = ID2SYM(rb_intern("as")); + sym_array = ID2SYM(rb_intern("array")); + + intern_merge = rb_intern("merge"); } \ No newline at end of file diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index 08f3803..bc48e6b 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -57,7 +57,7 @@ end asplode h unless have_header h end -$CFLAGS << ' -Wall -Wextra -funroll-loops' -# $CFLAGS << ' -O0 -ggdb3' +$CFLAGS << ' -Wall -funroll-loops' +# $CFLAGS << ' -O0 -ggdb3 -Wextra' create_makefile('mysql2/mysql2') diff --git a/ext/mysql2/mysql2_ext.c b/ext/mysql2/mysql2_ext.c index 02bd61d..c6be28c 100644 --- a/ext/mysql2/mysql2_ext.c +++ b/ext/mysql2/mysql2_ext.c @@ -1,9 +1,6 @@ #include -VALUE mMysql2, cMysql2Error, intern_encoding_from_charset; -ID sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as, - sym_array, sym_timezone, sym_utc, sym_local; -ID intern_merge; +VALUE mMysql2, cMysql2Error; /* call-seq: client.create_statement # => Mysql2::Statement * @@ -25,20 +22,6 @@ void Init_mysql2() { mMysql2 = rb_define_module("Mysql2"); cMysql2Error = rb_const_get(mMysql2, rb_intern("Error")); - intern_merge = rb_intern("merge"); - - sym_timezone = ID2SYM(rb_intern("timezone")); - sym_utc = ID2SYM(rb_intern("utc")); - sym_local = ID2SYM(rb_intern("local")); - sym_array = ID2SYM(rb_intern("array")); - sym_as = ID2SYM(rb_intern("as")); - sym_id = ID2SYM(rb_intern("id")); - sym_version = ID2SYM(rb_intern("version")); - sym_async = ID2SYM(rb_intern("async")); - sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys")); - - intern_encoding_from_charset = rb_intern("encoding_from_charset"); - init_mysql2_client(); init_mysql2_result(); init_mysql2_statement(); diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 02f70f0..6baa678 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -4,13 +4,15 @@ rb_encoding *binaryEncoding; #endif -ID intern_new, intern_utc, intern_local, intern_encoding_from_charset_code; - VALUE cMysql2Result; VALUE cBigDecimal, cDate, cDateTime; -extern VALUE mMysql2, cMysql2Client, cMysql2Error, intern_encoding_from_charset; -extern ID sym_symbolize_keys, sym_as, sym_array, sym_timezone, sym_local, sym_utc; -extern ID intern_merge; +extern VALUE mMysql2, cMysql2Client, cMysql2Error; +static VALUE intern_encoding_from_charset; +static ID intern_new, intern_utc, intern_local, intern_encoding_from_charset_code, + intern_localtime; +static ID sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, sym_application_timezone, + sym_local, sym_utc, sym_cast_booleans; +static ID intern_merge; static void rb_mysql_result_mark(void * wrapper) { mysql2_result_wrapper * w = wrapper; @@ -86,7 +88,7 @@ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int return rb_field; } -static VALUE rb_mysql_result_fetch_row(VALUE self, ID timezone, int symbolizeKeys, int asArray) { +static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezone, int symbolizeKeys, int asArray, int castBool) { VALUE rowVal; mysql2_result_wrapper * wrapper; MYSQL_ROW row; @@ -131,6 +133,10 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID timezone, int symbolizeKey val = rb_str_new(row[i], fieldLengths[i]); break; case MYSQL_TYPE_TINY: // TINYINT field + if (castBool && fields[i].length == 1) { + val = *row[i] == '1' ? Qtrue : Qfalse; + break; + } case MYSQL_TYPE_SHORT: // SMALLINT field case MYSQL_TYPE_LONG: // INTEGER field case MYSQL_TYPE_INT24: // MEDIUMINT field @@ -149,7 +155,14 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID timezone, int symbolizeKey case MYSQL_TYPE_TIME: { // TIME field int hour, min, sec, tokens; tokens = sscanf(row[i], "%2d:%2d:%2d", &hour, &min, &sec); - val = rb_funcall(rb_cTime, timezone, 6, INT2NUM(2000), INT2NUM(1), INT2NUM(1), INT2NUM(hour), INT2NUM(min), INT2NUM(sec)); + val = rb_funcall(rb_cTime, db_timezone, 6, INT2NUM(2000), INT2NUM(1), INT2NUM(1), INT2NUM(hour), INT2NUM(min), INT2NUM(sec)); + if (!NIL_P(app_timezone)) { + if (app_timezone == intern_local) { + val = rb_funcall(val, intern_localtime, 0); + } else { // utc + val = rb_funcall(val, intern_utc, 0); + } + } break; } case MYSQL_TYPE_TIMESTAMP: // TIMESTAMP field @@ -163,7 +176,14 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID timezone, int symbolizeKey rb_raise(cMysql2Error, "Invalid date: %s", row[i]); val = Qnil; } else { - val = rb_funcall(rb_cTime, timezone, 6, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec)); + val = rb_funcall(rb_cTime, db_timezone, 6, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec)); + if (!NIL_P(app_timezone)) { + if (app_timezone == intern_local) { + val = rb_funcall(val, intern_localtime, 0); + } else { // utc + val = rb_funcall(val, intern_utc, 0); + } + } } } break; @@ -237,9 +257,16 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID timezone, int symbolizeKey static VALUE rb_mysql_result_fetch_fields(VALUE self) { mysql2_result_wrapper * wrapper; unsigned int i = 0; + short int symbolizeKeys = 0; + VALUE defaults; GetMysql2Result(self, wrapper); + defaults = rb_iv_get(self, "@query_options"); + if (rb_hash_aref(defaults, sym_symbolize_keys) == Qtrue) { + symbolizeKeys = 1; + } + if (wrapper->fields == Qnil) { wrapper->numberOfFields = mysql_num_fields(wrapper->result); wrapper->fields = rb_ary_new2(wrapper->numberOfFields); @@ -247,7 +274,7 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) { if (RARRAY_LEN(wrapper->fields) != wrapper->numberOfFields) { for (i=0; inumberOfFields; i++) { - rb_mysql_result_fetch_field(self, i, 0); + rb_mysql_result_fetch_field(self, i, symbolizeKeys); } } @@ -255,11 +282,11 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) { } static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { - VALUE defaults, opts, block, timezoneVal; - ID timezone; + VALUE defaults, opts, block; + ID db_timezone, app_timezone, dbTz, appTz; mysql2_result_wrapper * wrapper; unsigned long i; - int symbolizeKeys = 0, asArray = 0; + int symbolizeKeys = 0, asArray = 0, castBool = 0; GetMysql2Result(self, wrapper); @@ -278,14 +305,29 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { asArray = 1; } - timezoneVal = rb_hash_aref(opts, sym_timezone); - if (timezoneVal == sym_local) { - timezone = intern_local; - } else if (timezoneVal == sym_utc) { - timezone = intern_utc; + if (rb_hash_aref(opts, sym_cast_booleans) == Qtrue) { + castBool = 1; + } + + dbTz = rb_hash_aref(opts, sym_database_timezone); + if (dbTz == sym_local) { + db_timezone = intern_local; + } else if (dbTz == sym_utc) { + db_timezone = intern_utc; } else { - rb_warn(":timezone config option must be :utc or :local - defaulting to :local"); - timezone = intern_local; + if (!NIL_P(dbTz)) { + rb_warn(":database_timezone option must be :utc or :local - defaulting to :local"); + } + db_timezone = intern_local; + } + + appTz = rb_hash_aref(opts, sym_application_timezone); + if (appTz == sym_local) { + app_timezone = intern_local; + } else if (appTz == sym_utc) { + app_timezone = intern_utc; + } else { + app_timezone = intern_local; } if (wrapper->lastRowProcessed == 0) { @@ -311,7 +353,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { if (i < rowsProcessed) { row = rb_ary_entry(wrapper->rows, i); } else { - row = rb_mysql_result_fetch_row(self, timezone, symbolizeKeys, asArray); + row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool); rb_ary_store(wrapper->rows, i, row); wrapper->lastRowProcessed++; } @@ -360,11 +402,24 @@ void init_mysql2_result() { rb_define_method(cMysql2Result, "each", rb_mysql_result_each, -1); rb_define_method(cMysql2Result, "fields", rb_mysql_result_fetch_fields, 0); - intern_new = rb_intern("new"); - intern_utc = rb_intern("utc"); - intern_local = rb_intern("local"); + intern_encoding_from_charset = rb_intern("encoding_from_charset"); intern_encoding_from_charset_code = rb_intern("encoding_from_charset_code"); + intern_new = rb_intern("new"); + intern_utc = rb_intern("utc"); + intern_local = rb_intern("local"); + intern_merge = rb_intern("merge"); + intern_localtime = rb_intern("localtime"); + + sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys")); + sym_as = ID2SYM(rb_intern("as")); + sym_array = ID2SYM(rb_intern("array")); + sym_local = ID2SYM(rb_intern("local")); + sym_utc = ID2SYM(rb_intern("utc")); + sym_cast_booleans = ID2SYM(rb_intern("cast_booleans")); + sym_database_timezone = ID2SYM(rb_intern("database_timezone")); + sym_application_timezone = ID2SYM(rb_intern("application_timezone")); + #ifdef HAVE_RUBY_ENCODING_H binaryEncoding = rb_enc_find("binary"); #endif diff --git a/lib/active_record/connection_adapters/mysql2_adapter.rb b/lib/active_record/connection_adapters/mysql2_adapter.rb index d4345a4..5687597 100644 --- a/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -302,7 +302,7 @@ module ActiveRecord def execute(sql, name = nil) # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been # made since we established the connection - @connection.query_options[:timezone] = ActiveRecord::Base.default_timezone + @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone if name == :skip_logging @connection.query(sql) else diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index f2db76e..5206e54 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -2,14 +2,17 @@ module Mysql2 class Client attr_reader :query_options @@default_query_options = { - :symbolize_keys => false, - :async => false, :as => :hash, - :timezone => :local + :async => false, + :cast_booleans => false, + :symbolize_keys => false, + :database_timezone => :local, # timezone Mysql2 will assume datetime objects are stored in + :application_timezone => nil # timezone Mysql2 will convert to before handing the object back to the caller } def initialize(opts = {}) @query_options = @@default_query_options.dup + @active = false init_connection diff --git a/lib/sequel/adapters/mysql2.rb b/lib/sequel/adapters/mysql2.rb deleted file mode 100644 index 26b74bf..0000000 --- a/lib/sequel/adapters/mysql2.rb +++ /dev/null @@ -1,237 +0,0 @@ -require 'mysql2' unless defined? Mysql2 - -Sequel.require %w'shared/mysql utils/stored_procedures', 'adapters' - -module Sequel - # Module for holding all MySQL-related classes and modules for Sequel. - module Mysql2 - # Mapping of type numbers to conversion procs - MYSQL_TYPES = {} - - MYSQL2_LITERAL_PROC = lambda{|v| v} - - # Use only a single proc for each type to save on memory - MYSQL_TYPE_PROCS = { - [0, 246] => MYSQL2_LITERAL_PROC, # decimal - [1] => lambda{|v| convert_tinyint_to_bool ? v != 0 : v}, # tinyint - [2, 3, 8, 9, 13, 247, 248] => MYSQL2_LITERAL_PROC, # integer - [4, 5] => MYSQL2_LITERAL_PROC, # float - [10, 14] => MYSQL2_LITERAL_PROC, # date - [7, 12] => MYSQL2_LITERAL_PROC, # datetime - [11] => MYSQL2_LITERAL_PROC, # time - [249, 250, 251, 252] => lambda{|v| Sequel::SQL::Blob.new(v)} # blob - } - MYSQL_TYPE_PROCS.each do |k,v| - k.each{|n| MYSQL_TYPES[n] = v} - end - - @convert_invalid_date_time = false - @convert_tinyint_to_bool = true - - class << self - # By default, Sequel raises an exception if in invalid date or time is used. - # However, if this is set to nil or :nil, the adapter treats dates - # like 0000-00-00 and times like 838:00:00 as nil values. If set to :string, - # it returns the strings as is. - attr_accessor :convert_invalid_date_time - - # Sequel converts the column type tinyint(1) to a boolean by default when - # using the native MySQL adapter. You can turn off the conversion by setting - # this to false. - attr_accessor :convert_tinyint_to_bool - end - - # Database class for MySQL databases used with Sequel. - class Database < Sequel::Database - include Sequel::MySQL::DatabaseMethods - - # Mysql::Error messages that indicate the current connection should be disconnected - MYSQL_DATABASE_DISCONNECT_ERRORS = /\A(Commands out of sync; you can't run this command now|Can't connect to local MySQL server through socket|MySQL server has gone away)/ - - set_adapter_scheme :mysql2 - - # Support stored procedures on MySQL - def call_sproc(name, opts={}, &block) - args = opts[:args] || [] - execute("CALL #{name}#{args.empty? ? '()' : literal(args)}", opts.merge(:sproc=>false), &block) - end - - # Connect to the database. In addition to the usual database options, - # the following options have effect: - # - # * :auto_is_null - Set to true to use MySQL default behavior of having - # a filter for an autoincrement column equals NULL to return the last - # inserted row. - # * :charset - Same as :encoding (:encoding takes precendence) - # * :compress - Set to false to not compress results from the server - # * :config_default_group - The default group to read from the in - # the MySQL config file. - # * :config_local_infile - If provided, sets the Mysql::OPT_LOCAL_INFILE - # option on the connection with the given value. - # * :encoding - Set all the related character sets for this - # connection (connection, client, database, server, and results). - # * :socket - Use a unix socket file instead of connecting via TCP/IP. - # * :timeout - Set the timeout in seconds before the server will - # disconnect this connection. - def connect(server) - opts = server_opts(server) - conn = ::Mysql2::Client.new({ - :host => opts[:host] || 'localhost', - :username => opts[:user], - :password => opts[:password], - :database => opts[:database], - :port => opts[:port], - :socket => opts[:socket] - }) - - # increase timeout so mysql server doesn't disconnect us - conn.query("set @@wait_timeout = #{opts[:timeout] || 2592000}") - - # By default, MySQL 'where id is null' selects the last inserted id - conn.query("set SQL_AUTO_IS_NULL=0") unless opts[:auto_is_null] - - conn - end - - # Returns instance of Sequel::MySQL::Dataset with the given options. - def dataset(opts = nil) - Mysql2::Dataset.new(self, opts) - end - - # Executes the given SQL using an available connection, yielding the - # connection if the block is given. - def execute(sql, opts={}, &block) - if opts[:sproc] - call_sproc(sql, opts, &block) - else - synchronize(opts[:server]){|conn| _execute(conn, sql, opts, &block)} - end - end - - # Return the version of the MySQL server two which we are connecting. - def server_version(server=nil) - @server_version ||= (synchronize(server){|conn| conn.info[:id]}) - end - - private - - # Execute the given SQL on the given connection. If the :type - # option is :select, yield the result of the query, otherwise - # yield the connection if a block is given. - def _execute(conn, sql, opts) - begin - r = log_yield(sql){conn.query(sql)} - if opts[:type] == :select - yield r if r - elsif block_given? - yield conn - end - rescue ::Mysql2::Error => e - raise_error(e, :disconnect=>MYSQL_DATABASE_DISCONNECT_ERRORS.match(e.message)) - end - end - - # MySQL connections use the query method to execute SQL without a result - def connection_execute_method - :query - end - - # The MySQL adapter main error class is Mysql::Error - def database_error_classes - [::Mysql2::Error] - end - - # The database name when using the native adapter is always stored in - # the :database option. - def database_name - @opts[:database] - end - - # Closes given database connection. - def disconnect_connection(c) - c.close - end - - # Convert tinyint(1) type to boolean if convert_tinyint_to_bool is true - def schema_column_type(db_type) - Sequel::Mysql2.convert_tinyint_to_bool && db_type == 'tinyint(1)' ? :boolean : super - end - end - - # Dataset class for MySQL datasets accessed via the native driver. - class Dataset < Sequel::Dataset - include Sequel::MySQL::DatasetMethods - include StoredProcedures - - # Methods for MySQL stored procedures using the native driver. - module StoredProcedureMethods - include Sequel::Dataset::StoredProcedureMethods - - private - - # Execute the database stored procedure with the stored arguments. - def execute(sql, opts={}, &block) - super(@sproc_name, {:args=>@sproc_args, :sproc=>true}.merge(opts), &block) - end - - # Same as execute, explicit due to intricacies of alias and super. - def execute_dui(sql, opts={}, &block) - super(@sproc_name, {:args=>@sproc_args, :sproc=>true}.merge(opts), &block) - end - end - - # Delete rows matching this dataset - def delete - execute_dui(delete_sql){|c| return c.affected_rows} - end - - # Yield all rows matching this dataset. If the dataset is set to - # split multiple statements, yield arrays of hashes one per statement - # instead of yielding results for all statements as hashes. - def fetch_rows(sql, &block) - execute(sql) do |r| - r.each(:symbolize_keys => true, &block) - end - self - end - - # Don't allow graphing a dataset that splits multiple statements - def graph(*) - raise(Error, "Can't graph a dataset that splits multiple result sets") if opts[:split_multiple_result_sets] - super - end - - # Insert a new value into this dataset - def insert(*values) - execute_dui(insert_sql(*values)){|c| return c.last_id} - end - - # Replace (update or insert) the matching row. - def replace(*args) - execute_dui(replace_sql(*args)){|c| return c.last_id} - end - - # Update the matching rows. - def update(values={}) - execute_dui(update_sql(values)){|c| return c.affected_rows} - end - - private - - # Set the :type option to :select if it hasn't been set. - def execute(sql, opts={}, &block) - super(sql, {:type=>:select}.merge(opts), &block) - end - - # Set the :type option to :dui if it hasn't been set. - def execute_dui(sql, opts={}, &block) - super(sql, {:type=>:dui}.merge(opts), &block) - end - - # Handle correct quoting of strings using ::Mysql2#escape. - def literal_string(v) - db.synchronize{|c| "'#{c.escape(v)}'"} - end - end - end -end diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 262dc6f..a6ff3c7 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -71,6 +71,13 @@ describe Mysql2::Client do it "should be able to return results with symbolized keys" do @client.query("SELECT 1", :symbolize_keys => true).first.keys[0].class.should eql(Symbol) end + + it "should not allow another query to be sent without fetching a result first" do + @client.query("SELECT 1", :async => true) + lambda { + @client.query("SELECT 1") + }.should raise_error(Mysql2::Error) + end end it "should respond to #escape" do diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 8585656..fd230cb 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -71,65 +71,9 @@ describe Mysql2::Result do context "row data type mapping" do before(:each) do @client.query "USE test" - @client.query %[ - CREATE TABLE IF NOT EXISTS mysql2_test ( - id MEDIUMINT NOT NULL AUTO_INCREMENT, - null_test VARCHAR(10), - bit_test BIT(64), - tiny_int_test TINYINT, - small_int_test SMALLINT, - medium_int_test MEDIUMINT, - int_test INT, - big_int_test BIGINT, - float_test FLOAT(10,3), - double_test DOUBLE(10,3), - decimal_test DECIMAL(10,3), - date_test DATE, - date_time_test DATETIME, - timestamp_test TIMESTAMP, - time_test TIME, - year_test YEAR(4), - char_test CHAR(10), - varchar_test VARCHAR(10), - binary_test BINARY(10), - varbinary_test VARBINARY(10), - tiny_blob_test TINYBLOB, - tiny_text_test TINYTEXT, - blob_test BLOB, - text_test TEXT, - medium_blob_test MEDIUMBLOB, - medium_text_test MEDIUMTEXT, - long_blob_test LONGBLOB, - long_text_test LONGTEXT, - enum_test ENUM('val1', 'val2'), - set_test SET('val1', 'val2'), - PRIMARY KEY (id) - ) - ] - @client.query %[ - INSERT INTO mysql2_test ( - null_test, bit_test, tiny_int_test, small_int_test, medium_int_test, int_test, big_int_test, - float_test, double_test, decimal_test, date_test, date_time_test, timestamp_test, time_test, - year_test, char_test, varchar_test, binary_test, varbinary_test, tiny_blob_test, - tiny_text_test, blob_test, text_test, medium_blob_test, medium_text_test, - long_blob_test, long_text_test, enum_test, set_test - ) - - VALUES ( - NULL, b'101', 1, 10, 10, 10, 10, - 10.3, 10.3, 10.3, '2010-4-4', '2010-4-4 11:44:00', '2010-4-4 11:44:00', '11:44:00', - 2009, "test", "test", "test", "test", "test", - "test", "test", "test", "test", "test", - "test", "test", 'val1', 'val1,val2' - ) - ] @test_result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first end - after(:all) do - @client.query("DELETE FROM mysql2_test WHERE id=#{@test_result['id']}") - end - it "should return nil for a NULL value" do @test_result['null_test'].class.should eql(NilClass) @test_result['null_test'].should eql(nil) @@ -145,6 +89,20 @@ describe Mysql2::Result do @test_result['tiny_int_test'].should eql(1) end + it "should return TrueClass or FalseClass for a TINYINT value if :cast_booleans is enabled" do + @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES (1)' + id1 = @client.last_id + @client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES (0)' + id2 = @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 + result1.first['bool_cast_test'].should be_true + result2.first['bool_cast_test'].should be_false + + @client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2})" + end + it "should return Fixnum for a SMALLINT value" do [Fixnum, Bignum].should include(@test_result['small_int_test'].class) @test_result['small_int_test'].should eql(10) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4d942a6..b50e6cc 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -3,3 +3,62 @@ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/..') $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') require 'mysql2' + +Spec::Runner.configure do |config| + config.before(:all) do + client = Mysql2::Client.new :database => 'test' + client.query %[ + CREATE TABLE IF NOT EXISTS mysql2_test ( + id MEDIUMINT NOT NULL AUTO_INCREMENT, + null_test VARCHAR(10), + bit_test BIT(64), + tiny_int_test TINYINT, + bool_cast_test TINYINT(1), + small_int_test SMALLINT, + medium_int_test MEDIUMINT, + int_test INT, + big_int_test BIGINT, + float_test FLOAT(10,3), + double_test DOUBLE(10,3), + decimal_test DECIMAL(10,3), + date_test DATE, + date_time_test DATETIME, + timestamp_test TIMESTAMP, + time_test TIME, + year_test YEAR(4), + char_test CHAR(10), + varchar_test VARCHAR(10), + binary_test BINARY(10), + varbinary_test VARBINARY(10), + tiny_blob_test TINYBLOB, + tiny_text_test TINYTEXT, + blob_test BLOB, + text_test TEXT, + medium_blob_test MEDIUMBLOB, + medium_text_test MEDIUMTEXT, + long_blob_test LONGBLOB, + long_text_test LONGTEXT, + enum_test ENUM('val1', 'val2'), + set_test SET('val1', 'val2'), + PRIMARY KEY (id) + ) + ] + client.query %[ + INSERT INTO mysql2_test ( + null_test, bit_test, tiny_int_test, bool_cast_test, small_int_test, medium_int_test, int_test, big_int_test, + float_test, double_test, decimal_test, date_test, date_time_test, timestamp_test, time_test, + year_test, char_test, varchar_test, binary_test, varbinary_test, tiny_blob_test, + tiny_text_test, blob_test, text_test, medium_blob_test, medium_text_test, + long_blob_test, long_text_test, enum_test, set_test + ) + + VALUES ( + NULL, b'101', 1, 1, 10, 10, 10, 10, + 10.3, 10.3, 10.3, '2010-4-4', '2010-4-4 11:44:00', '2010-4-4 11:44:00', '11:44:00', + 2009, "test", "test", "test", "test", "test", + "test", "test", "test", "test", "test", + "test", "test", 'val1', 'val1,val2' + ) + ] + end +end \ No newline at end of file