Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for casting DATE values as Time objects. (:cast_dates_as_times => true) #1012

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
49 changes: 31 additions & 18 deletions ext/mysql2/result.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ typedef struct {
int symbolizeKeys;
int asArray;
int castBool;
int castDateAsTime;
int cacheRows;
int cast;
int streaming;
Expand All @@ -40,6 +41,7 @@ static ID intern_new, intern_utc, intern_local, intern_localtime, intern_local_o
static VALUE sym_symbolize_keys, sym_as, sym_array, sym_database_timezone,
sym_application_timezone, sym_local, sym_utc, sym_cast_booleans,
sym_cache_rows, sym_cast, sym_stream, sym_name;
static VALUE sym_cast_dates_as_times;

/* Mark any VALUEs that are only referenced in C, so the GC won't get them. */
static void rb_mysql_result_mark(void * wrapper) {
Expand Down Expand Up @@ -212,6 +214,19 @@ static unsigned int msec_char_to_uint(char *msec_char, size_t len)
return (unsigned int)strtoul(msec_char, NULL, 10);
}

static VALUE new_time(unsigned int year, unsigned int month, unsigned int day, unsigned int hour, unsigned int minute, unsigned int second, unsigned long second_part, const result_each_args *args)
{
VALUE val = rb_funcall(rb_cTime, args->db_timezone, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(minute), UINT2NUM(second), ULONG2NUM(second_part));
if (!NIL_P(args->app_timezone)) {
if (args->app_timezone == intern_local) {
val = rb_funcall(val, intern_localtime, 0);
} else { // utc
val = rb_funcall(val, intern_utc, 0);
}
}
return val;
}

static void rb_mysql_result_alloc_result_buffers(VALUE self, MYSQL_FIELD *fields) {
unsigned int i;
GET_RESULT(self);
Expand Down Expand Up @@ -397,7 +412,11 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co
case MYSQL_TYPE_DATE: // MYSQL_TIME
case MYSQL_TYPE_NEWDATE: // MYSQL_TIME
ts = (MYSQL_TIME*)result_buffer->buffer;
val = rb_funcall(cDate, intern_new, 3, INT2NUM(ts->year), INT2NUM(ts->month), INT2NUM(ts->day));
if (args->castDateAsTime) {
val = new_time(ts->year, ts->month, ts->day, 0, 0, 0, 0, args);
} else {
val = rb_funcall(cDate, intern_new, 3, INT2NUM(ts->year), INT2NUM(ts->month), INT2NUM(ts->day));
}
break;
case MYSQL_TYPE_TIME: // MYSQL_TIME
ts = (MYSQL_TIME*)result_buffer->buffer;
Expand Down Expand Up @@ -432,14 +451,7 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co
}
}
} else {
val = rb_funcall(rb_cTime, args->db_timezone, 7, UINT2NUM(ts->year), UINT2NUM(ts->month), UINT2NUM(ts->day), UINT2NUM(ts->hour), UINT2NUM(ts->minute), UINT2NUM(ts->second), ULONG2NUM(ts->second_part));
if (!NIL_P(args->app_timezone)) {
if (args->app_timezone == intern_local) {
val = rb_funcall(val, intern_localtime, 0);
} else { // utc
val = rb_funcall(val, intern_utc, 0);
}
}
val = new_time(ts->year, ts->month, ts->day, ts->hour, ts->minute, ts->second, ts->second_part, args);
}
break;
}
Expand Down Expand Up @@ -621,14 +633,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const r
}
} else {
msec = msec_char_to_uint(msec_char, sizeof(msec_char));
val = rb_funcall(rb_cTime, args->db_timezone, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec));
if (!NIL_P(args->app_timezone)) {
if (args->app_timezone == intern_local) {
val = rb_funcall(val, intern_localtime, 0);
} else { /* utc */
val = rb_funcall(val, intern_utc, 0);
}
}
val = new_time(year, month, day, hour, min, sec, msec, args);
}
}
}
Expand All @@ -650,7 +655,11 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const r
rb_raise(cMysql2Error, "Invalid date in field '%.*s': %s", fields[i].name_length, fields[i].name, row[i]);
val = Qnil;
} else {
val = rb_funcall(cDate, intern_new, 3, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day));
if (args->castDateAsTime) {
val = new_time(year, month, day, 0, 0, 0, 0, args);
} else {
val = rb_funcall(cDate, intern_new, 3, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day));
}
}
}
break;
Expand Down Expand Up @@ -810,6 +819,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
VALUE defaults, opts, block, (*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args);
ID db_timezone, app_timezone, dbTz, appTz;
int symbolizeKeys, asArray, castBool, cacheRows, cast;
int castDateAsTime;

GET_RESULT(self);

Expand All @@ -828,6 +838,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
symbolizeKeys = RTEST(rb_hash_aref(opts, sym_symbolize_keys));
asArray = rb_hash_aref(opts, sym_as) == sym_array;
castBool = RTEST(rb_hash_aref(opts, sym_cast_booleans));
castDateAsTime = RTEST(rb_hash_aref(opts, sym_cast_dates_as_times));
cacheRows = RTEST(rb_hash_aref(opts, sym_cache_rows));
cast = RTEST(rb_hash_aref(opts, sym_cast));

Expand Down Expand Up @@ -881,6 +892,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
args.symbolizeKeys = symbolizeKeys;
args.asArray = asArray;
args.castBool = castBool;
args.castDateAsTime = castDateAsTime;
args.cacheRows = cacheRows;
args.cast = cast;
args.db_timezone = db_timezone;
Expand Down Expand Up @@ -986,6 +998,7 @@ void init_mysql2_result() {
sym_local = ID2SYM(rb_intern("local"));
sym_utc = ID2SYM(rb_intern("utc"));
sym_cast_booleans = ID2SYM(rb_intern("cast_booleans"));
sym_cast_dates_as_times = ID2SYM(rb_intern("cast_dates_as_times"));
sym_database_timezone = ID2SYM(rb_intern("database_timezone"));
sym_application_timezone = ID2SYM(rb_intern("application_timezone"));
sym_cache_rows = ID2SYM(rb_intern("cache_rows"));
Expand Down
6 changes: 6 additions & 0 deletions spec/mysql2/result_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,12 @@
expect(test_result['date_test'].strftime("%Y-%m-%d")).to eql('2010-04-04')
end

it "should return Time for a DATE value when :cast_dates_as_times is enabled" do
r = @client.query('SELECT date_test FROM mysql2_test', cast_dates_as_times: true).first
expect(r['date_test']).to be_an_instance_of(Time)
expect(r['date_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 00:00:00')
end

it "should return String for an ENUM value" do
expect(test_result['enum_test']).to be_an_instance_of(String)
expect(test_result['enum_test']).to eql('val1')
Expand Down
6 changes: 6 additions & 0 deletions spec/mysql2/statement_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,12 @@ def stmt_count
expect(test_result['date_test'].strftime("%Y-%m-%d")).to eql('2010-04-04')
end

it "should return Time for a DATE value when :cast_dates_as_times is enabled" do
r = @client.prepare('SELECT date_test FROM mysql2_test').execute(cast_dates_as_times: true).first
expect(r['date_test']).to be_an_instance_of(Time)
expect(r['date_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 00:00:00')
end

it "should return String for an ENUM value" do
expect(test_result['enum_test']).to be_an_instance_of(String)
expect(test_result['enum_test']).to eql('val1')
Expand Down