/* * pg_column_map.c - PG::ColumnMap class extension * $Id$ * */ #include "pg.h" #include "pg_util.h" #ifdef HAVE_INTTYPES_H #include #endif VALUE rb_mPG_BinaryEncoder; static ID s_id_year; static ID s_id_month; static ID s_id_day; /* * Document-class: PG::BinaryEncoder::Boolean < PG::SimpleEncoder * * This is the encoder class for the PostgreSQL boolean type. * * It accepts true and false. Other values will raise an exception. * */ static int pg_bin_enc_boolean(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate, int enc_idx) { char mybool; if (value == Qtrue) { mybool = 1; } else if (value == Qfalse) { mybool = 0; } else { rb_raise( rb_eTypeError, "wrong data for binary boolean converter" ); } if(out) *out = mybool; return 1; } /* * Document-class: PG::BinaryEncoder::Int2 < PG::SimpleEncoder * * This is the encoder class for the PostgreSQL +int2+ (alias +smallint+) type. * * Non-Number values are expected to have method +to_i+ defined. * */ static int pg_bin_enc_int2(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate, int enc_idx) { if(out){ write_nbo16(NUM2INT(*intermediate), out); }else{ *intermediate = pg_obj_to_i(value); } return 2; } /* * Document-class: PG::BinaryEncoder::Int4 < PG::SimpleEncoder * * This is the encoder class for the PostgreSQL +int4+ (alias +integer+) type. * * Non-Number values are expected to have method +to_i+ defined. * */ static int pg_bin_enc_int4(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate, int enc_idx) { if(out){ write_nbo32(NUM2LONG(*intermediate), out); }else{ *intermediate = pg_obj_to_i(value); } return 4; } /* * Document-class: PG::BinaryEncoder::Int8 < PG::SimpleEncoder * * This is the encoder class for the PostgreSQL +int8+ (alias +bigint+) type. * * Non-Number values are expected to have method +to_i+ defined. * */ static int pg_bin_enc_int8(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate, int enc_idx) { if(out){ write_nbo64(NUM2LL(*intermediate), out); }else{ *intermediate = pg_obj_to_i(value); } return 8; } /* * Document-class: PG::BinaryEncoder::Float4 < PG::SimpleEncoder * * This is the binary encoder class for the PostgreSQL +float4+ type. * */ static int pg_bin_enc_float4(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate, int enc_idx) { union { float f; int32_t i; } swap4; if(out){ swap4.f = NUM2DBL(*intermediate); write_nbo32(swap4.i, out); }else{ *intermediate = value; } return 4; } /* * Document-class: PG::BinaryEncoder::Float8 < PG::SimpleEncoder * * This is the binary encoder class for the PostgreSQL +float8+ type. * */ static int pg_bin_enc_float8(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate, int enc_idx) { union { double f; int64_t i; } swap8; if(out){ swap8.f = NUM2DBL(*intermediate); write_nbo64(swap8.i, out); }else{ *intermediate = value; } return 8; } #define PG_INT32_MIN (-0x7FFFFFFF-1) #define PG_INT32_MAX (0x7FFFFFFF) #define PG_INT64_MIN (-0x7FFFFFFFFFFFFFFFL - 1) #define PG_INT64_MAX 0x7FFFFFFFFFFFFFFFL /* * Document-class: PG::BinaryEncoder::Timestamp < PG::SimpleEncoder * * This is a encoder class for conversion of Ruby Time objects to PostgreSQL binary timestamps. * * The following flags can be used to specify timezone interpretation: * * +PG::Coder::TIMESTAMP_DB_UTC+ : Send timestamp as UTC time (default) * * +PG::Coder::TIMESTAMP_DB_LOCAL+ : Send timestamp as local time (slower) * * Example: * enco = PG::BinaryEncoder::Timestamp.new(flags: PG::Coder::TIMESTAMP_DB_UTC) * enco.encode(Time.utc(2000, 1, 1)) # => "\x00\x00\x00\x00\x00\x00\x00\x00" * * String values are expected to contain a binary data with a length of 8 byte. * */ static int pg_bin_enc_timestamp(t_pg_coder *this, VALUE value, char *out, VALUE *intermediate, int enc_idx) { if(out){ int64_t timestamp; struct timespec ts; /* second call -> write data to *out */ switch(TYPE(*intermediate)){ case T_STRING: return pg_coder_enc_to_s(this, value, out, intermediate, enc_idx); case T_TRUE: write_nbo64(PG_INT64_MAX, out); return 8; case T_FALSE: write_nbo64(PG_INT64_MIN, out); return 8; } ts = rb_time_timespec(*intermediate); /* PostgreSQL's timestamp is based on year 2000 and Ruby's time is based on 1970. * Adjust the 30 years difference. */ timestamp = ((int64_t)ts.tv_sec - 10957L * 24L * 3600L) * 1000000 + ((int64_t)ts.tv_nsec / 1000); if( this->flags & PG_CODER_TIMESTAMP_DB_LOCAL ) { /* send as local time */ timestamp += NUM2LL(rb_funcall(*intermediate, rb_intern("utc_offset"), 0)) * 1000000; } write_nbo64(timestamp, out); }else{ /* first call -> determine the required length */ if(TYPE(value) == T_STRING){ char *pstr = RSTRING_PTR(value); if(RSTRING_LEN(value) >= 1){ switch(pstr[0]) { case 'I': case 'i': *intermediate = Qtrue; return 8; case '-': if (RSTRING_LEN(value) >= 2 && (pstr[1] == 'I' || pstr[1] == 'i')) { *intermediate = Qfalse; return 8; } } } return pg_coder_enc_to_s(this, value, out, intermediate, enc_idx); } if( this->flags & PG_CODER_TIMESTAMP_DB_LOCAL ) { /* make a local time, so that utc_offset is set */ value = rb_funcall(value, rb_intern("getlocal"), 0); } *intermediate = value; } return 8; } #define POSTGRES_EPOCH_JDATE 2451545 /* == date2j(2000, 1, 1) */ int date2j(int year, int month, int day) { int julian; int century; if (month > 2) { month += 1; year += 4800; } else { month += 13; year += 4799; } century = year / 100; julian = year * 365 - 32167; julian += year / 4 - century + century / 4; julian += 7834 * month / 256 + day; return julian; } /* date2j() */ /* * Document-class: PG::BinaryEncoder::Date < PG::SimpleEncoder * * This is a encoder class for conversion of Ruby Date objects to PostgreSQL binary date. * * String values are expected to contain a binary data with a length of 4 byte. * */ static int pg_bin_enc_date(t_pg_coder *this, VALUE value, char *out, VALUE *intermediate, int enc_idx) { if(out){ /* second call -> write data to *out */ switch(TYPE(*intermediate)){ case T_STRING: return pg_coder_enc_to_s(this, value, out, intermediate, enc_idx); case T_TRUE: write_nbo32(PG_INT32_MAX, out); return 4; case T_FALSE: write_nbo32(PG_INT32_MIN, out); return 4; } VALUE year = rb_funcall(value, s_id_year, 0); VALUE month = rb_funcall(value, s_id_month, 0); VALUE day = rb_funcall(value, s_id_day, 0); int jday = date2j(NUM2INT(year), NUM2INT(month), NUM2INT(day)) - POSTGRES_EPOCH_JDATE; write_nbo32(jday, out); }else{ /* first call -> determine the required length */ if(TYPE(value) == T_STRING){ char *pstr = RSTRING_PTR(value); if(RSTRING_LEN(value) >= 1){ switch(pstr[0]) { case 'I': case 'i': *intermediate = Qtrue; return 4; case '-': if (RSTRING_LEN(value) >= 2 && (pstr[1] == 'I' || pstr[1] == 'i')) { *intermediate = Qfalse; return 4; } } } return pg_coder_enc_to_s(this, value, out, intermediate, enc_idx); } *intermediate = value; } return 4; } /* * Document-class: PG::BinaryEncoder::FromBase64 < PG::CompositeEncoder * * This is an encoder class for conversion of base64 encoded data * to it's binary representation. * */ static int pg_bin_enc_from_base64(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate, int enc_idx) { int strlen; VALUE subint; t_pg_composite_coder *this = (t_pg_composite_coder *)conv; t_pg_coder_enc_func enc_func = pg_coder_enc_func(this->elem); if(out){ /* Second encoder pass, if required */ strlen = enc_func(this->elem, value, out, intermediate, enc_idx); strlen = base64_decode( out, out, strlen ); return strlen; } else { /* First encoder pass */ strlen = enc_func(this->elem, value, NULL, &subint, enc_idx); if( strlen == -1 ){ /* Encoded string is returned in subint */ VALUE out_str; strlen = RSTRING_LENINT(subint); out_str = rb_str_new(NULL, BASE64_DECODED_SIZE(strlen)); strlen = base64_decode( RSTRING_PTR(out_str), RSTRING_PTR(subint), strlen); rb_str_set_len( out_str, strlen ); *intermediate = out_str; return -1; } else { *intermediate = subint; return BASE64_DECODED_SIZE(strlen); } } } void init_pg_binary_encoder(void) { s_id_year = rb_intern("year"); s_id_month = rb_intern("month"); s_id_day = rb_intern("day"); /* This module encapsulates all encoder classes with binary output format */ rb_mPG_BinaryEncoder = rb_define_module_under( rb_mPG, "BinaryEncoder" ); /* Make RDoc aware of the encoder classes... */ /* dummy = rb_define_class_under( rb_mPG_BinaryEncoder, "Boolean", rb_cPG_SimpleEncoder ); */ pg_define_coder( "Boolean", pg_bin_enc_boolean, rb_cPG_SimpleEncoder, rb_mPG_BinaryEncoder ); /* dummy = rb_define_class_under( rb_mPG_BinaryEncoder, "Int2", rb_cPG_SimpleEncoder ); */ pg_define_coder( "Int2", pg_bin_enc_int2, rb_cPG_SimpleEncoder, rb_mPG_BinaryEncoder ); /* dummy = rb_define_class_under( rb_mPG_BinaryEncoder, "Int4", rb_cPG_SimpleEncoder ); */ pg_define_coder( "Int4", pg_bin_enc_int4, rb_cPG_SimpleEncoder, rb_mPG_BinaryEncoder ); /* dummy = rb_define_class_under( rb_mPG_BinaryEncoder, "Int8", rb_cPG_SimpleEncoder ); */ pg_define_coder( "Int8", pg_bin_enc_int8, rb_cPG_SimpleEncoder, rb_mPG_BinaryEncoder ); /* dummy = rb_define_class_under( rb_mPG_BinaryEncoder, "Float4", rb_cPG_SimpleEncoder ); */ pg_define_coder( "Float4", pg_bin_enc_float4, rb_cPG_SimpleEncoder, rb_mPG_BinaryEncoder ); /* dummy = rb_define_class_under( rb_mPG_BinaryEncoder, "Float8", rb_cPG_SimpleEncoder ); */ pg_define_coder( "Float8", pg_bin_enc_float8, rb_cPG_SimpleEncoder, rb_mPG_BinaryEncoder ); /* dummy = rb_define_class_under( rb_mPG_BinaryEncoder, "String", rb_cPG_SimpleEncoder ); */ pg_define_coder( "String", pg_coder_enc_to_s, rb_cPG_SimpleEncoder, rb_mPG_BinaryEncoder ); /* dummy = rb_define_class_under( rb_mPG_BinaryEncoder, "Bytea", rb_cPG_SimpleEncoder ); */ pg_define_coder( "Bytea", pg_coder_enc_to_s, rb_cPG_SimpleEncoder, rb_mPG_BinaryEncoder ); /* dummy = rb_define_class_under( rb_mPG_BinaryEncoder, "Timestamp", rb_cPG_SimpleEncoder ); */ pg_define_coder( "Timestamp", pg_bin_enc_timestamp, rb_cPG_SimpleEncoder, rb_mPG_BinaryEncoder ); /* dummy = rb_define_class_under( rb_mPG_BinaryEncoder, "Date", rb_cPG_SimpleEncoder ); */ pg_define_coder( "Date", pg_bin_enc_date, rb_cPG_SimpleEncoder, rb_mPG_BinaryEncoder ); /* dummy = rb_define_class_under( rb_mPG_BinaryEncoder, "FromBase64", rb_cPG_CompositeEncoder ); */ pg_define_coder( "FromBase64", pg_bin_enc_from_base64, rb_cPG_CompositeEncoder, rb_mPG_BinaryEncoder ); }