/* * pg_column_map.c - PG::ColumnMap class extension * $Id: pg_binary_decoder.c,v 5d166a4d0441 2018/07/29 12:03:00 lars $ * */ #include "ruby/version.h" #include "pg.h" #include "util.h" #ifdef HAVE_INTTYPES_H #include #endif VALUE rb_mPG_BinaryDecoder; /* * Document-class: PG::BinaryDecoder::Boolean < PG::SimpleDecoder * * This is a decoder class for conversion of PostgreSQL binary bool type * to Ruby true or false objects. * */ static VALUE pg_bin_dec_boolean(t_pg_coder *conv, const char *val, int len, int tuple, int field, int enc_idx) { if (len < 1) { rb_raise( rb_eTypeError, "wrong data for binary boolean converter in tuple %d field %d", tuple, field); } return *val == 0 ? Qfalse : Qtrue; } /* * Document-class: PG::BinaryDecoder::Integer < PG::SimpleDecoder * * This is a decoder class for conversion of PostgreSQL binary int2, int4 and int8 types * to Ruby Integer objects. * */ static VALUE pg_bin_dec_integer(t_pg_coder *conv, const char *val, int len, int tuple, int field, int enc_idx) { switch( len ){ case 2: return INT2NUM(read_nbo16(val)); case 4: return LONG2NUM(read_nbo32(val)); case 8: return LL2NUM(read_nbo64(val)); default: rb_raise( rb_eTypeError, "wrong data for binary integer converter in tuple %d field %d length %d", tuple, field, len); } } /* * Document-class: PG::BinaryDecoder::Float < PG::SimpleDecoder * * This is a decoder class for conversion of PostgreSQL binary float4 and float8 types * to Ruby Float objects. * */ static VALUE pg_bin_dec_float(t_pg_coder *conv, const char *val, int len, int tuple, int field, int enc_idx) { union { float f; int32_t i; } swap4; union { double f; int64_t i; } swap8; switch( len ){ case 4: swap4.i = read_nbo32(val); return rb_float_new(swap4.f); case 8: swap8.i = read_nbo64(val); return rb_float_new(swap8.f); default: rb_raise( rb_eTypeError, "wrong data for BinaryFloat converter in tuple %d field %d length %d", tuple, field, len); } } /* * Document-class: PG::BinaryDecoder::Bytea < PG::SimpleDecoder * * This decoder class delivers the data received from the server as binary String object. * It is therefore suitable for conversion of PostgreSQL bytea data as well as any other * data in binary format. * */ VALUE pg_bin_dec_bytea(t_pg_coder *conv, const char *val, int len, int tuple, int field, int enc_idx) { VALUE ret; ret = rb_tainted_str_new( val, len ); PG_ENCODING_SET_NOCHECK( ret, rb_ascii8bit_encindex() ); return ret; } /* * Document-class: PG::BinaryDecoder::ToBase64 < PG::CompositeDecoder * * This is a decoder class for conversion of binary (bytea) to base64 data. * */ static VALUE pg_bin_dec_to_base64(t_pg_coder *conv, const char *val, int len, int tuple, int field, int enc_idx) { t_pg_composite_coder *this = (t_pg_composite_coder *)conv; t_pg_coder_dec_func dec_func = pg_coder_dec_func(this->elem, this->comp.format); int encoded_len = BASE64_ENCODED_SIZE(len); /* create a buffer of the encoded length */ VALUE out_value = rb_tainted_str_new(NULL, encoded_len); base64_encode( RSTRING_PTR(out_value), val, len ); /* Is it a pure String conversion? Then we can directly send out_value to the user. */ if( this->comp.format == 0 && dec_func == pg_text_dec_string ){ PG_ENCODING_SET_NOCHECK( out_value, enc_idx ); return out_value; } if( this->comp.format == 1 && dec_func == pg_bin_dec_bytea ){ PG_ENCODING_SET_NOCHECK( out_value, rb_ascii8bit_encindex() ); return out_value; } out_value = dec_func(this->elem, RSTRING_PTR(out_value), encoded_len, tuple, field, enc_idx); return out_value; } #define PG_INT64_MIN (-0x7FFFFFFFFFFFFFFFL - 1) #define PG_INT64_MAX 0x7FFFFFFFFFFFFFFFL /* * Document-class: PG::BinaryDecoder::Timestamp < PG::SimpleDecoder * * This is a decoder class for conversion of PostgreSQL binary timestamps * to Ruby Time objects. * * The following flags can be used to specify timezone interpretation: * * +PG::Coder::TIMESTAMP_DB_UTC+ : Interpret timestamp as UTC time (default) * * +PG::Coder::TIMESTAMP_DB_LOCAL+ : Interpret timestamp as local time * * +PG::Coder::TIMESTAMP_APP_UTC+ : Return timestamp as UTC time (default) * * +PG::Coder::TIMESTAMP_APP_LOCAL+ : Return timestamp as local time * * Example: * deco = PG::BinaryDecoder::Timestamp.new(flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_LOCAL) * deco.decode("\0"*8) # => 2000-01-01 01:00:00 +0100 */ static VALUE pg_bin_dec_timestamp(t_pg_coder *conv, const char *val, int len, int tuple, int field, int enc_idx) { int64_t timestamp; struct timespec ts; VALUE t; if( len != sizeof(timestamp) ){ rb_raise( rb_eTypeError, "wrong data for timestamp converter in tuple %d field %d length %d", tuple, field, len); } timestamp = read_nbo64(val); switch(timestamp){ case PG_INT64_MAX: return rb_str_new2("infinity"); case PG_INT64_MIN: return rb_str_new2("-infinity"); default: /* PostgreSQL's timestamp is based on year 2000 and Ruby's time is based on 1970. * Adjust the 30 years difference. */ ts.tv_sec = (timestamp / 1000000) + 10957L * 24L * 3600L; ts.tv_nsec = (timestamp % 1000000) * 1000; #if (RUBY_API_VERSION_MAJOR > 2 || (RUBY_API_VERSION_MAJOR == 2 && RUBY_API_VERSION_MINOR >= 3)) && defined(NEGATIVE_TIME_T) /* Fast path for time conversion */ t = rb_time_timespec_new(&ts, conv->flags & PG_CODER_TIMESTAMP_APP_LOCAL ? INT_MAX : INT_MAX-1); #else t = rb_funcall(rb_cTime, rb_intern("at"), 2, LL2NUM(ts.tv_sec), LL2NUM(ts.tv_nsec / 1000)); if( !(conv->flags & PG_CODER_TIMESTAMP_APP_LOCAL) ) { t = rb_funcall(t, rb_intern("utc"), 0); } #endif if( conv->flags & PG_CODER_TIMESTAMP_DB_LOCAL ) { /* interpret it as local time */ t = rb_funcall(t, rb_intern("-"), 1, rb_funcall(t, rb_intern("utc_offset"), 0)); } return t; } } /* * Document-class: PG::BinaryDecoder::String < PG::SimpleDecoder * * This is a decoder class for conversion of PostgreSQL text output to * to Ruby String object. The output value will have the character encoding * set with PG::Connection#internal_encoding= . * */ void init_pg_binary_decoder() { /* This module encapsulates all decoder classes with binary input format */ rb_mPG_BinaryDecoder = rb_define_module_under( rb_mPG, "BinaryDecoder" ); /* Make RDoc aware of the decoder classes... */ /* dummy = rb_define_class_under( rb_mPG_BinaryDecoder, "Boolean", rb_cPG_SimpleDecoder ); */ pg_define_coder( "Boolean", pg_bin_dec_boolean, rb_cPG_SimpleDecoder, rb_mPG_BinaryDecoder ); /* dummy = rb_define_class_under( rb_mPG_BinaryDecoder, "Integer", rb_cPG_SimpleDecoder ); */ pg_define_coder( "Integer", pg_bin_dec_integer, rb_cPG_SimpleDecoder, rb_mPG_BinaryDecoder ); /* dummy = rb_define_class_under( rb_mPG_BinaryDecoder, "Float", rb_cPG_SimpleDecoder ); */ pg_define_coder( "Float", pg_bin_dec_float, rb_cPG_SimpleDecoder, rb_mPG_BinaryDecoder ); /* dummy = rb_define_class_under( rb_mPG_BinaryDecoder, "String", rb_cPG_SimpleDecoder ); */ pg_define_coder( "String", pg_text_dec_string, rb_cPG_SimpleDecoder, rb_mPG_BinaryDecoder ); /* dummy = rb_define_class_under( rb_mPG_BinaryDecoder, "Bytea", rb_cPG_SimpleDecoder ); */ pg_define_coder( "Bytea", pg_bin_dec_bytea, rb_cPG_SimpleDecoder, rb_mPG_BinaryDecoder ); /* dummy = rb_define_class_under( rb_mPG_BinaryDecoder, "Timestamp", rb_cPG_SimpleDecoder ); */ pg_define_coder( "Timestamp", pg_bin_dec_timestamp, rb_cPG_SimpleDecoder, rb_mPG_BinaryDecoder ); /* dummy = rb_define_class_under( rb_mPG_BinaryDecoder, "ToBase64", rb_cPG_CompositeDecoder ); */ pg_define_coder( "ToBase64", pg_bin_dec_to_base64, rb_cPG_CompositeDecoder, rb_mPG_BinaryDecoder ); }