From b03aa432e32801fd9dfa53da9d78f56db0dca130 Mon Sep 17 00:00:00 2001 From: errolgrannum Date: Tue, 23 Oct 2018 02:18:31 +0200 Subject: [PATCH 1/9] add support for casting mysql date times to utc --- ext/mysql2/client.c | 2 +- ext/mysql2/result.c | 52 +++++++++++++++++++++++++++++++++------------ 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index d12f2c180..6b8a2d43a 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -656,7 +656,7 @@ static VALUE do_query(void *args) { tvp->tv_usec = 0; } - for(;;) { + for (;;) { retval = rb_wait_for_single_fd(async_args->fd, RB_WAITFD_IN, tvp); if (retval == 0) { diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 0ab38aabe..57d4ea401 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -24,6 +24,7 @@ typedef struct { int symbolizeKeys; int asArray; int castBool; + int castDateTime; int cacheRows; int cast; int streaming; @@ -38,7 +39,7 @@ static VALUE opt_decimal_zero, opt_float_zero, opt_time_year, opt_time_month, op static ID intern_new, intern_utc, intern_local, intern_localtime, intern_local_offset, intern_civil, intern_new_offset, intern_merge, intern_BigDecimal; static VALUE sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, - sym_application_timezone, sym_local, sym_utc, sym_cast_booleans, + sym_application_timezone, sym_local, sym_utc, sym_cast_booleans, sym_cast_datetimes, sym_cache_rows, sym_cast, sym_stream, sym_name; /* Mark any VALUEs that are only referenced in C, so the GC won't get them. */ @@ -397,7 +398,18 @@ 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->castDateTime) { + val = rb_funcall(rb_cTime, args->db_timezone, 3, INT2NUM(ts->year), INT2NUM(ts->month), INT2NUM(ts->day)); + 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); + } + } + } 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; @@ -519,14 +531,14 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const r val = mysql2_set_field_string_encoding(val, fields[i], default_internal_enc, conn_enc); } } else { - switch(type) { + switch (type) { case MYSQL_TYPE_NULL: /* NULL-type field */ val = Qnil; break; case MYSQL_TYPE_BIT: /* BIT field (MySQL 5.0.3 and up) */ if (args->castBool && fields[i].length == 1) { val = *row[i] == 1 ? Qtrue : Qfalse; - }else{ + } else { val = rb_str_new(row[i], fieldLengths[i]); } break; @@ -546,9 +558,9 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const r case MYSQL_TYPE_NEWDECIMAL: /* Precision math DECIMAL or NUMERIC field (MySQL 5.0.3 and up) */ if (fields[i].decimals == 0) { val = rb_cstr2inum(row[i], 10); - } else if (strtod(row[i], NULL) == 0.000000){ + } else if (strtod(row[i], NULL) == 0.000000) { val = rb_funcall(rb_mKernel, intern_BigDecimal, 1, opt_decimal_zero); - }else{ + } else { val = rb_funcall(rb_mKernel, intern_BigDecimal, 1, rb_str_new(row[i], fieldLengths[i])); } break; @@ -558,7 +570,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const r column_to_double = strtod(row[i], NULL); if (column_to_double == 0.000000){ val = opt_float_zero; - }else{ + } else { val = rb_float_new(column_to_double); } break; @@ -650,7 +662,18 @@ 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->castDateTime) { + val = rb_funcall(rb_cTime, args->db_timezone, 3, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day)); + 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); + } + } + } else { + val = rb_funcall(cDate, intern_new, 3, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day)); + } } } break; @@ -809,7 +832,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { result_each_args args; 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 symbolizeKeys, asArray, castBool, castDateTime, cacheRows, cast; GET_RESULT(self); @@ -828,6 +851,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)); + castDateTime = RTEST(rb_hash_aref(opts, sym_cast_datetimes)); cacheRows = RTEST(rb_hash_aref(opts, sym_cache_rows)); cast = RTEST(rb_hash_aref(opts, sym_cast)); @@ -881,6 +905,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { args.symbolizeKeys = symbolizeKeys; args.asArray = asArray; args.castBool = castBool; + args.castDateTime = castDateTime; args.cacheRows = cacheRows; args.cast = cast; args.db_timezone = db_timezone; @@ -986,12 +1011,13 @@ 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_datetimes = ID2SYM(rb_intern("cast_datetimes")); sym_database_timezone = ID2SYM(rb_intern("database_timezone")); sym_application_timezone = ID2SYM(rb_intern("application_timezone")); - sym_cache_rows = ID2SYM(rb_intern("cache_rows")); - sym_cast = ID2SYM(rb_intern("cast")); - sym_stream = ID2SYM(rb_intern("stream")); - sym_name = ID2SYM(rb_intern("name")); + sym_cache_rows = ID2SYM(rb_intern("cache_rows")); + sym_cast = ID2SYM(rb_intern("cast")); + sym_stream = ID2SYM(rb_intern("stream")); + sym_name = ID2SYM(rb_intern("name")); opt_decimal_zero = rb_str_new2("0.0"); rb_global_variable(&opt_decimal_zero); /*never GC */ From e99a032177d38b0f8a48ba328cdfb4270b5f4ccc Mon Sep 17 00:00:00 2001 From: errolgrannum Date: Tue, 23 Oct 2018 19:35:09 +0200 Subject: [PATCH 2/9] add rspec test to ensure datetimes are casted to UTC --- spec/mysql2/result_spec.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index a70b38ef0..f45b196fd 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -242,6 +242,15 @@ end end + context "cast datetimes to UTC when :cast_datetime is enabled" do + it "should return UTC Time for a DATETIME if :cast_datetime is enabled" do + result1 = @client.query('SELECT date_time_test FROM mysql2_test', database_timezone: :utc, application_timezone: :utc, cast_datetimes: true).first + result2 = @client.query('SELECT date_time_test FROM mysql2_test', cast_datetimes: false).first + expect(result1['date_time_test'].utc?).to be true + expect(result2['date_time_test'].utc?).to be false + end + end + it "should return Fixnum for a SMALLINT value" do expect(num_classes).to include(test_result['small_int_test'].class) expect(test_result['small_int_test']).to eql(10) From ddd66621c084c6467a2ada49a34e0d88f9f4502f Mon Sep 17 00:00:00 2001 From: errolgrannum Date: Tue, 30 Oct 2018 20:20:20 +0100 Subject: [PATCH 3/9] add test to ensure that cast datetimes is returning a Time object; remove UTC test --- spec/mysql2/result_spec.rb | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index f45b196fd..5009b45fe 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -242,15 +242,6 @@ end end - context "cast datetimes to UTC when :cast_datetime is enabled" do - it "should return UTC Time for a DATETIME if :cast_datetime is enabled" do - result1 = @client.query('SELECT date_time_test FROM mysql2_test', database_timezone: :utc, application_timezone: :utc, cast_datetimes: true).first - result2 = @client.query('SELECT date_time_test FROM mysql2_test', cast_datetimes: false).first - expect(result1['date_time_test'].utc?).to be true - expect(result2['date_time_test'].utc?).to be false - end - end - it "should return Fixnum for a SMALLINT value" do expect(num_classes).to include(test_result['small_int_test'].class) expect(test_result['small_int_test']).to eql(10) @@ -321,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_datetime is enabled" do + result1 = @client.query('SELECT date_test FROM mysql2_test', cast_datetimes: true).first + expect(result1['date_test']).to be_an_instance_of(Time) + expect(result1['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') From 1f04cad41cf5eddba9c830d75156437b49c79556 Mon Sep 17 00:00:00 2001 From: Joe Holt Date: Tue, 30 Oct 2018 16:33:15 -0400 Subject: [PATCH 4/9] revert unnecessary formatting changes to minimize diff noise --- ext/mysql2/client.c | 2 +- ext/mysql2/result.c | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 6b8a2d43a..d12f2c180 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -656,7 +656,7 @@ static VALUE do_query(void *args) { tvp->tv_usec = 0; } - for (;;) { + for(;;) { retval = rb_wait_for_single_fd(async_args->fd, RB_WAITFD_IN, tvp); if (retval == 0) { diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 57d4ea401..7492b5d07 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -531,14 +531,14 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const r val = mysql2_set_field_string_encoding(val, fields[i], default_internal_enc, conn_enc); } } else { - switch (type) { + switch(type) { case MYSQL_TYPE_NULL: /* NULL-type field */ val = Qnil; break; case MYSQL_TYPE_BIT: /* BIT field (MySQL 5.0.3 and up) */ if (args->castBool && fields[i].length == 1) { val = *row[i] == 1 ? Qtrue : Qfalse; - } else { + }else{ val = rb_str_new(row[i], fieldLengths[i]); } break; @@ -558,9 +558,9 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const r case MYSQL_TYPE_NEWDECIMAL: /* Precision math DECIMAL or NUMERIC field (MySQL 5.0.3 and up) */ if (fields[i].decimals == 0) { val = rb_cstr2inum(row[i], 10); - } else if (strtod(row[i], NULL) == 0.000000) { + } else if (strtod(row[i], NULL) == 0.000000){ val = rb_funcall(rb_mKernel, intern_BigDecimal, 1, opt_decimal_zero); - } else { + }else{ val = rb_funcall(rb_mKernel, intern_BigDecimal, 1, rb_str_new(row[i], fieldLengths[i])); } break; @@ -570,7 +570,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const r column_to_double = strtod(row[i], NULL); if (column_to_double == 0.000000){ val = opt_float_zero; - } else { + }else{ val = rb_float_new(column_to_double); } break; @@ -1014,10 +1014,10 @@ void init_mysql2_result() { sym_cast_datetimes = ID2SYM(rb_intern("cast_datetimes")); sym_database_timezone = ID2SYM(rb_intern("database_timezone")); sym_application_timezone = ID2SYM(rb_intern("application_timezone")); - sym_cache_rows = ID2SYM(rb_intern("cache_rows")); - sym_cast = ID2SYM(rb_intern("cast")); - sym_stream = ID2SYM(rb_intern("stream")); - sym_name = ID2SYM(rb_intern("name")); + sym_cache_rows = ID2SYM(rb_intern("cache_rows")); + sym_cast = ID2SYM(rb_intern("cast")); + sym_stream = ID2SYM(rb_intern("stream")); + sym_name = ID2SYM(rb_intern("name")); opt_decimal_zero = rb_str_new2("0.0"); rb_global_variable(&opt_decimal_zero); /*never GC */ From d9923775c969da699ae0fdbe0e099e2b3184de26 Mon Sep 17 00:00:00 2001 From: Joe Holt Date: Tue, 30 Oct 2018 16:43:01 -0400 Subject: [PATCH 5/9] rename option --- ext/mysql2/result.c | 16 ++++++++-------- spec/mysql2/result_spec.rb | 12 ++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 7492b5d07..58d761c0c 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -24,7 +24,7 @@ typedef struct { int symbolizeKeys; int asArray; int castBool; - int castDateTime; + int castDateAsTime; int cacheRows; int cast; int streaming; @@ -39,7 +39,7 @@ static VALUE opt_decimal_zero, opt_float_zero, opt_time_year, opt_time_month, op static ID intern_new, intern_utc, intern_local, intern_localtime, intern_local_offset, intern_civil, intern_new_offset, intern_merge, intern_BigDecimal; static VALUE sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, - sym_application_timezone, sym_local, sym_utc, sym_cast_booleans, sym_cast_datetimes, + sym_application_timezone, sym_local, sym_utc, sym_cast_booleans, sym_cast_dates_as_times, sym_cache_rows, sym_cast, sym_stream, sym_name; /* Mark any VALUEs that are only referenced in C, so the GC won't get them. */ @@ -398,7 +398,7 @@ 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; - if (args->castDateTime) { + if (args->castDateAsTime) { val = rb_funcall(rb_cTime, args->db_timezone, 3, INT2NUM(ts->year), INT2NUM(ts->month), INT2NUM(ts->day)); if (!NIL_P(args->app_timezone)) { if (args->app_timezone == intern_local) { @@ -662,7 +662,7 @@ 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 { - if (args->castDateTime) { + if (args->castDateAsTime) { val = rb_funcall(rb_cTime, args->db_timezone, 3, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day)); if (!NIL_P(args->app_timezone)) { if (args->app_timezone == intern_local) { @@ -832,7 +832,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { result_each_args args; 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, castDateTime, cacheRows, cast; + int symbolizeKeys, asArray, castBool, castDateAsTime, cacheRows, cast; GET_RESULT(self); @@ -851,7 +851,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)); - castDateTime = RTEST(rb_hash_aref(opts, sym_cast_datetimes)); + 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)); @@ -905,7 +905,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { args.symbolizeKeys = symbolizeKeys; args.asArray = asArray; args.castBool = castBool; - args.castDateTime = castDateTime; + args.castDateAsTime = castDateAsTime; args.cacheRows = cacheRows; args.cast = cast; args.db_timezone = db_timezone; @@ -1011,7 +1011,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_datetimes = ID2SYM(rb_intern("cast_datetimes")); + 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")); diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 5009b45fe..945522921 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -312,17 +312,17 @@ 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_datetime is enabled" do - result1 = @client.query('SELECT date_test FROM mysql2_test', cast_datetimes: true).first - expect(result1['date_test']).to be_an_instance_of(Time) - expect(result1['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') end + it "should return Time for a DATE value when :cast_dates_as_times is enabled" do + result = @client.query('SELECT date_test FROM mysql2_test', cast_dates_as_times: true).first + expect(result['date_test']).to be_an_instance_of(Time) + expect(result['date_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 00:00:00') + end + it "should raise an error given an invalid DATETIME" do expect { @client.query("SELECT CAST('1972-00-27 00:00:00' AS DATETIME) as bad_datetime").each }.to \ raise_error(Mysql2::Error, "Invalid date in field 'bad_datetime': 1972-00-27 00:00:00") From f0cabc418e5fadd26b199834bd88532ef448c23d Mon Sep 17 00:00:00 2001 From: Joe Holt Date: Tue, 30 Oct 2018 19:01:38 -0400 Subject: [PATCH 6/9] factor out new_time --- ext/mysql2/result.c | 51 ++++++++++++++------------------------ spec/mysql2/result_spec.rb | 12 ++++----- 2 files changed, 25 insertions(+), 38 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 58d761c0c..47a0a08ee 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -213,6 +213,21 @@ 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; + + 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); @@ -399,14 +414,7 @@ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, co case MYSQL_TYPE_NEWDATE: // MYSQL_TIME ts = (MYSQL_TIME*)result_buffer->buffer; if (args->castDateAsTime) { - val = rb_funcall(rb_cTime, args->db_timezone, 3, INT2NUM(ts->year), INT2NUM(ts->month), INT2NUM(ts->day)); - 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, 0, 0, 0, 0, args); } else { val = rb_funcall(cDate, intern_new, 3, INT2NUM(ts->year), INT2NUM(ts->month), INT2NUM(ts->day)); } @@ -444,14 +452,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; } @@ -633,14 +634,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); } } } @@ -663,14 +657,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const r val = Qnil; } else { if (args->castDateAsTime) { - val = rb_funcall(rb_cTime, args->db_timezone, 3, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day)); - 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, 0, 0, 0, 0, args); } else { val = rb_funcall(cDate, intern_new, 3, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day)); } diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index 945522921..9656f6781 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -312,17 +312,17 @@ 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') end - it "should return Time for a DATE value when :cast_dates_as_times is enabled" do - result = @client.query('SELECT date_test FROM mysql2_test', cast_dates_as_times: true).first - expect(result['date_test']).to be_an_instance_of(Time) - expect(result['date_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 00:00:00') - end - it "should raise an error given an invalid DATETIME" do expect { @client.query("SELECT CAST('1972-00-27 00:00:00' AS DATETIME) as bad_datetime").each }.to \ raise_error(Mysql2::Error, "Invalid date in field 'bad_datetime': 1972-00-27 00:00:00") From 9c9e0e6edd4a6c38314bc6fd479e79038a9c7ece Mon Sep 17 00:00:00 2001 From: Joe Holt Date: Tue, 30 Oct 2018 19:07:38 -0400 Subject: [PATCH 7/9] formatting --- ext/mysql2/result.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 47a0a08ee..749f56a1a 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -215,9 +215,7 @@ static unsigned int msec_char_to_uint(char *msec_char, size_t len) 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; - - val = rb_funcall(rb_cTime, args->db_timezone, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(minute), UINT2NUM(second), ULONG2NUM(second_part)); + 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); From a18ed737420879d2c539a1ed9f5dc755c39bd423 Mon Sep 17 00:00:00 2001 From: Joe Holt Date: Wed, 31 Oct 2018 13:35:30 -0400 Subject: [PATCH 8/9] rework a little to avoid conflicts with other branch --- ext/mysql2/result.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 749f56a1a..c0a2005c0 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -39,8 +39,9 @@ static VALUE opt_decimal_zero, opt_float_zero, opt_time_year, opt_time_month, op static ID intern_new, intern_utc, intern_local, intern_localtime, intern_local_offset, intern_civil, intern_new_offset, intern_merge, intern_BigDecimal; static VALUE sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, - sym_application_timezone, sym_local, sym_utc, sym_cast_booleans, sym_cast_dates_as_times, + 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) { @@ -817,7 +818,8 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) { result_each_args args; 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, castDateAsTime, cacheRows, cast; + int symbolizeKeys, asArray, castBool, cacheRows, cast; + int castDateAsTime; GET_RESULT(self); From 3a2b2c0bab5ffa8d531f4dff5726b153a9654b07 Mon Sep 17 00:00:00 2001 From: Joe Holt Date: Thu, 1 Nov 2018 11:32:21 -0400 Subject: [PATCH 9/9] added unit test for statements --- spec/mysql2/statement_spec.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spec/mysql2/statement_spec.rb b/spec/mysql2/statement_spec.rb index dbc185e6b..dd9c21d10 100644 --- a/spec/mysql2/statement_spec.rb +++ b/spec/mysql2/statement_spec.rb @@ -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')