Skip to content
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