add support for configuring which timezone Time objects should be created in

This commit is contained in:
Brian Lopez 2010-08-02 01:19:28 -07:00
parent f3151db928
commit fc6c24a20c
7 changed files with 50 additions and 26 deletions

View File

@ -100,6 +100,10 @@ The default result type is set to :hash, but you can override a previous setting
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. 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 ;) 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.

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

@ -1,7 +1,8 @@
#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, sym_symbolize_keys, sym_as, sym_array; ID sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as,
sym_array, sym_timezone, sym_utc, sym_local;
ID intern_merge; ID intern_merge;
/* Ruby Extension initializer */ /* Ruby Extension initializer */
@ -11,6 +12,9 @@ void Init_mysql2() {
intern_merge = rb_intern("merge"); 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_array = ID2SYM(rb_intern("array"));
sym_as = ID2SYM(rb_intern("as")); sym_as = ID2SYM(rb_intern("as"));
sym_id = ID2SYM(rb_intern("id")); sym_id = ID2SYM(rb_intern("id"));

View File

@ -4,12 +4,12 @@
rb_encoding *binaryEncoding; rb_encoding *binaryEncoding;
#endif #endif
ID intern_new, intern_utc, intern_encoding_from_charset_code; ID intern_new, intern_utc, intern_local, 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; extern ID sym_symbolize_keys, sym_as, sym_array, sym_timezone, sym_local, sym_utc;
extern ID intern_merge; extern ID intern_merge;
static void rb_mysql_result_mark(void * wrapper) { static void rb_mysql_result_mark(void * wrapper) {
@ -86,13 +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(VALUE self, VALUE opts) { static VALUE rb_mysql_result_fetch_row(VALUE self, ID timezone, int symbolizeKeys, int asArray) {
VALUE rowVal; 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; unsigned int i = 0;
int symbolizeKeys = 0, asArray = 0;
unsigned long * fieldLengths; unsigned long * fieldLengths;
void * ptr; void * ptr;
#ifdef HAVE_RUBY_ENCODING_H #ifdef HAVE_RUBY_ENCODING_H
@ -102,14 +101,6 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, VALUE opts) {
GetMysql2Result(self, wrapper); GetMysql2Result(self, wrapper);
if (rb_hash_aref(opts, sym_symbolize_keys) == Qtrue) {
symbolizeKeys = 1;
}
if (rb_hash_aref(opts, sym_as) == sym_array) {
asArray = 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) {
@ -158,7 +149,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, VALUE opts) {
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(2000), 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
@ -172,7 +163,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, VALUE opts) {
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;
@ -264,9 +255,11 @@ 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 defaults, 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);
@ -277,6 +270,24 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
opts = defaults; 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) {
@ -300,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(self, opts); 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++;
} }
@ -349,8 +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);
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

@ -295,6 +295,9 @@ module ActiveRecord
# Executes the SQL statement in the context of this connection. # 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

View File

@ -4,7 +4,8 @@ module Mysql2
@@default_query_options = { @@default_query_options = {
:symbolize_keys => false, :symbolize_keys => false,
:async => false, :async => false,
:as => :hash :as => :hash,
:timezone => :local
} }
def initialize(opts = {}) def initialize(opts = {})

View File

@ -28,7 +28,7 @@ describe ActiveRecord::ConnectionAdapters::Mysql2Adapter do
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 %[
@ -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,8 @@ 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(Time)
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')