Merge branch 'master' into stmt

This commit is contained in:
Brian Lopez 2010-08-02 10:04:07 -07:00
commit ee4fd98611
13 changed files with 261 additions and 85 deletions

View File

@ -58,6 +58,52 @@ How about with symbolized keys?
# do something with row, it's ready to rock # do something with row, it's ready to rock
end end
== Cascading config
The default config hash is at:
Mysql2::Client.default_query_options
which defaults to:
{:async => false, :as => :hash, :symbolize_keys => false}
that can be used as so:
# these are the defaults all Mysql2::Client instances inherit
Mysql2::Client.default_query_options.merge!(:as => :array)
or
# this will change the defaults for all future results returned by the #query method _for this connection only_
c = Mysql2::Client.new
c.query_options.merge!(:symbolize_keys => true)
or
# this will set the options for the Mysql2::Result instance returned from the #query method
c = Mysql2::Client.new
c.query(sql, :symbolize_keys => true)
== Result types
=== Array of Arrays
Pass the {:as => :array} option to any of the above methods of configuration
=== Array of Hashes
The default result type is set to :hash, but you can override a previous setting to something else with {:as => :hash}
=== Others...
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.
If you'd like to see either of these (or others), open an issue and start bugging me about it ;)
== Timezones
You can set the :timezone option to :local or :utc to tell Mysql2 which timezone you'd like to have Time objects in
== Async == Async
Mysql2::Client takes advantage of the MySQL C API's (undocumented) non-blocking function mysql_send_query for *all* queries. Mysql2::Client takes advantage of the MySQL C API's (undocumented) non-blocking function mysql_send_query for *all* queries.
@ -151,11 +197,11 @@ then iterating over every row using an #each like method yielding a block:
# These results are from the query_with_mysql_casting.rb script in the benchmarks folder # These results are from the query_with_mysql_casting.rb script in the benchmarks folder
user system total real user system total real
Mysql2 Mysql2
0.890000 0.190000 1.080000 ( 2.028887) 0.750000 0.180000 0.930000 ( 1.821655)
do_mysql do_mysql
1.740000 0.220000 1.960000 ( 2.909290) 1.650000 0.200000 1.850000 ( 2.811357)
Mysql Mysql
7.330000 0.350000 7.680000 ( 8.013160) 7.500000 0.210000 7.710000 ( 8.065871)
== Special Thanks == Special Thanks

View File

@ -5,7 +5,7 @@ require 'rubygems'
require 'benchmark' require 'benchmark'
require 'active_record' require 'active_record'
ActiveRecord::Base.default_timezone = 'Pacific Time (US & Canada)' ActiveRecord::Base.default_timezone = :local
ActiveRecord::Base.time_zone_aware_attributes = true ActiveRecord::Base.time_zone_aware_attributes = true
number_of = 10 number_of = 10

View File

@ -45,8 +45,8 @@ Benchmark.bmbm do |x|
x.report do x.report do
puts "Mysql2" puts "Mysql2"
number_of.times do number_of.times do
mysql2_result = mysql2.query sql mysql2_result = mysql2.query sql, :symbolize_keys => true
mysql2_result.each(:symbolize_keys => true) do |res| mysql2_result.each do |res|
# puts res.inspect # puts res.inspect
end end
end end

View File

@ -17,8 +17,8 @@ Benchmark.bmbm do |x|
x.report do x.report do
puts "Mysql2" puts "Mysql2"
number_of.times do number_of.times do
mysql2_result = mysql2.query sql mysql2_result = mysql2.query sql, :symbolize_keys => true
mysql2_result.each(:symbolize_keys => true) do |res| mysql2_result.each do |res|
# puts res.inspect # puts res.inspect
end end
end end

View File

