From 98610b13d728e63f97db2cff1a25e091192df84a Mon Sep 17 00:00:00 2001 From: Remy Obein Date: Mon, 18 Dec 2017 14:23:16 +0000 Subject: [PATCH 01/11] Added a C implementation of the SimpleDecoder's timestamp decode functions when using ruby >= 2.3 - SimpleDecoder::TimestampWithTimeZone::decode - SimpleDecoder::TimestampWithoutTimeZone::decode --- ext/pg_text_decoder.c | 200 +++++++++++++++++++++++++++++++++++++++++ lib/pg/text_decoder.rb | 34 +++---- 2 files changed, 218 insertions(+), 16 deletions(-) diff --git a/ext/pg_text_decoder.c b/ext/pg_text_decoder.c index ab7663026..0f34c44be 100644 --- a/ext/pg_text_decoder.c +++ b/ext/pg_text_decoder.c @@ -28,12 +28,28 @@ * */ +#include "ruby/version.h" #include "pg.h" #include "util.h" #ifdef HAVE_INTTYPES_H #include #endif +#ifdef RUBY_API_VERSION_MAJOR +# if RUBY_API_VERSION_MAJOR > 2 || (RUBY_API_VERSION_MAJOR == 2 && RUBY_API_VERSION_MINOR >= 3) + /* use C implementation of the SimpleDecoder`s timestamp function + * when using ruby >= 2.3 */ +# define PG_TEXT_DECODER_TIMESTAMP_EXT +# endif +#endif + +#ifdef PG_TEXT_DECODER_TIMESTAMP_EXT +# include +# include +static VALUE rb_cPG_TimestampWithTimeZone; +static VALUE rb_cPG_TimestampWithoutTimeZone; +#endif + VALUE rb_mPG_TextDecoder; static ID s_id_decode; @@ -488,6 +504,180 @@ pg_text_dec_from_base64(t_pg_coder *conv, char *val, int len, int tuple, int fie return out_value; } +#ifdef PG_TEXT_DECODER_TIMESTAMP_EXT +static inline int char_to_digit(char c) +{ + return c - '0'; +} + +static int str4_to_int(const char *str) +{ + return char_to_digit(str[0]) * 1000 + + char_to_digit(str[1]) * 100 + + char_to_digit(str[2]) * 10 + + char_to_digit(str[3]); +} + +static int str2_to_int(const char *str) +{ + return char_to_digit(str[0]) * 10 + + char_to_digit(str[1]); +} + +static VALUE pg_text_decoder_timestamp_do(VALUE rstr, int with_timezone) +{ + const char *str = StringValuePtr(rstr); + + if (isdigit(str[0]) && isdigit(str[1]) && isdigit(str[2]) && isdigit(str[3]) + && str[4] == '-' + && isdigit(str[5]) && isdigit(str[6]) + && str[7] == '-' + && isdigit(str[8]) && isdigit(str[9]) + && str[10] == ' ' + && isdigit(str[11]) && isdigit(str[12]) + && str[13] == ':' + && isdigit(str[14]) && isdigit(str[15]) + && str[16] == ':' + && isdigit(str[17]) && isdigit(str[18]) + ) + { + struct tm tm; + int nsec = 0; + int tz_neg = 0; + int tz_hour = 0; + int tz_min = 0; + int tz_sec = 0; + + tm.tm_year = str4_to_int(&str[0]) - 1900; + tm.tm_mon = str2_to_int(&str[5]) - 1; + tm.tm_mday = str2_to_int(&str[8]); + tm.tm_hour = str2_to_int(&str[11]); + tm.tm_min = str2_to_int(&str[14]); + tm.tm_sec = str2_to_int(&str[17]); + tm.tm_isdst = 0; + str += 19; + + if (str[0] == '.' && isdigit(str[1])) + { + /* nano second part, up to 9 digits */ + static const int coef[9] = { + 100000000, 10000000, 1000000, + 100000, 10000, 1000, 100, 10, 1 + }; + int i; + + str++; + for (i = 0; i < 9 && isdigit(*str); i++) + { + nsec += coef[i] * char_to_digit(*str++); + } + } + + if (with_timezone) + { + if ((str[0] == '+' || str[0] == '-') && isdigit(str[1]) && isdigit(str[2])) + { + tz_neg = str[0] == '-'; + tz_hour = str2_to_int(&str[1]); + str += 3; + } + if (str[0] == ':') + { + str++; + } + if (isdigit(str[0]) && isdigit(str[1])) + { + tz_min = str2_to_int(str); + str += 2; + } + if (str[0] == ':') + { + str++; + } + if (isdigit(str[0]) && isdigit(str[1])) + { + tz_sec = str2_to_int(str); + str += 2; + } + } + if (*str != '\0') + { + // not consumed all the string + return rstr; + } + + if (with_timezone) + { +#ifdef _WIN32 + /* we can't use _mkgmtime because it is not available when using mingw32 */ + time_t time; + time_t prevTZ = _timezone; + _timezone = 0; + time = mktime(&tm); + _timezone = prevTZ; +#else + time_t time = timegm(&tm); +#endif + if (time != -1) + { + struct timespec ts; + int gmt_offset; + + gmt_offset = tz_hour * 3600 + tz_min * 60 + tz_sec; + if (tz_neg) + { + gmt_offset = - gmt_offset; + } + ts.tv_sec = time - gmt_offset; + ts.tv_nsec = nsec; + return rb_time_timespec_new(&ts, gmt_offset); + } + } + else + { + time_t time = mktime(&tm); + if (time != -1) + { + struct timespec ts; + + ts.tv_sec = time; + ts.tv_nsec = nsec; + return rb_time_timespec_new(&ts, 0); + } + } + } + return rstr; +} + +static VALUE +pg_text_dec_timestamp_with_time_zone(int argc, VALUE *argv, VALUE self) +{ + if (argc < 1 || argc > 3) + { + rb_raise(rb_eArgError, "wrong number of arguments (%i for 1..3)", argc); + } + if (NIL_P(argv[0])) + { + return Qnil; + } + return pg_text_decoder_timestamp_do(argv[0], 1); +} + +static VALUE +pg_text_dec_timestamp_without_time_zone(int argc, VALUE *argv, VALUE self) +{ + if (argc < 1 || argc > 3) + { + rb_raise(rb_eArgError, "wrong number of arguments (%i for 1..3)", argc); + } + if (NIL_P(argv[0])) + { + return Qnil; + } + return pg_text_decoder_timestamp_do(argv[0], 0); +} +#endif + void init_pg_text_decoder() { @@ -514,4 +704,14 @@ init_pg_text_decoder() pg_define_coder( "Array", pg_text_dec_array, rb_cPG_CompositeDecoder, rb_mPG_TextDecoder ); /* dummy = rb_define_class_under( rb_mPG_TextDecoder, "FromBase64", rb_cPG_CompositeDecoder ); */ pg_define_coder( "FromBase64", pg_text_dec_from_base64, rb_cPG_CompositeDecoder, rb_mPG_TextDecoder ); + +#ifdef PG_TEXT_DECODER_TIMESTAMP_EXT + /* Document-class: PG::TimestampWithTimeZone < PG::SimpleDecoder */ + rb_cPG_TimestampWithTimeZone = rb_define_class_under( rb_mPG_TextDecoder, "TimestampWithTimeZone", rb_cPG_SimpleDecoder ); + rb_define_method( rb_cPG_TimestampWithTimeZone, "decode", pg_text_dec_timestamp_with_time_zone, -1 ); + + /* Document-class: PG::TimestampWithoutTimeZone < PG::SimpleDecoder */ + rb_cPG_TimestampWithoutTimeZone = rb_define_class_under( rb_mPG_TextDecoder, "TimestampWithoutTimeZone", rb_cPG_SimpleDecoder ); + rb_define_method( rb_cPG_TimestampWithoutTimeZone, "decode", pg_text_dec_timestamp_without_time_zone, -1 ); +#endif } diff --git a/lib/pg/text_decoder.rb b/lib/pg/text_decoder.rb index 65e7ad5bc..686e184b6 100644 --- a/lib/pg/text_decoder.rb +++ b/lib/pg/text_decoder.rb @@ -17,26 +17,28 @@ def decode(string, tuple=nil, field=nil) end end - class TimestampWithoutTimeZone < SimpleDecoder - ISO_DATETIME_WITHOUT_TIMEZONE = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/ - - def decode(string, tuple=nil, field=nil) - if string =~ ISO_DATETIME_WITHOUT_TIMEZONE - Time.new $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, "#{$6}#{$7}".to_r - else - string + if RUBY_VERSION < '2.3.0' + class TimestampWithoutTimeZone < SimpleDecoder + ISO_DATETIME_WITHOUT_TIMEZONE = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/ + + def decode(string, tuple=nil, field=nil) + if string =~ ISO_DATETIME_WITHOUT_TIMEZONE + Time.new $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, "#{$6}#{$7}".to_r + else + string + end end end - end - class TimestampWithTimeZone < SimpleDecoder - ISO_DATETIME_WITH_TIMEZONE = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?([-\+]\d\d):?(\d\d)?:?(\d\d)?\z/ + class TimestampWithTimeZone < SimpleDecoder + ISO_DATETIME_WITH_TIMEZONE = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?([-\+]\d\d):?(\d\d)?:?(\d\d)?\z/ - def decode(string, tuple=nil, field=nil) - if string =~ ISO_DATETIME_WITH_TIMEZONE - Time.new $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, "#{$6}#{$7}".to_r, "#{$8}:#{$9 || '00'}:#{$10 || '00'}" - else - string + def decode(string, tuple=nil, field=nil) + if string =~ ISO_DATETIME_WITH_TIMEZONE + Time.new $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, "#{$6}#{$7}".to_r, "#{$8}:#{$9 || '00'}:#{$10 || '00'}" + else + string + end end end end From cb361580cc761e6c15bcdb17aa80d731bf51eff1 Mon Sep 17 00:00:00 2001 From: Remy Obein Date: Mon, 8 Jan 2018 16:30:56 +0000 Subject: [PATCH 02/11] Use pg_define_coder to define SimpleDecoder's timestamp decode function. --- ext/pg_text_decoder.c | 112 ++++++++++++++++-------------------------- 1 file changed, 43 insertions(+), 69 deletions(-) diff --git a/ext/pg_text_decoder.c b/ext/pg_text_decoder.c index 0f34c44be..f73bf2817 100644 --- a/ext/pg_text_decoder.c +++ b/ext/pg_text_decoder.c @@ -46,8 +46,6 @@ #ifdef PG_TEXT_DECODER_TIMESTAMP_EXT # include # include -static VALUE rb_cPG_TimestampWithTimeZone; -static VALUE rb_cPG_TimestampWithoutTimeZone; #endif VALUE rb_mPG_TextDecoder; @@ -524,10 +522,8 @@ static int str2_to_int(const char *str) + char_to_digit(str[1]); } -static VALUE pg_text_decoder_timestamp_do(VALUE rstr, int with_timezone) +static VALUE pg_text_decoder_timestamp_do(const char *str, int len, int tuple, int field, int with_timezone) { - const char *str = StringValuePtr(rstr); - if (isdigit(str[0]) && isdigit(str[1]) && isdigit(str[2]) && isdigit(str[3]) && str[4] == '-' && isdigit(str[5]) && isdigit(str[6]) @@ -600,81 +596,63 @@ static VALUE pg_text_decoder_timestamp_do(VALUE rstr, int with_timezone) str += 2; } } - if (*str != '\0') - { - // not consumed all the string - return rstr; - } - - if (with_timezone) + if (*str == '\0') { + // must have consumed all the string + if (with_timezone) + { #ifdef _WIN32 - /* we can't use _mkgmtime because it is not available when using mingw32 */ - time_t time; - time_t prevTZ = _timezone; - _timezone = 0; - time = mktime(&tm); - _timezone = prevTZ; + /* we can't use _mkgmtime because it is not available when using mingw32 */ + time_t time; + time_t prevTZ = _timezone; + _timezone = 0; + time = mktime(&tm); + _timezone = prevTZ; #else - time_t time = timegm(&tm); + time_t time = timegm(&tm); #endif - if (time != -1) - { - struct timespec ts; - int gmt_offset; - - gmt_offset = tz_hour * 3600 + tz_min * 60 + tz_sec; - if (tz_neg) + if (time != -1) { - gmt_offset = - gmt_offset; + struct timespec ts; + int gmt_offset; + + gmt_offset = tz_hour * 3600 + tz_min * 60 + tz_sec; + if (tz_neg) + { + gmt_offset = - gmt_offset; + } + ts.tv_sec = time - gmt_offset; + ts.tv_nsec = nsec; + return rb_time_timespec_new(&ts, gmt_offset); } - ts.tv_sec = time - gmt_offset; - ts.tv_nsec = nsec; - return rb_time_timespec_new(&ts, gmt_offset); } - } - else - { - time_t time = mktime(&tm); - if (time != -1) + else { - struct timespec ts; + time_t time = mktime(&tm); + if (time != -1) + { + struct timespec ts; - ts.tv_sec = time; - ts.tv_nsec = nsec; - return rb_time_timespec_new(&ts, 0); + ts.tv_sec = time; + ts.tv_nsec = nsec; + return rb_time_timespec_new(&ts, 0); + } } } } - return rstr; + rb_raise( rb_eTypeError, "wrong data for %s converter in tuple %d field %d length %d", with_timezone ? "TimestampWithTimeZone" : "TimestampWithoutTimeZOne", tuple, field, len); } static VALUE -pg_text_dec_timestamp_with_time_zone(int argc, VALUE *argv, VALUE self) +pg_text_dec_timestamp_with_time_zone(t_pg_coder *conv, char *val, int len, int tuple, int field, int enc_idx) { - if (argc < 1 || argc > 3) - { - rb_raise(rb_eArgError, "wrong number of arguments (%i for 1..3)", argc); - } - if (NIL_P(argv[0])) - { - return Qnil; - } - return pg_text_decoder_timestamp_do(argv[0], 1); + return pg_text_decoder_timestamp_do(val, len, tuple, field, 1); } static VALUE -pg_text_dec_timestamp_without_time_zone(int argc, VALUE *argv, VALUE self) +pg_text_dec_timestamp_without_time_zone(t_pg_coder *conv, char *val, int len, int tuple, int field, int enc_idx) { - if (argc < 1 || argc > 3) - { - rb_raise(rb_eArgError, "wrong number of arguments (%i for 1..3)", argc); - } - if (NIL_P(argv[0])) - { - return Qnil; - } - return pg_text_decoder_timestamp_do(argv[0], 0); + return pg_text_decoder_timestamp_do(val, len, tuple, field, 0); } #endif @@ -705,13 +683,9 @@ init_pg_text_decoder() /* dummy = rb_define_class_under( rb_mPG_TextDecoder, "FromBase64", rb_cPG_CompositeDecoder ); */ pg_define_coder( "FromBase64", pg_text_dec_from_base64, rb_cPG_CompositeDecoder, rb_mPG_TextDecoder ); -#ifdef PG_TEXT_DECODER_TIMESTAMP_EXT - /* Document-class: PG::TimestampWithTimeZone < PG::SimpleDecoder */ - rb_cPG_TimestampWithTimeZone = rb_define_class_under( rb_mPG_TextDecoder, "TimestampWithTimeZone", rb_cPG_SimpleDecoder ); - rb_define_method( rb_cPG_TimestampWithTimeZone, "decode", pg_text_dec_timestamp_with_time_zone, -1 ); - - /* Document-class: PG::TimestampWithoutTimeZone < PG::SimpleDecoder */ - rb_cPG_TimestampWithoutTimeZone = rb_define_class_under( rb_mPG_TextDecoder, "TimestampWithoutTimeZone", rb_cPG_SimpleDecoder ); - rb_define_method( rb_cPG_TimestampWithoutTimeZone, "decode", pg_text_dec_timestamp_without_time_zone, -1 ); -#endif + /* dummy = rb_define_class_under( rb_mPG_TextDecoder, "TimestampWithTimeZone", rb_cPG_SimpleDecoder ); */ + pg_define_coder( "TimestampWithTimeZone", pg_text_dec_timestamp_with_time_zone, rb_cPG_SimpleDecoder, rb_mPG_TextDecoder); + + /* dummy = rb_define_class_under( rb_mPG_TextDecoder, "TimestampWithoutTimeZone", rb_cPG_SimpleDecoder ); */ + pg_define_coder( "TimestampWithoutTimeZone", pg_text_dec_timestamp_without_time_zone, rb_cPG_SimpleDecoder, rb_mPG_TextDecoder); } From 2c260949f004fd9c1b94c33bd5198e369dba99ba Mon Sep 17 00:00:00 2001 From: Remy Obein Date: Mon, 8 Jan 2018 17:47:23 +0000 Subject: [PATCH 03/11] SimpleDecoder's timestamp decode function return the original string when the string can't be parsed. --- ext/pg_text_decoder.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ext/pg_text_decoder.c b/ext/pg_text_decoder.c index f73bf2817..6f0da85d8 100644 --- a/ext/pg_text_decoder.c +++ b/ext/pg_text_decoder.c @@ -524,6 +524,8 @@ static int str2_to_int(const char *str) static VALUE pg_text_decoder_timestamp_do(const char *str, int len, int tuple, int field, int with_timezone) { + const char *rstr = str; + if (isdigit(str[0]) && isdigit(str[1]) && isdigit(str[2]) && isdigit(str[3]) && str[4] == '-' && isdigit(str[5]) && isdigit(str[6]) @@ -640,7 +642,7 @@ static VALUE pg_text_decoder_timestamp_do(const char *str, int len, int tuple, i } } } - rb_raise( rb_eTypeError, "wrong data for %s converter in tuple %d field %d length %d", with_timezone ? "TimestampWithTimeZone" : "TimestampWithoutTimeZOne", tuple, field, len); + return rb_tainted_str_new(rstr, len); } static VALUE From 9cf1c29331629a365be385b3bf55f6724257d8c1 Mon Sep 17 00:00:00 2001 From: Remy Obein Date: Mon, 8 Jan 2018 18:58:10 +0000 Subject: [PATCH 04/11] Use rb_funcall to Time.new as fallback for Ruby < 2.3 --- ext/pg_text_decoder.c | 78 +++++++++++++++++++++++++++++------------- lib/pg/text_decoder.rb | 26 -------------- 2 files changed, 55 insertions(+), 49 deletions(-) diff --git a/ext/pg_text_decoder.c b/ext/pg_text_decoder.c index 6f0da85d8..4634d5817 100644 --- a/ext/pg_text_decoder.c +++ b/ext/pg_text_decoder.c @@ -34,23 +34,13 @@ #ifdef HAVE_INTTYPES_H #include #endif - -#ifdef RUBY_API_VERSION_MAJOR -# if RUBY_API_VERSION_MAJOR > 2 || (RUBY_API_VERSION_MAJOR == 2 && RUBY_API_VERSION_MINOR >= 3) - /* use C implementation of the SimpleDecoder`s timestamp function - * when using ruby >= 2.3 */ -# define PG_TEXT_DECODER_TIMESTAMP_EXT -# endif -#endif - -#ifdef PG_TEXT_DECODER_TIMESTAMP_EXT -# include -# include -#endif +#include +#include VALUE rb_mPG_TextDecoder; static ID s_id_decode; +extern VALUE rb_cTime; /* * Document-class: PG::TextDecoder::Boolean < PG::SimpleDecoder @@ -502,7 +492,6 @@ pg_text_dec_from_base64(t_pg_coder *conv, char *val, int len, int tuple, int fie return out_value; } -#ifdef PG_TEXT_DECODER_TIMESTAMP_EXT static inline int char_to_digit(char c) { return c - '0'; @@ -539,20 +528,20 @@ static VALUE pg_text_decoder_timestamp_do(const char *str, int len, int tuple, i && isdigit(str[17]) && isdigit(str[18]) ) { - struct tm tm; + int year, mon, day; + int hour, min, sec; int nsec = 0; int tz_neg = 0; int tz_hour = 0; int tz_min = 0; int tz_sec = 0; - tm.tm_year = str4_to_int(&str[0]) - 1900; - tm.tm_mon = str2_to_int(&str[5]) - 1; - tm.tm_mday = str2_to_int(&str[8]); - tm.tm_hour = str2_to_int(&str[11]); - tm.tm_min = str2_to_int(&str[14]); - tm.tm_sec = str2_to_int(&str[17]); - tm.tm_isdst = 0; + year = str4_to_int(&str[0]); + mon = str2_to_int(&str[5]); + day = str2_to_int(&str[8]); + hour = str2_to_int(&str[11]); + min = str2_to_int(&str[14]); + sec = str2_to_int(&str[17]); str += 19; if (str[0] == '.' && isdigit(str[1])) @@ -600,7 +589,17 @@ static VALUE pg_text_decoder_timestamp_do(const char *str, int len, int tuple, i } if (*str == '\0') { +#if RUBY_API_VERSION_MAJOR > 2 || (RUBY_API_VERSION_MAJOR == 2 && RUBY_API_VERSION_MINOR >= 3) // must have consumed all the string + struct tm tm; + tm.tm_year = year - 1900; + tm.tm_mon = mon - 1; + tm.tm_mday = day; + tm.tm_hour = hour; + tm.tm_min = min; + tm.tm_sec = sec; + tm.tm_isdst = 0; + if (with_timezone) { #ifdef _WIN32 @@ -640,6 +639,40 @@ static VALUE pg_text_decoder_timestamp_do(const char *str, int len, int tuple, i return rb_time_timespec_new(&ts, 0); } } +#else + VALUE sec_value; + VALUE gmt_offset_value = Qnil; + if (nsec) + { + int sec_numerator = sec * 1000000 + nsec / 1000; + int sec_denominator = 1000000; + sec_value = rb_funcall(Qnil, rb_intern("Rational"), 2, + INT2NUM(sec_numerator), INT2NUM(sec_denominator)); + } + else + { + sec_value = INT2NUM(sec); + } + if (with_timezone) + { + int gmt_offset; + + gmt_offset = tz_hour * 3600 + tz_min * 60 + tz_sec; + if (tz_neg) + { + gmt_offset = - gmt_offset; + } + gmt_offset_value = INT2NUM(gmt_offset); + } + return rb_funcall(rb_cTime, rb_intern("new"), 7, + INT2NUM(year), + INT2NUM(mon), + INT2NUM(day), + INT2NUM(hour), + INT2NUM(min), + sec_value, + gmt_offset_value); +#endif } } return rb_tainted_str_new(rstr, len); @@ -656,7 +689,6 @@ pg_text_dec_timestamp_without_time_zone(t_pg_coder *conv, char *val, int len, in { return pg_text_decoder_timestamp_do(val, len, tuple, field, 0); } -#endif void init_pg_text_decoder() diff --git a/lib/pg/text_decoder.rb b/lib/pg/text_decoder.rb index 686e184b6..ac7870240 100644 --- a/lib/pg/text_decoder.rb +++ b/lib/pg/text_decoder.rb @@ -17,32 +17,6 @@ def decode(string, tuple=nil, field=nil) end end - if RUBY_VERSION < '2.3.0' - class TimestampWithoutTimeZone < SimpleDecoder - ISO_DATETIME_WITHOUT_TIMEZONE = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/ - - def decode(string, tuple=nil, field=nil) - if string =~ ISO_DATETIME_WITHOUT_TIMEZONE - Time.new $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, "#{$6}#{$7}".to_r - else - string - end - end - end - - class TimestampWithTimeZone < SimpleDecoder - ISO_DATETIME_WITH_TIMEZONE = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?([-\+]\d\d):?(\d\d)?:?(\d\d)?\z/ - - def decode(string, tuple=nil, field=nil) - if string =~ ISO_DATETIME_WITH_TIMEZONE - Time.new $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, "#{$6}#{$7}".to_r, "#{$8}:#{$9 || '00'}:#{$10 || '00'}" - else - string - end - end - end - end - class JSON < SimpleDecoder def decode(string, tuple=nil, field=nil) ::JSON.parse(string, quirks_mode: true) From 97d8440cf56b248b88c387b11f329e01d4b725d8 Mon Sep 17 00:00:00 2001 From: Remy Obein Date: Mon, 8 Jan 2018 23:12:18 +0000 Subject: [PATCH 05/11] Use pg_text_dec_string as fallback when a timestamp can't be parsed --- ext/pg_text_decoder.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ext/pg_text_decoder.c b/ext/pg_text_decoder.c index 4634d5817..29a6155d4 100644 --- a/ext/pg_text_decoder.c +++ b/ext/pg_text_decoder.c @@ -511,9 +511,9 @@ static int str2_to_int(const char *str) + char_to_digit(str[1]); } -static VALUE pg_text_decoder_timestamp_do(const char *str, int len, int tuple, int field, int with_timezone) +static VALUE pg_text_decoder_timestamp_do(t_pg_coder *conv, char *val, int len, int tuple, int field, int enc_idx, int with_timezone) { - const char *rstr = str; + const char *str = val; if (isdigit(str[0]) && isdigit(str[1]) && isdigit(str[2]) && isdigit(str[3]) && str[4] == '-' @@ -675,19 +675,19 @@ static VALUE pg_text_decoder_timestamp_do(const char *str, int len, int tuple, i #endif } } - return rb_tainted_str_new(rstr, len); + return pg_text_dec_string(conv, val, len, tuple, field, enc_idx); } static VALUE pg_text_dec_timestamp_with_time_zone(t_pg_coder *conv, char *val, int len, int tuple, int field, int enc_idx) { - return pg_text_decoder_timestamp_do(val, len, tuple, field, 1); + return pg_text_decoder_timestamp_do(conv, val, len, tuple, field, enc_idx, 1); } static VALUE pg_text_dec_timestamp_without_time_zone(t_pg_coder *conv, char *val, int len, int tuple, int field, int enc_idx) { - return pg_text_decoder_timestamp_do(val, len, tuple, field, 0); + return pg_text_decoder_timestamp_do(conv, val, len, tuple, field, enc_idx, 0); } void From 5c87b38d62b1c210d3da09dda63befc3a2eafe55 Mon Sep 17 00:00:00 2001 From: Lars Kanis Date: Tue, 9 Jan 2018 21:39:36 +0100 Subject: [PATCH 06/11] Timestamp decoder: - Don't fail when the time is more precise than processed - Shorten some expect-series. - Change Spaces->Tabs. - Add a test case for leap-seconds. --- ext/pg_text_decoder.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ext/pg_text_decoder.c b/ext/pg_text_decoder.c index 29a6155d4..9ef1dfbce 100644 --- a/ext/pg_text_decoder.c +++ b/ext/pg_text_decoder.c @@ -535,7 +535,7 @@ static VALUE pg_text_decoder_timestamp_do(t_pg_coder *conv, char *val, int len, int tz_hour = 0; int tz_min = 0; int tz_sec = 0; - + year = str4_to_int(&str[0]); mon = str2_to_int(&str[5]); day = str2_to_int(&str[8]); @@ -543,7 +543,7 @@ static VALUE pg_text_decoder_timestamp_do(t_pg_coder *conv, char *val, int len, min = str2_to_int(&str[14]); sec = str2_to_int(&str[17]); str += 19; - + if (str[0] == '.' && isdigit(str[1])) { /* nano second part, up to 9 digits */ @@ -558,6 +558,8 @@ static VALUE pg_text_decoder_timestamp_do(t_pg_coder *conv, char *val, int len, { nsec += coef[i] * char_to_digit(*str++); } + /* consume digits smaller than nsec */ + while(isdigit(*str)) str++; } if (with_timezone) From 2d273d468f2c9947af1e772c60988c70f25815af Mon Sep 17 00:00:00 2001 From: Lars Kanis Date: Tue, 9 Jan 2018 22:11:43 +0100 Subject: [PATCH 07/11] Remove unnecessary extern declaration --- ext/pg_text_decoder.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/ext/pg_text_decoder.c b/ext/pg_text_decoder.c index 9ef1dfbce..fd1dab258 100644 --- a/ext/pg_text_decoder.c +++ b/ext/pg_text_decoder.c @@ -40,8 +40,6 @@ VALUE rb_mPG_TextDecoder; static ID s_id_decode; -extern VALUE rb_cTime; - /* * Document-class: PG::TextDecoder::Boolean < PG::SimpleDecoder * From be07045d3eae80bf985b179932112551e9155111 Mon Sep 17 00:00:00 2001 From: Lars Kanis Date: Thu, 11 Jan 2018 15:07:31 +0100 Subject: [PATCH 08/11] Timestamp-Decoder: Use Time.new on Windows instead of rb_time_timespec_new() Both _mkgmtime() as well as mktime() return errors with certain dates. This path is approx. 10 times slower, compared with rb_time_timespec_new(). Its still 10 times faster than the previous RegExp way. --- ext/pg_text_decoder.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/ext/pg_text_decoder.c b/ext/pg_text_decoder.c index fd1dab258..f898e53a7 100644 --- a/ext/pg_text_decoder.c +++ b/ext/pg_text_decoder.c @@ -589,7 +589,7 @@ static VALUE pg_text_decoder_timestamp_do(t_pg_coder *conv, char *val, int len, } if (*str == '\0') { -#if RUBY_API_VERSION_MAJOR > 2 || (RUBY_API_VERSION_MAJOR == 2 && RUBY_API_VERSION_MINOR >= 3) +#if RUBY_API_VERSION_MAJOR > 2 || (RUBY_API_VERSION_MAJOR == 2 && RUBY_API_VERSION_MINOR >= 3) && !defined(_WIN32) // must have consumed all the string struct tm tm; tm.tm_year = year - 1900; @@ -602,16 +602,7 @@ static VALUE pg_text_decoder_timestamp_do(t_pg_coder *conv, char *val, int len, if (with_timezone) { -#ifdef _WIN32 - /* we can't use _mkgmtime because it is not available when using mingw32 */ - time_t time; - time_t prevTZ = _timezone; - _timezone = 0; - time = mktime(&tm); - _timezone = prevTZ; -#else time_t time = timegm(&tm); -#endif if (time != -1) { struct timespec ts; From 4ba476d1da1a86c0534e171a8d66da7bff516edc Mon Sep 17 00:00:00 2001 From: Lars Kanis Date: Thu, 26 Apr 2018 21:38:14 +0200 Subject: [PATCH 09/11] Move symbol lookup for timestamp to initialization --- ext/pg_text_decoder.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ext/pg_text_decoder.c b/ext/pg_text_decoder.c index f898e53a7..6f4bc12a8 100644 --- a/ext/pg_text_decoder.c +++ b/ext/pg_text_decoder.c @@ -39,6 +39,8 @@ VALUE rb_mPG_TextDecoder; static ID s_id_decode; +static ID s_id_Rational; +static ID s_id_new; /* * Document-class: PG::TextDecoder::Boolean < PG::SimpleDecoder @@ -637,7 +639,7 @@ static VALUE pg_text_decoder_timestamp_do(t_pg_coder *conv, char *val, int len, { int sec_numerator = sec * 1000000 + nsec / 1000; int sec_denominator = 1000000; - sec_value = rb_funcall(Qnil, rb_intern("Rational"), 2, + sec_value = rb_funcall(Qnil, s_id_Rational, 2, INT2NUM(sec_numerator), INT2NUM(sec_denominator)); } else @@ -655,7 +657,7 @@ static VALUE pg_text_decoder_timestamp_do(t_pg_coder *conv, char *val, int len, } gmt_offset_value = INT2NUM(gmt_offset); } - return rb_funcall(rb_cTime, rb_intern("new"), 7, + return rb_funcall(rb_cTime, s_id_new, 7, INT2NUM(year), INT2NUM(mon), INT2NUM(day), @@ -685,6 +687,8 @@ void init_pg_text_decoder() { s_id_decode = rb_intern("decode"); + s_id_Rational = rb_intern("Rational"); + s_id_new = rb_intern("new"); /* This module encapsulates all decoder classes with text input format */ rb_mPG_TextDecoder = rb_define_module_under( rb_mPG, "TextDecoder" ); From 450c0750db50a0460b49fd37397f3cd5abaf6885 Mon Sep 17 00:00:00 2001 From: Lars Kanis Date: Mon, 30 Apr 2018 15:13:54 +0200 Subject: [PATCH 10/11] TextDecoder: change tabs to spaces like in the rest of the file --- ext/pg_text_decoder.c | 332 +++++++++++++++++++++--------------------- 1 file changed, 166 insertions(+), 166 deletions(-) diff --git a/ext/pg_text_decoder.c b/ext/pg_text_decoder.c index 6f4bc12a8..42250df81 100644 --- a/ext/pg_text_decoder.c +++ b/ext/pg_text_decoder.c @@ -494,193 +494,193 @@ pg_text_dec_from_base64(t_pg_coder *conv, char *val, int len, int tuple, int fie static inline int char_to_digit(char c) { - return c - '0'; + return c - '0'; } static int str4_to_int(const char *str) { - return char_to_digit(str[0]) * 1000 - + char_to_digit(str[1]) * 100 - + char_to_digit(str[2]) * 10 - + char_to_digit(str[3]); + return char_to_digit(str[0]) * 1000 + + char_to_digit(str[1]) * 100 + + char_to_digit(str[2]) * 10 + + char_to_digit(str[3]); } static int str2_to_int(const char *str) { - return char_to_digit(str[0]) * 10 - + char_to_digit(str[1]); + return char_to_digit(str[0]) * 10 + + char_to_digit(str[1]); } static VALUE pg_text_decoder_timestamp_do(t_pg_coder *conv, char *val, int len, int tuple, int field, int enc_idx, int with_timezone) { - const char *str = val; - - if (isdigit(str[0]) && isdigit(str[1]) && isdigit(str[2]) && isdigit(str[3]) - && str[4] == '-' - && isdigit(str[5]) && isdigit(str[6]) - && str[7] == '-' - && isdigit(str[8]) && isdigit(str[9]) - && str[10] == ' ' - && isdigit(str[11]) && isdigit(str[12]) - && str[13] == ':' - && isdigit(str[14]) && isdigit(str[15]) - && str[16] == ':' - && isdigit(str[17]) && isdigit(str[18]) - ) - { - int year, mon, day; - int hour, min, sec; - int nsec = 0; - int tz_neg = 0; - int tz_hour = 0; - int tz_min = 0; - int tz_sec = 0; - - year = str4_to_int(&str[0]); - mon = str2_to_int(&str[5]); - day = str2_to_int(&str[8]); - hour = str2_to_int(&str[11]); - min = str2_to_int(&str[14]); - sec = str2_to_int(&str[17]); - str += 19; - - if (str[0] == '.' && isdigit(str[1])) - { - /* nano second part, up to 9 digits */ - static const int coef[9] = { - 100000000, 10000000, 1000000, - 100000, 10000, 1000, 100, 10, 1 - }; - int i; - - str++; - for (i = 0; i < 9 && isdigit(*str); i++) - { - nsec += coef[i] * char_to_digit(*str++); - } - /* consume digits smaller than nsec */ - while(isdigit(*str)) str++; - } - - if (with_timezone) - { - if ((str[0] == '+' || str[0] == '-') && isdigit(str[1]) && isdigit(str[2])) - { - tz_neg = str[0] == '-'; - tz_hour = str2_to_int(&str[1]); - str += 3; - } - if (str[0] == ':') - { - str++; - } - if (isdigit(str[0]) && isdigit(str[1])) - { - tz_min = str2_to_int(str); - str += 2; - } - if (str[0] == ':') - { - str++; - } - if (isdigit(str[0]) && isdigit(str[1])) - { - tz_sec = str2_to_int(str); - str += 2; - } - } - if (*str == '\0') - { + const char *str = val; + + if (isdigit(str[0]) && isdigit(str[1]) && isdigit(str[2]) && isdigit(str[3]) + && str[4] == '-' + && isdigit(str[5]) && isdigit(str[6]) + && str[7] == '-' + && isdigit(str[8]) && isdigit(str[9]) + && str[10] == ' ' + && isdigit(str[11]) && isdigit(str[12]) + && str[13] == ':' + && isdigit(str[14]) && isdigit(str[15]) + && str[16] == ':' + && isdigit(str[17]) && isdigit(str[18]) + ) + { + int year, mon, day; + int hour, min, sec; + int nsec = 0; + int tz_neg = 0; + int tz_hour = 0; + int tz_min = 0; + int tz_sec = 0; + + year = str4_to_int(&str[0]); + mon = str2_to_int(&str[5]); + day = str2_to_int(&str[8]); + hour = str2_to_int(&str[11]); + min = str2_to_int(&str[14]); + sec = str2_to_int(&str[17]); + str += 19; + + if (str[0] == '.' && isdigit(str[1])) + { + /* nano second part, up to 9 digits */ + static const int coef[9] = { + 100000000, 10000000, 1000000, + 100000, 10000, 1000, 100, 10, 1 + }; + int i; + + str++; + for (i = 0; i < 9 && isdigit(*str); i++) + { + nsec += coef[i] * char_to_digit(*str++); + } + /* consume digits smaller than nsec */ + while(isdigit(*str)) str++; + } + + if (with_timezone) + { + if ((str[0] == '+' || str[0] == '-') && isdigit(str[1]) && isdigit(str[2])) + { + tz_neg = str[0] == '-'; + tz_hour = str2_to_int(&str[1]); + str += 3; + } + if (str[0] == ':') + { + str++; + } + if (isdigit(str[0]) && isdigit(str[1])) + { + tz_min = str2_to_int(str); + str += 2; + } + if (str[0] == ':') + { + str++; + } + if (isdigit(str[0]) && isdigit(str[1])) + { + tz_sec = str2_to_int(str); + str += 2; + } + } + if (*str == '\0') + { #if RUBY_API_VERSION_MAJOR > 2 || (RUBY_API_VERSION_MAJOR == 2 && RUBY_API_VERSION_MINOR >= 3) && !defined(_WIN32) - // must have consumed all the string - struct tm tm; - tm.tm_year = year - 1900; - tm.tm_mon = mon - 1; - tm.tm_mday = day; - tm.tm_hour = hour; - tm.tm_min = min; - tm.tm_sec = sec; - tm.tm_isdst = 0; - - if (with_timezone) - { - time_t time = timegm(&tm); - if (time != -1) - { - struct timespec ts; - int gmt_offset; - - gmt_offset = tz_hour * 3600 + tz_min * 60 + tz_sec; - if (tz_neg) - { - gmt_offset = - gmt_offset; - } - ts.tv_sec = time - gmt_offset; - ts.tv_nsec = nsec; - return rb_time_timespec_new(&ts, gmt_offset); - } - } - else - { - time_t time = mktime(&tm); - if (time != -1) - { - struct timespec ts; - - ts.tv_sec = time; - ts.tv_nsec = nsec; - return rb_time_timespec_new(&ts, 0); - } - } + // must have consumed all the string + struct tm tm; + tm.tm_year = year - 1900; + tm.tm_mon = mon - 1; + tm.tm_mday = day; + tm.tm_hour = hour; + tm.tm_min = min; + tm.tm_sec = sec; + tm.tm_isdst = 0; + + if (with_timezone) + { + time_t time = timegm(&tm); + if (time != -1) + { + struct timespec ts; + int gmt_offset; + + gmt_offset = tz_hour * 3600 + tz_min * 60 + tz_sec; + if (tz_neg) + { + gmt_offset = - gmt_offset; + } + ts.tv_sec = time - gmt_offset; + ts.tv_nsec = nsec; + return rb_time_timespec_new(&ts, gmt_offset); + } + } + else + { + time_t time = mktime(&tm); + if (time != -1) + { + struct timespec ts; + + ts.tv_sec = time; + ts.tv_nsec = nsec; + return rb_time_timespec_new(&ts, 0); + } + } #else - VALUE sec_value; - VALUE gmt_offset_value = Qnil; - if (nsec) - { - int sec_numerator = sec * 1000000 + nsec / 1000; - int sec_denominator = 1000000; - sec_value = rb_funcall(Qnil, s_id_Rational, 2, - INT2NUM(sec_numerator), INT2NUM(sec_denominator)); - } - else - { - sec_value = INT2NUM(sec); - } - if (with_timezone) - { - int gmt_offset; - - gmt_offset = tz_hour * 3600 + tz_min * 60 + tz_sec; - if (tz_neg) - { - gmt_offset = - gmt_offset; - } - gmt_offset_value = INT2NUM(gmt_offset); - } - return rb_funcall(rb_cTime, s_id_new, 7, - INT2NUM(year), - INT2NUM(mon), - INT2NUM(day), - INT2NUM(hour), - INT2NUM(min), - sec_value, - gmt_offset_value); + VALUE sec_value; + VALUE gmt_offset_value = Qnil; + if (nsec) + { + int sec_numerator = sec * 1000000 + nsec / 1000; + int sec_denominator = 1000000; + sec_value = rb_funcall(Qnil, s_id_Rational, 2, + INT2NUM(sec_numerator), INT2NUM(sec_denominator)); + } + else + { + sec_value = INT2NUM(sec); + } + if (with_timezone) + { + int gmt_offset; + + gmt_offset = tz_hour * 3600 + tz_min * 60 + tz_sec; + if (tz_neg) + { + gmt_offset = - gmt_offset; + } + gmt_offset_value = INT2NUM(gmt_offset); + } + return rb_funcall(rb_cTime, s_id_new, 7, + INT2NUM(year), + INT2NUM(mon), + INT2NUM(day), + INT2NUM(hour), + INT2NUM(min), + sec_value, + gmt_offset_value); #endif - } - } - return pg_text_dec_string(conv, val, len, tuple, field, enc_idx); + } + } + return pg_text_dec_string(conv, val, len, tuple, field, enc_idx); } static VALUE pg_text_dec_timestamp_with_time_zone(t_pg_coder *conv, char *val, int len, int tuple, int field, int enc_idx) { - return pg_text_decoder_timestamp_do(conv, val, len, tuple, field, enc_idx, 1); + return pg_text_decoder_timestamp_do(conv, val, len, tuple, field, enc_idx, 1); } static VALUE pg_text_dec_timestamp_without_time_zone(t_pg_coder *conv, char *val, int len, int tuple, int field, int enc_idx) { - return pg_text_decoder_timestamp_do(conv, val, len, tuple, field, enc_idx, 0); + return pg_text_decoder_timestamp_do(conv, val, len, tuple, field, enc_idx, 0); } void @@ -712,9 +712,9 @@ init_pg_text_decoder() /* dummy = rb_define_class_under( rb_mPG_TextDecoder, "FromBase64", rb_cPG_CompositeDecoder ); */ pg_define_coder( "FromBase64", pg_text_dec_from_base64, rb_cPG_CompositeDecoder, rb_mPG_TextDecoder ); - /* dummy = rb_define_class_under( rb_mPG_TextDecoder, "TimestampWithTimeZone", rb_cPG_SimpleDecoder ); */ - pg_define_coder( "TimestampWithTimeZone", pg_text_dec_timestamp_with_time_zone, rb_cPG_SimpleDecoder, rb_mPG_TextDecoder); + /* dummy = rb_define_class_under( rb_mPG_TextDecoder, "TimestampWithTimeZone", rb_cPG_SimpleDecoder ); */ + pg_define_coder( "TimestampWithTimeZone", pg_text_dec_timestamp_with_time_zone, rb_cPG_SimpleDecoder, rb_mPG_TextDecoder); - /* dummy = rb_define_class_under( rb_mPG_TextDecoder, "TimestampWithoutTimeZone", rb_cPG_SimpleDecoder ); */ - pg_define_coder( "TimestampWithoutTimeZone", pg_text_dec_timestamp_without_time_zone, rb_cPG_SimpleDecoder, rb_mPG_TextDecoder); + /* dummy = rb_define_class_under( rb_mPG_TextDecoder, "TimestampWithoutTimeZone", rb_cPG_SimpleDecoder ); */ + pg_define_coder( "TimestampWithoutTimeZone", pg_text_dec_timestamp_without_time_zone, rb_cPG_SimpleDecoder, rb_mPG_TextDecoder); } From 7aac3ec40e574775005c36087c4db9523832808c Mon Sep 17 00:00:00 2001 From: Lars Kanis Date: Thu, 26 Apr 2018 13:59:38 +0200 Subject: [PATCH 11/11] TextDecoder: better test for timegm() - it's a non-std GNU extension Also fall through to the slow path, because it fails on Android (using termux) with certain time values otherwise: Failures: 1) PG::Type derivations PG::SimpleCoder#decode timestamps decodes timestamps with date before 1823 Failure/Error: expect( textdec_timestamp.decode('1822-01-02 23:23:59.123456') ). to be_within(0.000001).of( Time.new(1822,01,02, 23, 23, 59.123456) ) expected "1822-01-02 23:23:59.123456" to be within 1.0e-06 of 1822-01-02 23:23:59 +0100, but it could not be treated as a numeric value # ./spec/pg/type_spec.rb:125:in `block (5 levels) in ' # ./spec/helpers.rb:31:in `block in included' 2) PG::Type derivations PG::SimpleCoder#decode timestamps decodes timestamps with date after 2116 Failure/Error: expect( textdec_timestamp.decode('2117-01-02 23:23:59.123456') ). to be_within(0.000001).of( Time.new(2117,01,02, 23, 23, 59.123456) ) expected "2117-01-02 23:23:59.123456" to be within 1.0e-06 of 2117-01-02 23:23:59 +0100, but it could not be treated as a numeric value # ./spec/pg/type_spec.rb:129:in `block (5 levels) in ' # ./spec/helpers.rb:31:in `block in included' Finished in 1 minute 19.47 seconds (files took 1.27 seconds to load) 386 examples, 2 failures, 1 pending --- ext/extconf.rb | 1 + ext/pg_text_decoder.c | 28 +++++++++++++++++----------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/ext/extconf.rb b/ext/extconf.rb index e75f0deb3..d19c7915f 100755 --- a/ext/extconf.rb +++ b/ext/extconf.rb @@ -76,6 +76,7 @@ have_func 'PQconninfo' have_func 'PQsslAttribute' have_func 'PQencryptPasswordConn' +have_func 'timegm' have_const 'PG_DIAG_TABLE_NAME', 'libpq-fe.h' diff --git a/ext/pg_text_decoder.c b/ext/pg_text_decoder.c index 42250df81..c1494acf4 100644 --- a/ext/pg_text_decoder.c +++ b/ext/pg_text_decoder.c @@ -589,10 +589,14 @@ static VALUE pg_text_decoder_timestamp_do(t_pg_coder *conv, char *val, int len, str += 2; } } - if (*str == '\0') + + if (*str == '\0') /* must have consumed all the string */ { -#if RUBY_API_VERSION_MAJOR > 2 || (RUBY_API_VERSION_MAJOR == 2 && RUBY_API_VERSION_MINOR >= 3) && !defined(_WIN32) - // must have consumed all the string + VALUE sec_value; + VALUE gmt_offset_value = Qnil; + +#if RUBY_API_VERSION_MAJOR > 2 || (RUBY_API_VERSION_MAJOR == 2 && RUBY_API_VERSION_MINOR >= 3) && defined(HAVE_TIMEGM) + /* Fast path for time conversion */ struct tm tm; tm.tm_year = year - 1900; tm.tm_mon = mon - 1; @@ -632,9 +636,10 @@ static VALUE pg_text_decoder_timestamp_do(t_pg_coder *conv, char *val, int len, return rb_time_timespec_new(&ts, 0); } } -#else - VALUE sec_value; - VALUE gmt_offset_value = Qnil; + /* Some libc implementations fail to convert certain values, + * so that we fall through to the slow path. + */ +#endif if (nsec) { int sec_numerator = sec * 1000000 + nsec / 1000; @@ -665,9 +670,10 @@ static VALUE pg_text_decoder_timestamp_do(t_pg_coder *conv, char *val, int len, INT2NUM(min), sec_value, gmt_offset_value); -#endif } } + + /* fall through to string conversion */ return pg_text_dec_string(conv, val, len, tuple, field, enc_idx); } @@ -712,9 +718,9 @@ init_pg_text_decoder() /* dummy = rb_define_class_under( rb_mPG_TextDecoder, "FromBase64", rb_cPG_CompositeDecoder ); */ pg_define_coder( "FromBase64", pg_text_dec_from_base64, rb_cPG_CompositeDecoder, rb_mPG_TextDecoder ); - /* dummy = rb_define_class_under( rb_mPG_TextDecoder, "TimestampWithTimeZone", rb_cPG_SimpleDecoder ); */ - pg_define_coder( "TimestampWithTimeZone", pg_text_dec_timestamp_with_time_zone, rb_cPG_SimpleDecoder, rb_mPG_TextDecoder); + /* dummy = rb_define_class_under( rb_mPG_TextDecoder, "TimestampWithTimeZone", rb_cPG_SimpleDecoder ); */ + pg_define_coder( "TimestampWithTimeZone", pg_text_dec_timestamp_with_time_zone, rb_cPG_SimpleDecoder, rb_mPG_TextDecoder); - /* dummy = rb_define_class_under( rb_mPG_TextDecoder, "TimestampWithoutTimeZone", rb_cPG_SimpleDecoder ); */ - pg_define_coder( "TimestampWithoutTimeZone", pg_text_dec_timestamp_without_time_zone, rb_cPG_SimpleDecoder, rb_mPG_TextDecoder); + /* dummy = rb_define_class_under( rb_mPG_TextDecoder, "TimestampWithoutTimeZone", rb_cPG_SimpleDecoder ); */ + pg_define_coder( "TimestampWithoutTimeZone", pg_text_dec_timestamp_without_time_zone, rb_cPG_SimpleDecoder, rb_mPG_TextDecoder); }