@ -3,7 +3,8 @@
VALUE cMysql2Client; VALUE cMysql2Client;
extern VALUE mMysql2, cMysql2Error, intern_encoding_from_charset; extern VALUE mMysql2, cMysql2Error, intern_encoding_from_charset;
extern ID sym_id, sym_version, sym_async; extern ID sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as, sym_array;
extern ID intern_merge;
#define REQUIRE_OPEN_DB(_ctxt) \ #define REQUIRE_OPEN_DB(_ctxt) \
if(!_ctxt->net.vio) { \ if(!_ctxt->net.vio) { \
@ -225,7 +226,12 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
} }
VALUE resultObj = rb_mysql_result_to_obj(result); VALUE resultObj = rb_mysql_result_to_obj(result);
// pass-through query options for result construction later
rb_iv_set(resultObj, "@query_options", rb_obj_dup(rb_iv_get(self, "@query_options")));
#ifdef HAVE_RUBY_ENCODING_H
rb_iv_set(resultObj, "@encoding", rb_iv_get(self, "@encoding")); rb_iv_set(resultObj, "@encoding", rb_iv_get(self, "@encoding"));
#endif
return resultObj; return resultObj;
} }
@ -234,29 +240,31 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
fd_set fdset; fd_set fdset;
int fd, retval; int fd, retval;
int async = 0; int async = 0;
VALUE opts; VALUE opts, defaults;
VALUE rb_async; MYSQL *client;
MYSQL * client; Data_Get_Struct(self, MYSQL, client);
REQUIRE_OPEN_DB(client);
args.mysql = client;
defaults = rb_iv_get(self, "@query_options");
if (rb_scan_args(argc, argv, "11", &args.sql, &opts) == 2) { if (rb_scan_args(argc, argv, "11", &args.sql, &opts) == 2) {
if ((rb_async = rb_hash_aref(opts, sym_async)) != Qnil) { opts = rb_funcall(defaults, intern_merge, 1, opts);
async = rb_async == Qtrue ? 1 : 0; rb_iv_set(self, "@query_options", opts);
if (rb_hash_aref(opts, sym_async) == Qtrue) {
async = 1;
} }
} else {
opts = defaults;
} }
Check_Type(args.sql, T_STRING);
#ifdef HAVE_RUBY_ENCODING_H #ifdef HAVE_RUBY_ENCODING_H
rb_encoding *conn_enc = rb_to_encoding(rb_iv_get(self, "@encoding")); rb_encoding *conn_enc = rb_to_encoding(rb_iv_get(self, "@encoding"));
// ensure the string is in the encoding the connection is expecting // ensure the string is in the encoding the connection is expecting
args.sql = rb_str_export_to_enc(args.sql, conn_enc); args.sql = rb_str_export_to_enc(args.sql, conn_enc);
#endif #endif
Data_Get_Struct(self, MYSQL, client);
REQUIRE_OPEN_DB(client);
args.mysql = client;
if (rb_thread_blocking_region(nogvl_send_query, &args, RUBY_UBF_IO, 0) == Qfalse) { if (rb_thread_blocking_region(nogvl_send_query, &args, RUBY_UBF_IO, 0) == Qfalse) {
return rb_raise_mysql2_error(client); return rb_raise_mysql2_error(client);
} }
@ -272,15 +280,20 @@ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
retval = rb_thread_select(fd + 1, &fdset, NULL, NULL, NULL); retval = rb_thread_select(fd + 1, &fdset, NULL, NULL, NULL);
if (retval < 0) { if (retval < 0) {
rb_sys_fail(0); rb_sys_fail(0);
} }
if (retval > 0) { if (retval > 0) {
break; break;
} }
} }
return rb_mysql_client_async_result(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 { } else {
return Qnil; return Qnil;
} }

View File

@ -1,7 +1,9 @@
#include <mysql2_ext.h> #include <mysql2_ext.h>
VALUE mMysql2, cMysql2Error, intern_encoding_from_charset; VALUE mMysql2, cMysql2Error, intern_encoding_from_charset;
ID sym_id, sym_version, sym_async; ID sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as,
sym_array, sym_timezone, sym_utc, sym_local;
ID intern_merge;
/* call-seq: client.create_statement # => Mysql2::Statement /* call-seq: client.create_statement # => Mysql2::Statement
* *
@ -23,9 +25,17 @@ void Init_mysql2() {
mMysql2 = rb_define_module("Mysql2"); mMysql2 = rb_define_module("Mysql2");
cMysql2Error = rb_const_get(mMysql2, rb_intern("Error")); cMysql2Error = rb_const_get(mMysql2, rb_intern("Error"));
sym_id = ID2SYM(rb_intern("id")); intern_merge = rb_intern("merge");
sym_version = ID2SYM(rb_intern("version"));
sym_async = ID2SYM(rb_intern("async")); 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"); intern_encoding_from_charset = rb_intern("encoding_from_charset");

View File

@ -4,12 +4,13 @@
rb_encoding *binaryEncoding; rb_encoding *binaryEncoding;
#endif #endif
ID sym_symbolize_keys; ID intern_new, intern_utc, intern_local, intern_encoding_from_charset_code;
ID intern_new, intern_utc, intern_encoding_from_charset_code;
VALUE cMysql2Result; VALUE cMysql2Result;
VALUE cBigDecimal, cDate, cDateTime; VALUE cBigDecimal, cDate, cDateTime;
extern VALUE mMysql2, cMysql2Client, cMysql2Error, intern_encoding_from_charset; 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;
static void rb_mysql_result_mark(void * wrapper) { static void rb_mysql_result_mark(void * wrapper) {
mysql2_result_wrapper * w = wrapper; mysql2_result_wrapper * w = wrapper;
@ -85,12 +86,12 @@ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int
return rb_field; return rb_field;
} }
static VALUE rb_mysql_result_fetch_row(int argc, VALUE * argv, VALUE self) { static VALUE rb_mysql_result_fetch_row(VALUE self, ID timezone, int symbolizeKeys, int asArray) {
VALUE rowHash, opts, block; VALUE rowVal;
mysql2_result_wrapper * wrapper; mysql2_result_wrapper * wrapper;
MYSQL_ROW row; MYSQL_ROW row;
MYSQL_FIELD * fields = NULL; MYSQL_FIELD * fields = NULL;
unsigned int i = 0, symbolizeKeys = 0; unsigned int i = 0;
unsigned long * fieldLengths; unsigned long * fieldLengths;
void * ptr; void * ptr;
#ifdef HAVE_RUBY_ENCODING_H #ifdef HAVE_RUBY_ENCODING_H
@ -100,20 +101,17 @@ static VALUE rb_mysql_result_fetch_row(int argc, VALUE * argv, VALUE self) {
GetMysql2Result(self, wrapper); GetMysql2Result(self, wrapper);
if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1) {
Check_Type(opts, T_HASH);
if (rb_hash_aref(opts, sym_symbolize_keys) == Qtrue) {
symbolizeKeys = 1;
}
}
ptr = wrapper->result; ptr = wrapper->result;
row = (MYSQL_ROW)rb_thread_blocking_region(nogvl_fetch_row, ptr, RUBY_UBF_IO, 0); row = (MYSQL_ROW)rb_thread_blocking_region(nogvl_fetch_row, ptr, RUBY_UBF_IO, 0);
if (row == NULL) { if (row == NULL) {
return Qnil; return Qnil;
} }
rowHash = rb_hash_new(); if (asArray) {
rowVal = rb_ary_new2(wrapper->numberOfFields);
} else {
rowVal = rb_hash_new();
}
fields = mysql_fetch_fields(wrapper->result); fields = mysql_fetch_fields(wrapper->result);
fieldLengths = mysql_fetch_lengths(wrapper->result); fieldLengths = mysql_fetch_lengths(wrapper->result);
if (wrapper->fields == Qnil) { if (wrapper->fields == Qnil) {
@ -151,7 +149,7 @@ static VALUE rb_mysql_result_fetch_row(int argc, VALUE * argv, VALUE self) {
case MYSQL_TYPE_TIME: { // TIME field case MYSQL_TYPE_TIME: { // TIME field
int hour, min, sec, tokens; int hour, min, sec, tokens;
tokens = sscanf(row[i], "%2d:%2d:%2d", &hour, &min, &sec); tokens = sscanf(row[i], "%2d:%2d:%2d", &hour, &min, &sec);
val = rb_funcall(rb_cTime, intern_utc, 6, INT2NUM(0), INT2NUM(1), INT2NUM(1), INT2NUM(hour), INT2NUM(min), INT2NUM(sec)); val = rb_funcall(rb_cTime, timezone, 6, INT2NUM(2000), INT2NUM(1), INT2NUM(1), INT2NUM(hour), INT2NUM(min), INT2NUM(sec));
break; break;
} }
case MYSQL_TYPE_TIMESTAMP: // TIMESTAMP field case MYSQL_TYPE_TIMESTAMP: // TIMESTAMP field
@ -165,7 +163,7 @@ static VALUE rb_mysql_result_fetch_row(int argc, VALUE * argv, VALUE self) {
rb_raise(cMysql2Error, "Invalid date: %s", row[i]); rb_raise(cMysql2Error, "Invalid date: %s", row[i]);
val = Qnil; val = Qnil;
} else { } else {
val = rb_funcall(rb_cTime, intern_utc, 6, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec)); val = rb_funcall(rb_cTime, timezone, 6, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec));
} }
} }
break; break;
@ -220,12 +218,20 @@ static VALUE rb_mysql_result_fetch_row(int argc, VALUE * argv, VALUE self) {
#endif #endif
break; break;
} }
rb_hash_aset(rowHash, field, val); if (asArray) {
rb_ary_push(rowVal, val);
} else {
rb_hash_aset(rowVal, field, val);
}
} else { } else {
rb_hash_aset(rowHash, field, Qnil); if (asArray) {
rb_ary_push(rowVal, Qnil);
} else {
rb_hash_aset(rowVal, field, Qnil);
}
} }
} }
return rowHash; return rowVal;
} }
static VALUE rb_mysql_result_fetch_fields(VALUE self) { static VALUE rb_mysql_result_fetch_fields(VALUE self) {
@ -249,18 +255,44 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) {
} }
static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
VALUE opts, block; VALUE defaults, opts, block, timezoneVal;
ID timezone;
mysql2_result_wrapper * wrapper; mysql2_result_wrapper * wrapper;
unsigned long i; unsigned long i;
int symbolizeKeys = 0, asArray = 0;
GetMysql2Result(self, wrapper); GetMysql2Result(self, wrapper);
rb_scan_args(argc, argv, "01&", &opts, &block); defaults = rb_iv_get(self, "@query_options");
if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1) {
opts = rb_funcall(defaults, intern_merge, 1, opts);
} else {
opts = defaults;
}
if (rb_hash_aref(opts, sym_symbolize_keys) == Qtrue) {
symbolizeKeys = 1;
}
if (rb_hash_aref(opts, sym_as) == sym_array) {
asArray = 1;
}
timezoneVal = rb_hash_aref(opts, sym_timezone);
if (timezoneVal == sym_local) {
timezone = intern_local;
} else if (timezoneVal == sym_utc) {
timezone = intern_utc;
} else {
rb_warn(":timezone config option must be :utc or :local - defaulting to :local");
timezone = intern_local;
}
if (wrapper->lastRowProcessed == 0) { if (wrapper->lastRowProcessed == 0) {
wrapper->numberOfRows = mysql_num_rows(wrapper->result); wrapper->numberOfRows = mysql_num_rows(wrapper->result);
if (wrapper->numberOfRows == 0) { if (wrapper->numberOfRows == 0) {
return Qnil; wrapper->rows = rb_ary_new();
return wrapper->rows;
} }
wrapper->rows = rb_ary_new2(wrapper->numberOfRows); wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
} }
@ -279,7 +311,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
if (i < rowsProcessed) { if (i < rowsProcessed) {
row = rb_ary_entry(wrapper->rows, i); row = rb_ary_entry(wrapper->rows, i);
} else { } else {
row = rb_mysql_result_fetch_row(argc, argv, self); row = rb_mysql_result_fetch_row(self, timezone, symbolizeKeys, asArray);
rb_ary_store(wrapper->rows, i, row); rb_ary_store(wrapper->rows, i, row);
wrapper->lastRowProcessed++; wrapper->lastRowProcessed++;
} }
@ -319,8 +351,7 @@ VALUE rb_mysql_result_to_obj(MYSQL_RES * r) {
return obj; return obj;
} }
void init_mysql2_result() void init_mysql2_result() {
{
cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal")); cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal"));
cDate = rb_const_get(rb_cObject, rb_intern("Date")); cDate = rb_const_get(rb_cObject, rb_intern("Date"));
cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime")); cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
@ -329,9 +360,9 @@ void init_mysql2_result()
rb_define_method(cMysql2Result, "each", rb_mysql_result_each, -1); rb_define_method(cMysql2Result, "each", rb_mysql_result_each, -1);
rb_define_method(cMysql2Result, "fields", rb_mysql_result_fetch_fields, 0); rb_define_method(cMysql2Result, "fields", rb_mysql_result_fetch_fields, 0);
sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys")); intern_new = rb_intern("new");
intern_new = rb_intern("new"); intern_utc = rb_intern("utc");
intern_utc = rb_intern("utc"); intern_local = rb_intern("local");
intern_encoding_from_charset_code = rb_intern("encoding_from_charset_code"); intern_encoding_from_charset_code = rb_intern("encoding_from_charset_code");
#ifdef HAVE_RUBY_ENCODING_H #ifdef HAVE_RUBY_ENCODING_H

View File

@ -131,7 +131,7 @@ module ActiveRecord
cattr_accessor :emulate_booleans cattr_accessor :emulate_booleans
self.emulate_booleans = true self.emulate_booleans = true
ADAPTER_NAME = 'MySQL2' ADAPTER_NAME = 'Mysql2'
PRIMARY = "PRIMARY" PRIMARY = "PRIMARY"
LOST_CONNECTION_ERROR_MESSAGES = [ LOST_CONNECTION_ERROR_MESSAGES = [
@ -161,6 +161,7 @@ module ActiveRecord
super(connection, logger) super(connection, logger)
@connection_options, @config = connection_options, config @connection_options, @config = connection_options, config
@quoted_column_names, @quoted_table_names = {}, {} @quoted_column_names, @quoted_table_names = {}, {}
configure_connection
end end
def adapter_name def adapter_name
@ -263,15 +264,45 @@ module ActiveRecord
# DATABASE STATEMENTS ====================================== # DATABASE STATEMENTS ======================================
def select_values(sql, name = nil) # FIXME: re-enable the following once a "better" query_cache solution is in core
select(sql, name).map { |row| row.values.first } #
end # The overrides below perform much better than the originals in AbstractAdapter
# because we're able to take advantage of mysql2's lazy-loading capabilities
#
# # Returns a record hash with the column names as keys and column values
# # as values.
# def select_one(sql, name = nil)
# result = execute(sql, name)
# result.each(:as => :hash) do |r|
# return r
# end
# end
#
# # Returns a single value from a record
# def select_value(sql, name = nil)
# result = execute(sql, name)
# if first = result.first
# first.first
# end
# end
#
# # Returns an array of the values of the first column in a select:
# # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
# def select_values(sql, name = nil)
# execute(sql, name).map { |row| row.first }
# end
# Returns an array of arrays containing the field values.
# Order is the same as that returned by +columns+.
def select_rows(sql, name = nil) def select_rows(sql, name = nil)
select(sql, name).map { |row| row.values } execute(sql, name).to_a
end end
# Executes the SQL statement in the context of this connection.
def execute(sql, name = nil) 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
if name == :skip_logging if name == :skip_logging
@connection.query(sql) @connection.query(sql)
else else
@ -286,7 +317,7 @@ module ActiveRecord
end end
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
super sql, name super
id_value || @connection.last_id id_value || @connection.last_id
end end
alias :create :insert_sql alias :create :insert_sql
@ -393,8 +424,8 @@ module ActiveRecord
def tables(name = nil) def tables(name = nil)
tables = [] tables = []
execute("SHOW TABLES", name).each(:symbolize_keys => true) do |field| execute("SHOW TABLES", name).each do |field|
tables << field.values.first tables << field.first
end end
tables tables
end end
@ -407,7 +438,7 @@ module ActiveRecord
indexes = [] indexes = []
current_index = nil current_index = nil
result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name) result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name)
result.each(:symbolize_keys => true) do |row| result.each(:symbolize_keys => true, :as => :hash) do |row|
if current_index != row[:Key_name] if current_index != row[:Key_name]
next if row[:Key_name] == PRIMARY # skip the primary key next if row[:Key_name] == PRIMARY # skip the primary key
current_index = row[:Key_name] current_index = row[:Key_name]
@ -423,7 +454,7 @@ module ActiveRecord
sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}" sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
columns = [] columns = []
result = execute(sql, :skip_logging) result = execute(sql, :skip_logging)
result.each(:symbolize_keys => true) { |field| result.each(:symbolize_keys => true, :as => :hash) { |field|
columns << Mysql2Column.new(field[:Field], field[:Default], field[:Type], field[:Null] == "YES") columns << Mysql2Column.new(field[:Field], field[:Default], field[:Type], field[:Null] == "YES")
} }
columns columns
@ -520,7 +551,7 @@ module ActiveRecord
def pk_and_sequence_for(table) def pk_and_sequence_for(table)
keys = [] keys = []
result = execute("describe #{quote_table_name(table)}") result = execute("describe #{quote_table_name(table)}")
result.each(:symbolize_keys => true) do |row| result.each(:symbolize_keys => true, :as => :hash) do |row|
keys << row[:Field] if row[:Key] == "PRI" keys << row[:Field] if row[:Key] == "PRI"
end end
keys.length == 1 ? [keys.first, nil] : nil keys.length == 1 ? [keys.first, nil] : nil
@ -574,6 +605,7 @@ module ActiveRecord
end end
def configure_connection def configure_connection
@connection.query_options.merge!(:as => :array)
encoding = @config[:encoding] encoding = @config[:encoding]
execute("SET NAMES '#{encoding}'", :skip_logging) if encoding execute("SET NAMES '#{encoding}'", :skip_logging) if encoding
@ -582,8 +614,10 @@ module ActiveRecord
execute("SET SQL_AUTO_IS_NULL=0", :skip_logging) execute("SET SQL_AUTO_IS_NULL=0", :skip_logging)
end end
# Returns an array of record hashes with the column names as keys and
# column values as values.
def select(sql, name = nil) def select(sql, name = nil)
execute(sql, name).to_a execute(sql, name).each(:as => :hash)
end end
def supports_views? def supports_views?

View File

@ -1,6 +1,16 @@
module Mysql2 module Mysql2
class Client class Client
def initialize opts = {} attr_reader :query_options
@@default_query_options = {
:symbolize_keys => false,
:async => false,
:as => :hash,
:timezone => :local
}
def initialize(opts = {})
@query_options = @@default_query_options.dup
init_connection init_connection
[:reconnect, :connect_timeout].each do |key| [:reconnect, :connect_timeout].each do |key|
@ -23,6 +33,10 @@ module Mysql2
connect user, pass, host, port, database, socket connect user, pass, host, port, database, socket
end end
def self.default_query_options
@@default_query_options
end
# NOTE: from ruby-mysql # NOTE: from ruby-mysql
if defined? Encoding if defined? Encoding
CHARSET_MAP = { CHARSET_MAP = {

View File

@ -9,7 +9,7 @@ Gem::Specification.new do |s|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Brian Lopez"] s.authors = ["Brian Lopez"]
s.date = %q{2010-07-17} s.date = %q{2010-08-01}
s.email = %q{seniorlopez@gmail.com} s.email = %q{seniorlopez@gmail.com}
s.extensions = ["ext/mysql2/extconf.rb"] s.extensions = ["ext/mysql2/extconf.rb"]
s.extra_rdoc_files = [ s.extra_rdoc_files = [
@ -29,6 +29,8 @@ Gem::Specification.new do |s|
"benchmark/sequel.rb", "benchmark/sequel.rb",
"benchmark/setup_db.rb", "benchmark/setup_db.rb",
"examples/eventmachine.rb", "examples/eventmachine.rb",
"ext/mysql2/client.c",
"ext/mysql2/client.h",
"ext/mysql2/extconf.rb", "ext/mysql2/extconf.rb",
"ext/mysql2/mysql2_ext.c", "ext/mysql2/mysql2_ext.c",
"ext/mysql2/mysql2_ext.h", "ext/mysql2/mysql2_ext.h",

View File

@ -21,14 +21,14 @@ describe ActiveRecord::ConnectionAdapters::Mysql2Adapter do
end end
it "should be able to execute a raw query" do it "should be able to execute a raw query" do
@connection.execute("SELECT 1 as one").first['one'].should eql(1) @connection.execute("SELECT 1 as one").first.first.should eql(1)
@connection.execute("SELECT NOW() as n").first['n'].class.should eql(Time) @connection.execute("SELECT NOW() as n").first.first.class.should eql(Time)
end end
end end
context "columns" do context "columns" do
before(:all) do before(:all) do
ActiveRecord::Base.default_timezone = 'Pacific Time (US & Canada)' ActiveRecord::Base.default_timezone = :local
ActiveRecord::Base.time_zone_aware_attributes = true ActiveRecord::Base.time_zone_aware_attributes = true
ActiveRecord::Base.establish_connection(:adapter => 'mysql2', :database => 'test') ActiveRecord::Base.establish_connection(:adapter => 'mysql2', :database => 'test')
Mysql2Test2.connection.execute %[ Mysql2Test2.connection.execute %[
@ -72,7 +72,7 @@ describe ActiveRecord::ConnectionAdapters::Mysql2Adapter do
end end
after(:all) do after(:all) do
Mysql2Test2.connection.execute("DELETE FROM mysql2_test WHERE id=#{@test_result['id']}") Mysql2Test2.connection.execute("DELETE FROM mysql2_test WHERE id=#{@test_result.first}")
end end
it "default value should be cast to the expected type of the field" do it "default value should be cast to the expected type of the field" do
@ -89,9 +89,9 @@ describe ActiveRecord::ConnectionAdapters::Mysql2Adapter do
test.double_test.should eql('1.0000'.to_f) test.double_test.should eql('1.0000'.to_f)
test.decimal_test.should eql(BigDecimal.new('1.0000')) test.decimal_test.should eql(BigDecimal.new('1.0000'))
test.date_test.should eql(Date.parse('2010-01-01')) test.date_test.should eql(Date.parse('2010-01-01'))
test.date_time_test.should eql(DateTime.parse('2010-01-01 00:00:00')) test.date_time_test.should eql(Time.local(2010,1,1,0,0,0))
test.timestamp_test.should be_nil test.timestamp_test.should be_nil
test.time_test.class.should eql(DateTime) test.time_test.class.should eql(Time)
test.year_test.should eql(2010) test.year_test.should eql(2010)
test.char_test.should eql('abcdefghij') test.char_test.should eql('abcdefghij')
test.varchar_test.should eql('abcdefghij') test.varchar_test.should eql('abcdefghij')
@ -125,8 +125,9 @@ describe ActiveRecord::ConnectionAdapters::Mysql2Adapter do
test.double_test.should eql('1.0000'.to_f) test.double_test.should eql('1.0000'.to_f)
test.decimal_test.should eql(BigDecimal.new('1.0000')) test.decimal_test.should eql(BigDecimal.new('1.0000'))
test.date_test.should eql(Date.parse('2010-01-01')) test.date_test.should eql(Date.parse('2010-01-01'))
test.date_time_test.should eql(Time.utc(2010,1,1,0,0,0)) test.date_time_test.should eql(Time.local(2010,1,1,0,0,0))
test.timestamp_test.class.should eql(ActiveSupport::TimeWithZone) test.timestamp_test.class.should eql(ActiveSupport::TimeWithZone) if RUBY_VERSION >= "1.9"
test.timestamp_test.class.should eql(Time) if RUBY_VERSION < "1.9"
test.time_test.class.should eql(Time) test.time_test.class.should eql(Time)
test.year_test.should eql(2010) test.year_test.should eql(2010)
test.char_test.should eql('abcdefghij') test.char_test.should eql('abcdefghij')

View File

@ -14,6 +14,10 @@ describe Mysql2::Client do
end end
end end
it "should have a global default_query_options hash" do
Mysql2::Client.should respond_to(:default_query_options)
end
it "should be able to connect via SSL options" do it "should be able to connect via SSL options" do
pending("DON'T WORRY, THIS TEST PASSES :) - but is machine-specific. You need to have MySQL running with SSL configured and enabled. Then update the paths in this test to your needs and remove the pending state.") pending("DON'T WORRY, THIS TEST PASSES :) - but is machine-specific. You need to have MySQL running with SSL configured and enabled. Then update the paths in this test to your needs and remove the pending state.")
ssl_client = nil ssl_client = nil
@ -49,6 +53,26 @@ describe Mysql2::Client do
@client.should respond_to(:query) @client.should respond_to(:query)
end end
context "#query" do
it "should accept an options hash that inherits from Mysql2::Client.default_query_options" do
@client.query "SELECT 1", :something => :else
@client.query_options.should eql(@client.query_options.merge(:something => :else))
end
it "should return results as a hash by default" do
@client.query("SELECT 1").first.class.should eql(Hash)
end
it "should be able to return results as an array" do
@client.query("SELECT 1", :as => :array).first.class.should eql(Array)
@client.query("SELECT 1").each(:as => :array)
end
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
end
it "should respond to #escape" do it "should respond to #escape" do
@client.should respond_to(:escape) @client.should respond_to(:escape)
end end

View File

@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
describe Mysql2::Result do describe Mysql2::Result do
before(:all) do before(:each) do
@client = Mysql2::Client.new :host => "localhost", :username => "root" @client = Mysql2::Client.new :host => "localhost", :username => "root"
end end
@ -37,18 +37,23 @@ describe Mysql2::Result do
it "should yield rows as hash's with symbol keys if :symbolize_keys was set to true" do it "should yield rows as hash's with symbol keys if :symbolize_keys was set to true" do
@result.each(:symbolize_keys => true) do |row| @result.each(:symbolize_keys => true) do |row|
row.class.should eql(Hash)
row.keys.first.class.should eql(Symbol) row.keys.first.class.should eql(Symbol)
end end
end end
it "should be able to return results as an array" do
@result.each(:as => :array) do |row|
row.class.should eql(Array)
end
end
it "should cache previously yielded results" do it "should cache previously yielded results" do
@result.first.should eql(@result.first) @result.first.should eql(@result.first)
end end
end end
context "#fields" do context "#fields" do
before(:all) do before(:each) do
@client.query "USE test" @client.query "USE test"
@test_result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1") @test_result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1")
end end
@ -64,7 +69,7 @@ describe Mysql2::Result do
end end
context "row data type mapping" do context "row data type mapping" do
before(:all) do before(:each) do
@client.query "USE test" @client.query "USE test"
@client.query %[ @client.query %[
CREATE TABLE IF NOT EXISTS mysql2_test ( CREATE TABLE IF NOT EXISTS mysql2_test (
@ -192,11 +197,7 @@ describe Mysql2::Result do
it "should return Time for a TIME value" do it "should return Time for a TIME value" do
@test_result['time_test'].class.should eql(Time) @test_result['time_test'].class.should eql(Time)
if RUBY_VERSION >= "1.9.2" @test_result['time_test'].strftime("%F %T").should eql('2000-01-01 11:44:00')
@test_result['time_test'].strftime("%F %T").should eql('0000-01-01 11:44:00')
else
@test_result['time_test'].strftime("%F %T").should eql('2000-01-01 11:44:00')
end
end end
it "should return Date for a DATE value" do it "should return Date for a DATE value" do