ext/pg_connection.c in pg-0.18.0.pre20140820094244 vs ext/pg_connection.c in pg-0.18.0.pre20141017155815

- old
+ new

@@ -1,23 +1,28 @@ /* * pg_connection.c - PG::Connection class extension - * $Id: pg_connection.c,v affbd590e74e 2014/08/20 16:14:47 ged $ + * $Id: pg_connection.c,v 57d770944b5d 2014/10/09 20:20:11 lars $ * */ #include "pg.h" +/* Number of bytes that are reserved on the stack for query params. */ +#define QUERYDATA_BUFFER_SIZE 4000 + VALUE rb_cPGconn; +static ID s_id_encode; +static VALUE sym_type, sym_format, sym_value; static PQnoticeReceiver default_notice_receiver = NULL; static PQnoticeProcessor default_notice_processor = NULL; -static PGconn *pgconn_check( VALUE ); static VALUE pgconn_finish( VALUE ); #ifdef M17N_SUPPORTED static VALUE pgconn_set_default_encoding( VALUE self ); +void pgconn_set_internal_encoding_index( VALUE ); #endif #ifndef HAVE_RB_THREAD_FD_SELECT #define rb_fdset_t fd_set #define rb_fd_init(f) @@ -30,33 +35,62 @@ /* * Global functions */ /* - * Fetch the data pointer and check it for sanity. + * Fetch the PG::Connection object data pointer. */ +t_pg_connection * +pg_get_connection( VALUE self ) +{ + t_pg_connection *this; + Data_Get_Struct( self, t_pg_connection, this); + + return this; +} + +/* + * Fetch the PG::Connection object data pointer and check it's + * PGconn data pointer for sanity. + */ +t_pg_connection * +pg_get_connection_safe( VALUE self ) +{ + t_pg_connection *this; + Data_Get_Struct( self, t_pg_connection, this); + + if ( !this->pgconn ) + rb_raise( rb_eConnectionBad, "connection is closed" ); + + return this; +} + +/* + * Fetch the PGconn data pointer and check it for sanity. + */ PGconn * pg_get_pgconn( VALUE self ) { - PGconn *conn = pgconn_check( self ); + t_pg_connection *this; + Data_Get_Struct( self, t_pg_connection, this); - if ( !conn ) + if ( !this->pgconn ) rb_raise( rb_eConnectionBad, "connection is closed" ); - return conn; + return this->pgconn; } - /* * Close the associated socket IO object if there is one. */ void pgconn_close_socket_io( VALUE self ) { - VALUE socket_io = rb_iv_get( self, "@socket_io" ); + t_pg_connection *this = pg_get_connection( self ); + VALUE socket_io = this->socket_io; if ( RTEST(socket_io) ) { #if defined(_WIN32) && defined(HAVE_RB_W32_WRAP_IO_HANDLE) int ruby_sd = NUM2INT(rb_funcall( socket_io, rb_intern("fileno"), 0 )); if( rb_w32_unwrap_io_handle(ruby_sd) ){ @@ -64,11 +98,11 @@ } #endif rb_funcall( socket_io, rb_intern("close"), 0 ); } - rb_iv_set( self, "@socket_io", Qnil ); + this->socket_io = Qnil; } /* * Create a Ruby Array of Hashes out of a PGconninfoOptions array. @@ -103,38 +137,37 @@ return ary; } /* - * Allocation/ + * GC Mark function */ - -/* - * Object validity checker. Returns the data pointer. - */ -static PGconn * -pgconn_check( VALUE self ) { - - Check_Type( self, T_DATA ); - - if ( !rb_obj_is_kind_of(self, rb_cPGconn) ) { - rb_raise( rb_eTypeError, "wrong argument type %s (expected PG::Connection)", - rb_obj_classname( self ) ); - } - - return DATA_PTR( self ); +static void +pgconn_gc_mark( t_pg_connection *this ) +{ + rb_gc_mark( this->socket_io ); + rb_gc_mark( this->notice_receiver ); + rb_gc_mark( this->notice_processor ); + rb_gc_mark( this->type_map_for_queries ); + rb_gc_mark( this->type_map_for_results ); + rb_gc_mark( this->trace_stream ); + rb_gc_mark( this->external_encoding ); + rb_gc_mark( this->encoder_for_put_copy_data ); + rb_gc_mark( this->decoder_for_get_copy_data ); } /* * GC Free function */ static void -pgconn_gc_free( PGconn *conn ) +pgconn_gc_free( t_pg_connection *this ) { - if (conn != NULL) - PQfinish( conn ); + if (this->pgconn != NULL) + PQfinish( this->pgconn ); + + xfree(this); } /************************************************************************** * Class Methods @@ -147,14 +180,24 @@ * PG::Connection.allocate -> conn */ static VALUE pgconn_s_allocate( VALUE klass ) { - VALUE self = Data_Wrap_Struct( klass, NULL, pgconn_gc_free, NULL ); - rb_iv_set( self, "@socket_io", Qnil ); - rb_iv_set( self, "@notice_receiver", Qnil); - rb_iv_set( self, "@notice_processor", Qnil); + t_pg_connection *this; + VALUE self = Data_Make_Struct( klass, t_pg_connection, pgconn_gc_mark, pgconn_gc_free, this ); + + this->pgconn = NULL; + this->socket_io = Qnil; + this->notice_receiver = Qnil; + this->notice_processor = Qnil; + this->type_map_for_queries = Qnil; + this->type_map_for_results = Qnil; + this->encoder_for_put_copy_data = Qnil; + this->decoder_for_get_copy_data = Qnil; + this->trace_stream = Qnil; + this->external_encoding = Qnil; + return self; } /* @@ -215,25 +258,23 @@ * Raises a PG::Error if the connection fails. */ static VALUE pgconn_init(int argc, VALUE *argv, VALUE self) { - PGconn *conn = NULL; + t_pg_connection *this; VALUE conninfo; VALUE error; + this = pg_get_connection( self ); conninfo = rb_funcall2( rb_cPGconn, rb_intern("parse_connect_args"), argc, argv ); - conn = gvl_PQconnectdb(StringValuePtr(conninfo)); + this->pgconn = gvl_PQconnectdb(StringValueCStr(conninfo)); - if(conn == NULL) + if(this->pgconn == NULL) rb_raise(rb_ePGerror, "PQconnectdb() unable to allocate structure"); - Check_Type(self, T_DATA); - DATA_PTR(self) = conn; - - if (PQstatus(conn) == CONNECTION_BAD) { - error = rb_exc_new2(rb_eConnectionBad, PQerrorMessage(conn)); + if (PQstatus(this->pgconn) == CONNECTION_BAD) { + error = rb_exc_new2(rb_eConnectionBad, PQerrorMessage(this->pgconn)); rb_iv_set(error, "@connection", self); rb_exc_raise(error); } #ifdef M17N_SUPPORTED @@ -263,31 +304,29 @@ * */ static VALUE pgconn_s_connect_start( int argc, VALUE *argv, VALUE klass ) { - PGconn *conn = NULL; VALUE rb_conn; VALUE conninfo; VALUE error; + t_pg_connection *this; /* * PG::Connection.connect_start must act as both alloc() and initialize() * because it is not invoked by calling new(). */ rb_conn = pgconn_s_allocate( klass ); + this = pg_get_connection( rb_conn ); conninfo = rb_funcall2( klass, rb_intern("parse_connect_args"), argc, argv ); - conn = gvl_PQconnectStart( StringValuePtr(conninfo) ); + this->pgconn = gvl_PQconnectStart( StringValueCStr(conninfo) ); - if( conn == NULL ) + if( this->pgconn == NULL ) rb_raise(rb_ePGerror, "PQconnectStart() unable to allocate structure"); - Check_Type(rb_conn, T_DATA); - DATA_PTR(rb_conn) = conn; - - if ( PQstatus(conn) == CONNECTION_BAD ) { - error = rb_exc_new2(rb_eConnectionBad, PQerrorMessage(conn)); + if ( PQstatus(this->pgconn) == CONNECTION_BAD ) { + error = rb_exc_new2(rb_eConnectionBad, PQerrorMessage(this->pgconn)); rb_iv_set(error, "@connection", rb_conn); rb_exc_raise(error); } if ( rb_block_given_p() ) { @@ -320,11 +359,11 @@ { PGPing ping; VALUE conninfo; conninfo = rb_funcall2( klass, rb_intern("parse_connect_args"), argc, argv ); - ping = PQping( StringValuePtr(conninfo) ); + ping = PQping( StringValueCStr(conninfo) ); return INT2FIX((int)ping); } #endif @@ -385,11 +424,11 @@ UNUSED( self ); Check_Type(password, T_STRING); Check_Type(username, T_STRING); - encrypted = PQencryptPassword(StringValuePtr(password), StringValuePtr(username)); + encrypted = PQencryptPassword(StringValueCStr(password), StringValueCStr(username)); rval = rb_str_new2( encrypted ); PQfreemem( encrypted ); OBJ_INFECT( rval, password ); OBJ_INFECT( rval, username ); @@ -451,13 +490,15 @@ * Closes the backend connection. */ static VALUE pgconn_finish( VALUE self ) { + t_pg_connection *this = pg_get_connection_safe( self ); + pgconn_close_socket_io( self ); - PQfinish( pg_get_pgconn(self) ); - DATA_PTR( self ) = NULL; + PQfinish( this->pgconn ); + this->pgconn = NULL; return Qnil; } /* @@ -467,11 +508,12 @@ * Returns +true+ if the backend connection has been closed. */ static VALUE pgconn_finished_p( VALUE self ) { - if ( DATA_PTR(self) ) return Qfalse; + t_pg_connection *this = pg_get_connection( self ); + if ( this->pgconn ) return Qfalse; return Qtrue; } /* @@ -621,10 +663,11 @@ if (!options) return Qnil; return rb_tainted_str_new2(options); } +#ifdef HAVE_PQCONNINFO /* * call-seq: * conn.conninfo -> hash * * Returns the connection options used by a live connection. @@ -639,13 +682,13 @@ PQconninfoFree(options); return array; } +#endif - /* * call-seq: * conn.status() * * Returns status of connection : CONNECTION_OK or CONNECTION_BAD @@ -692,11 +735,11 @@ * Returns nil if the value of the parameter is not known. */ static VALUE pgconn_parameter_status(VALUE self, VALUE param_name) { - const char *ret = PQparameterStatus(pg_get_pgconn(self), StringValuePtr(param_name)); + const char *ret = PQparameterStatus(pg_get_pgconn(self), StringValueCStr(param_name)); if(ret == NULL) return Qnil; else return rb_tainted_str_new2(ret); } @@ -791,14 +834,15 @@ pgconn_socket_io(VALUE self) { int sd; int ruby_sd; ID id_autoclose = rb_intern("autoclose="); - VALUE socket_io = rb_iv_get( self, "@socket_io" ); + t_pg_connection *this = pg_get_connection_safe( self ); + VALUE socket_io = this->socket_io; if ( !RTEST(socket_io) ) { - if( (sd = PQsocket(pg_get_pgconn(self))) < 0) + if( (sd = PQsocket(this->pgconn)) < 0) rb_raise(rb_eConnectionBad, "PQsocket() can't get socket descriptor"); #ifdef _WIN32 ruby_sd = rb_w32_wrap_io_handle((HANDLE)(intptr_t)sd, O_RDWR|O_BINARY|O_NOINHERIT); #else @@ -810,11 +854,11 @@ /* Disable autoclose feature, when supported */ if( rb_respond_to(socket_io, id_autoclose) ){ rb_funcall( socket_io, id_autoclose, 1, Qfalse ); } - rb_iv_set( self, "@socket_io", socket_io ); + this->socket_io = socket_io; } return socket_io; } @@ -899,11 +943,11 @@ /* If called with no parameters, use PQexec */ if ( argc == 1 ) { Check_Type(argv[0], T_STRING); - result = gvl_PQexec(conn, StringValuePtr(argv[0])); + result = gvl_PQexec(conn, StringValueCStr(argv[0])); rb_pgresult = pg_new_result(result, self); pg_result_check(rb_pgresult); if (rb_block_given_p()) { return rb_ensure(rb_yield, rb_pgresult, pg_result_clear, rb_pgresult); } @@ -916,14 +960,266 @@ } } +struct linked_typecast_data { + struct linked_typecast_data *next; + char data[0]; +}; + +/* This struct is allocated on the stack for all query execution functions. */ +struct query_params_data { + + /* + * Filled by caller + */ + + /* Is the query function to execute one with types array? */ + int with_types; + /* Array of query params from user space */ + VALUE params; + /* The typemap given from user space */ + VALUE typemap; + + /* + * Filled by alloc_query_params() + */ + + /* Wraps the pointer of allocated memory, if function parameters dont't + * fit in the memory_pool below. + */ + VALUE heap_pool; + + /* Pointer to the value string pointers (either within memory_pool or heap_pool). + * The value strings itself are either directly within RString memory or, + * in case of type casted values, within memory_pool or typecast_heap_chain. + */ + char **values; + /* Pointer to the param lengths (either within memory_pool or heap_pool) */ + int *lengths; + /* Pointer to the format codes (either within memory_pool or heap_pool) */ + int *formats; + /* Pointer to the OID types (either within memory_pool or heap_pool) */ + Oid *types; + + /* This array takes the string values for the timeframe of the query, + * if param value convertion is required + */ + VALUE gc_array; + + /* Wraps a single linked list of allocated memory chunks for type casted params. + * Used when the memory_pool is to small. + */ + VALUE typecast_heap_chain; + + /* This memory pool is used to place above query function parameters on it. */ + char memory_pool[QUERYDATA_BUFFER_SIZE]; +}; + +static void +free_typecast_heap_chain(struct linked_typecast_data *chain_entry) +{ + while(chain_entry){ + struct linked_typecast_data *next = chain_entry->next; + xfree(chain_entry); + chain_entry = next; + } +} + +static char * +alloc_typecast_buf( VALUE *typecast_heap_chain, int len ) +{ + /* Allocate a new memory chunk from heap */ + struct linked_typecast_data *allocated = + (struct linked_typecast_data *)xmalloc(sizeof(struct linked_typecast_data) + len); + + /* Did we already wrap a memory chain per T_DATA object? */ + if( NIL_P( *typecast_heap_chain ) ){ + /* Leave free'ing of the buffer chain to the GC, when paramsData has left the stack */ + *typecast_heap_chain = Data_Wrap_Struct( rb_cObject, NULL, free_typecast_heap_chain, allocated ); + allocated->next = NULL; + } else { + /* Append to the chain */ + allocated->next = DATA_PTR( *typecast_heap_chain ); + DATA_PTR( *typecast_heap_chain ) = allocated; + } + + return &allocated->data[0]; +} + + +static int +alloc_query_params1(struct query_params_data *paramsData) +{ + VALUE param_value; + t_typemap *p_typemap; + int nParams; + int i=0; + t_pg_coder *conv; + unsigned int required_pool_size; + char *memory_pool; + + Data_Get_Struct( paramsData->typemap, t_typemap, p_typemap); + + nParams = (int)RARRAY_LEN(paramsData->params); + + required_pool_size = nParams * ( + sizeof(char *) + + sizeof(int) + + sizeof(int) + + (paramsData->with_types ? sizeof(Oid) : 0)); + + if( sizeof(paramsData->memory_pool) < required_pool_size ){ + /* Allocate one combined memory pool for all possible function parameters */ + memory_pool = (char*)xmalloc( required_pool_size ); + /* Leave free'ing of the buffer to the GC, when paramsData has left the stack */ + paramsData->heap_pool = Data_Wrap_Struct( rb_cObject, NULL, -1, memory_pool ); + required_pool_size = 0; + }else{ + /* Use stack memory for function parameters */ + memory_pool = paramsData->memory_pool; + } + + paramsData->values = (char **)memory_pool; + paramsData->lengths = (int *)((char*)paramsData->values + sizeof(char *) * nParams); + paramsData->formats = (int *)((char*)paramsData->lengths + sizeof(int) * nParams); + paramsData->types = (Oid *)((char*)paramsData->formats + sizeof(int) * nParams); + + { + char *typecast_buf = paramsData->memory_pool + required_pool_size; + + for ( i = 0; i < nParams; i++ ) { + param_value = rb_ary_entry(paramsData->params, i); + + paramsData->formats[i] = 0; + if( paramsData->with_types ) + paramsData->types[i] = 0; + + /* Let the given typemap select a coder for this param */ + conv = p_typemap->typecast_query_param(paramsData->typemap, param_value, i); + + /* Using a coder object for the param_value? Then set it's format code and oid. */ + if( conv ){ + paramsData->formats[i] = conv->format; + if( paramsData->with_types ) + paramsData->types[i] = conv->oid; + } else { + /* No coder, but got we a hash form for the query param? + * Then take format code and oid from there. */ + if (TYPE(param_value) == T_HASH) { + VALUE format_value = rb_hash_aref(param_value, sym_format); + if( !NIL_P(format_value) ) + paramsData->formats[i] = NUM2INT(format_value); + if( paramsData->with_types ){ + VALUE type_value = rb_hash_aref(param_value, sym_type); + if( !NIL_P(type_value) ) + paramsData->types[i] = NUM2UINT(type_value); + } + param_value = rb_hash_aref(param_value, sym_value); + } + } + + if( NIL_P(param_value) ){ + paramsData->values[i] = NULL; + paramsData->lengths[i] = 0; + } else { + t_pg_coder_enc_func enc_func = pg_coder_enc_func( conv ); + VALUE intermediate; + + /* 1st pass for retiving the required memory space */ + int len = enc_func(conv, param_value, NULL, &intermediate); + + if( len == -1 ){ + /* The intermediate value is a String that can be used directly. */ + + /* Ensure that the String object is zero terminated as expected by libpq. */ + if( paramsData->formats[i] == 0 ) + StringValueCStr(intermediate); + /* In case a new string object was generated, make sure it doesn't get freed by the GC */ + if( intermediate != param_value ) + rb_ary_push(paramsData->gc_array, intermediate); + paramsData->values[i] = RSTRING_PTR(intermediate); + paramsData->lengths[i] = RSTRING_LENINT(intermediate); + + } else { + /* Is the stack memory pool too small to take the type casted value? */ + if( sizeof(paramsData->memory_pool) < required_pool_size + len + 1){ + typecast_buf = alloc_typecast_buf( &paramsData->typecast_heap_chain, len + 1 ); + } + + /* 2nd pass for writing the data to prepared buffer */ + len = enc_func(conv, param_value, typecast_buf, &intermediate); + paramsData->values[i] = typecast_buf; + if( paramsData->formats[i] == 0 ){ + /* text format strings must be zero terminated and lengths are ignored */ + typecast_buf[len] = 0; + typecast_buf += len + 1; + required_pool_size += len + 1; + } else { + paramsData->lengths[i] = len; + typecast_buf += len; + required_pool_size += len; + } + } + + RB_GC_GUARD(intermediate); + } + } + } + + return nParams; +} + +static void +free_query_params(struct query_params_data *paramsData) +{ + /* currently nothing to free */ +} + +static int +alloc_query_params(struct query_params_data *paramsData) +{ + int nParams; + Check_Type(paramsData->params, T_ARRAY); + + if( NIL_P(paramsData->typemap) ){ + /* We don't need to call fit_to_query for pg_default_typemap. It does nothing. */ + paramsData->typemap = pg_default_typemap; + } else { + t_typemap *p_typemap = DATA_PTR( paramsData->typemap ); + paramsData->typemap = p_typemap->fit_to_query( paramsData->typemap, paramsData->params ); + } + + paramsData->heap_pool = Qnil; + paramsData->typecast_heap_chain = Qnil; + paramsData->gc_array = rb_ary_new(); + + nParams = alloc_query_params1(paramsData); + return nParams; +} + +void +pgconn_query_assign_typemap( VALUE self, struct query_params_data *paramsData ) +{ + if(NIL_P(paramsData->typemap)){ + /* Use default typemap for queries. It's type is checked when assigned. */ + paramsData->typemap = pg_get_connection(self)->type_map_for_queries; + }else{ + /* Check type of method param */ + if ( !rb_obj_is_kind_of(paramsData->typemap, rb_cTypeMap) ) { + rb_raise( rb_eTypeError, "wrong argument type %s (expected kind of PG::TypeMap)", + rb_obj_classname( paramsData->typemap ) ); + } + Check_Type( paramsData->typemap, T_DATA ); + } +} + /* * call-seq: - * conn.exec_params(sql, params[, result_format ] ) -> PG::Result - * conn.exec_params(sql, params[, result_format ] ) {|pg_result| block } + * conn.exec_params(sql, params[, result_format[, type_map]] ) -> PG::Result + * conn.exec_params(sql, params[, result_format[, type_map]] ) {|pg_result| block } * * Sends SQL query request specified by +sql+ to PostgreSQL using placeholders * for parameters. * * Returns a PG::Result instance on success. On failure, it raises a PG::Error. @@ -949,117 +1245,51 @@ * For example: "SELECT $1::int" * * The optional +result_format+ should be 0 for text results, 1 * for binary. * + * type_map can be a PG::TypeMap derivation (such as PG::BasicTypeMapForQueries). + * This will type cast the params form various Ruby types before transmission + * based on the encoders defined by the type map. When a type encoder is used + * the format and oid of a given bind parameter are retrieved from the encoder + * instead out of the hash form described above. + * * If the optional code block is given, it will be passed <i>result</i> as an argument, * and the PG::Result object will automatically be cleared when the block terminates. * In this instance, <code>conn.exec</code> returns the value of the block. */ static VALUE pgconn_exec_params( int argc, VALUE *argv, VALUE self ) { PGconn *conn = pg_get_pgconn(self); PGresult *result = NULL; VALUE rb_pgresult; - VALUE command, params, in_res_fmt; - VALUE param, param_type, param_value, param_format; - VALUE param_value_tmp; - VALUE sym_type, sym_value, sym_format; - VALUE gc_array; - int i=0; + VALUE command, in_res_fmt; int nParams; - Oid *paramTypes; - char ** paramValues; - int *paramLengths; - int *paramFormats; int resultFormat; + struct query_params_data paramsData; - rb_scan_args(argc, argv, "12", &command, &params, &in_res_fmt); + rb_scan_args(argc, argv, "13", &command, &paramsData.params, &in_res_fmt, &paramsData.typemap); + paramsData.with_types = 1; /* * Handle the edge-case where the caller is coming from #exec, but passed an explict +nil+ * for the second parameter. */ - if ( NIL_P(params) ) { + if ( NIL_P(paramsData.params) ) { return pgconn_exec( 1, argv, self ); - } - - Check_Type(params, T_ARRAY); - - if ( NIL_P(in_res_fmt) ) { - resultFormat = 0; } - else { - resultFormat = NUM2INT(in_res_fmt); - } + pgconn_query_assign_typemap( self, &paramsData ); - gc_array = rb_ary_new(); - rb_gc_register_address(&gc_array); + resultFormat = NIL_P(in_res_fmt) ? 0 : NUM2INT(in_res_fmt); + nParams = alloc_query_params( &paramsData ); - sym_type = ID2SYM(rb_intern("type")); - sym_value = ID2SYM(rb_intern("value")); - sym_format = ID2SYM(rb_intern("format")); - nParams = (int)RARRAY_LEN(params); - paramTypes = ALLOC_N(Oid, nParams); - paramValues = ALLOC_N(char *, nParams); - paramLengths = ALLOC_N(int, nParams); - paramFormats = ALLOC_N(int, nParams); + result = gvl_PQexecParams(conn, StringValueCStr(command), nParams, paramsData.types, + (const char * const *)paramsData.values, paramsData.lengths, paramsData.formats, resultFormat); - for ( i = 0; i < nParams; i++ ) { - param = rb_ary_entry(params, i); - if (TYPE(param) == T_HASH) { - param_type = rb_hash_aref(param, sym_type); - param_value_tmp = rb_hash_aref(param, sym_value); - if(param_value_tmp == Qnil) - param_value = param_value_tmp; - else - param_value = rb_obj_as_string(param_value_tmp); - param_format = rb_hash_aref(param, sym_format); - } - else { - param_type = Qnil; - if(param == Qnil) - param_value = param; - else - param_value = rb_obj_as_string(param); - param_format = Qnil; - } + free_query_params( &paramsData ); - if(param_type == Qnil) - paramTypes[i] = 0; - else - paramTypes[i] = NUM2INT(param_type); - - if(param_value == Qnil) { - paramValues[i] = NULL; - paramLengths[i] = 0; - } - else { - Check_Type(param_value, T_STRING); - /* make sure param_value doesn't get freed by the GC */ - rb_ary_push(gc_array, param_value); - paramValues[i] = StringValuePtr(param_value); - paramLengths[i] = (int)RSTRING_LEN(param_value); - } - - if(param_format == Qnil) - paramFormats[i] = 0; - else - paramFormats[i] = NUM2INT(param_format); - } - - result = gvl_PQexecParams(conn, StringValuePtr(command), nParams, paramTypes, - (const char * const *)paramValues, paramLengths, paramFormats, resultFormat); - - rb_gc_unregister_address(&gc_array); - - xfree(paramTypes); - xfree(paramValues); - xfree(paramLengths); - xfree(paramFormats); - rb_pgresult = pg_new_result(result, self); pg_result_check(rb_pgresult); if (rb_block_given_p()) { return rb_ensure(rb_yield, rb_pgresult, pg_result_clear, rb_pgresult); @@ -1108,18 +1338,17 @@ Check_Type(in_paramtypes, T_ARRAY); nParams = (int)RARRAY_LEN(in_paramtypes); paramTypes = ALLOC_N(Oid, nParams); for(i = 0; i < nParams; i++) { param = rb_ary_entry(in_paramtypes, i); - Check_Type(param, T_FIXNUM); if(param == Qnil) paramTypes[i] = 0; else - paramTypes[i] = NUM2INT(param); + paramTypes[i] = NUM2UINT(param); } } - result = gvl_PQprepare(conn, StringValuePtr(name), StringValuePtr(command), + result = gvl_PQprepare(conn, StringValueCStr(name), StringValueCStr(command), nParams, paramTypes); xfree(paramTypes); rb_pgresult = pg_new_result(result, self); @@ -1127,12 +1356,12 @@ return rb_pgresult; } /* * call-seq: - * conn.exec_prepared(statement_name [, params, result_format ] ) -> PG::Result - * conn.exec_prepared(statement_name [, params, result_format ] ) {|pg_result| block } + * conn.exec_prepared(statement_name [, params, result_format[, type_map]] ) -> PG::Result + * conn.exec_prepared(statement_name [, params, result_format[, type_map]] ) {|pg_result| block } * * Execute prepared named statement specified by _statement_name_. * Returns a PG::Result instance on success. * On failure, it raises a PG::Error. * @@ -1150,104 +1379,49 @@ * to $1, the 1st element is bound to $2, etc. +nil+ is treated as +NULL+. * * The optional +result_format+ should be 0 for text results, 1 * for binary. * + * type_map can be a PG::TypeMap derivation (such as PG::BasicTypeMapForQueries). + * This will type cast the params form various Ruby types before transmission + * based on the encoders defined by the type map. When a type encoder is used + * the format and oid of a given bind parameter are retrieved from the encoder + * instead out of the hash form described above. + * * If the optional code block is given, it will be passed <i>result</i> as an argument, * and the PG::Result object will automatically be cleared when the block terminates. * In this instance, <code>conn.exec_prepared</code> returns the value of the block. */ static VALUE pgconn_exec_prepared(int argc, VALUE *argv, VALUE self) { PGconn *conn = pg_get_pgconn(self); PGresult *result = NULL; VALUE rb_pgresult; - VALUE name, params, in_res_fmt; - VALUE param, param_value, param_format; - VALUE param_value_tmp; - VALUE sym_value, sym_format; - VALUE gc_array; - int i = 0; + VALUE name, in_res_fmt; int nParams; - char ** paramValues; - int *paramLengths; - int *paramFormats; int resultFormat; + struct query_params_data paramsData; - - rb_scan_args(argc, argv, "12", &name, &params, &in_res_fmt); + rb_scan_args(argc, argv, "13", &name, &paramsData.params, &in_res_fmt, &paramsData.typemap); + paramsData.with_types = 0; Check_Type(name, T_STRING); - if(NIL_P(params)) { - params = rb_ary_new2(0); - resultFormat = 0; + if(NIL_P(paramsData.params)) { + paramsData.params = rb_ary_new2(0); } - else { - Check_Type(params, T_ARRAY); - } + pgconn_query_assign_typemap( self, &paramsData ); - if(NIL_P(in_res_fmt)) { - resultFormat = 0; - } - else { - resultFormat = NUM2INT(in_res_fmt); - } + resultFormat = NIL_P(in_res_fmt) ? 0 : NUM2INT(in_res_fmt); + nParams = alloc_query_params( &paramsData ); - gc_array = rb_ary_new(); - rb_gc_register_address(&gc_array); - sym_value = ID2SYM(rb_intern("value")); - sym_format = ID2SYM(rb_intern("format")); - nParams = (int)RARRAY_LEN(params); - paramValues = ALLOC_N(char *, nParams); - paramLengths = ALLOC_N(int, nParams); - paramFormats = ALLOC_N(int, nParams); - for(i = 0; i < nParams; i++) { - param = rb_ary_entry(params, i); - if (TYPE(param) == T_HASH) { - param_value_tmp = rb_hash_aref(param, sym_value); - if(param_value_tmp == Qnil) - param_value = param_value_tmp; - else - param_value = rb_obj_as_string(param_value_tmp); - param_format = rb_hash_aref(param, sym_format); - } - else { - if(param == Qnil) - param_value = param; - else - param_value = rb_obj_as_string(param); - param_format = INT2NUM(0); - } - if(param_value == Qnil) { - paramValues[i] = NULL; - paramLengths[i] = 0; - } - else { - Check_Type(param_value, T_STRING); - /* make sure param_value doesn't get freed by the GC */ - rb_ary_push(gc_array, param_value); - paramValues[i] = StringValuePtr(param_value); - paramLengths[i] = (int)RSTRING_LEN(param_value); - } - - if(param_format == Qnil) - paramFormats[i] = 0; - else - paramFormats[i] = NUM2INT(param_format); - } - - result = gvl_PQexecPrepared(conn, StringValuePtr(name), nParams, - (const char * const *)paramValues, paramLengths, paramFormats, + result = gvl_PQexecPrepared(conn, StringValueCStr(name), nParams, + (const char * const *)paramsData.values, paramsData.lengths, paramsData.formats, resultFormat); - rb_gc_unregister_address(&gc_array); + free_query_params( &paramsData ); - xfree(paramValues); - xfree(paramLengths); - xfree(paramFormats); - rb_pgresult = pg_new_result(result, self); pg_result_check(rb_pgresult); if (rb_block_given_p()) { return rb_ensure(rb_yield, rb_pgresult, pg_result_clear, rb_pgresult); @@ -1272,11 +1446,11 @@ if(stmt_name == Qnil) { stmt = NULL; } else { Check_Type(stmt_name, T_STRING); - stmt = StringValuePtr(stmt_name); + stmt = StringValueCStr(stmt_name); } result = gvl_PQdescribePrepared(conn, stmt); rb_pgresult = pg_new_result(result, self); pg_result_check(rb_pgresult); return rb_pgresult; @@ -1300,11 +1474,11 @@ if(stmt_name == Qnil) { stmt = NULL; } else { Check_Type(stmt_name, T_STRING); - stmt = StringValuePtr(stmt_name); + stmt = StringValueCStr(stmt_name); } result = gvl_PQdescribePortal(conn, stmt); rb_pgresult = pg_new_result(result, self); pg_result_check(rb_pgresult); return rb_pgresult; @@ -1362,13 +1536,10 @@ { char *escaped; size_t size; int error; VALUE result; -#ifdef M17N_SUPPORTED - rb_encoding* enc; -#endif Check_Type(string, T_STRING); escaped = ALLOC_N(char, RSTRING_LEN(string) * 2 + 1); if(rb_obj_class(self) == rb_cPGconn) { @@ -1377,25 +1548,17 @@ if(error) { xfree(escaped); rb_raise(rb_ePGerror, "%s", PQerrorMessage(pg_get_pgconn(self))); } } else { - size = PQescapeString(escaped, RSTRING_PTR(string), (int)RSTRING_LEN(string)); + size = PQescapeString(escaped, RSTRING_PTR(string), RSTRING_LENINT(string)); } result = rb_str_new(escaped, size); xfree(escaped); OBJ_INFECT(result, string); + PG_ENCODING_SET_NOCHECK(result, ENCODING_GET( rb_obj_class(self) == rb_cPGconn ? self : string )); -#ifdef M17N_SUPPORTED - if ( rb_obj_class(self) == rb_cPGconn ) { - enc = pg_conn_enc_get( pg_get_pgconn(self) ); - } else { - enc = rb_enc_get(string); - } - rb_enc_associate(result, enc); -#endif - return result; } /* * call-seq: @@ -1462,11 +1625,11 @@ VALUE ret; UNUSED( self ); Check_Type(str, T_STRING); - from = (unsigned char*)StringValuePtr(str); + from = (unsigned char*)StringValueCStr(str); to = PQunescapeBytea(from, &to_len); ret = rb_str_new((char*)to, to_len); OBJ_INFECT(ret, str); @@ -1500,15 +1663,12 @@ return Qnil; } result = rb_str_new2(escaped); PQfreemem(escaped); OBJ_INFECT(result, string); + PG_ENCODING_SET_NOCHECK(result, ENCODING_GET(self)); -#ifdef M17N_SUPPORTED - rb_enc_associate(result, pg_conn_enc_get( pg_get_pgconn(self) )); -#endif - return result; } #endif #ifdef HAVE_PQESCAPEIDENTIFIER @@ -1540,15 +1700,12 @@ return Qnil; } result = rb_str_new2(escaped); PQfreemem(escaped); OBJ_INFECT(result, string); + PG_ENCODING_SET_NOCHECK(result, ENCODING_GET(self)); -#ifdef M17N_SUPPORTED - rb_enc_associate(result, pg_conn_enc_get( pg_get_pgconn(self) )); -#endif - return result; } #endif #ifdef HAVE_PQSETSINGLEROWMODE @@ -1606,11 +1763,11 @@ } #endif /* * call-seq: - * conn.send_query(sql [, params, result_format ] ) -> nil + * conn.send_query(sql [, params, result_format[, type_map ]] ) -> nil * * Sends SQL query request specified by _sql_ to PostgreSQL for * asynchronous processing, and immediately returns. * On failure, it raises a PG::Error. * @@ -1634,118 +1791,56 @@ * * For example: "SELECT $1::int" * * The optional +result_format+ should be 0 for text results, 1 * for binary. + * + * type_map can be a PG::TypeMap derivation (such as PG::BasicTypeMapForQueries). + * This will type cast the params form various Ruby types before transmission + * based on the encoders defined by the type map. When a type encoder is used + * the format and oid of a given bind parameter are retrieved from the encoder + * instead out of the hash form described above. + * */ static VALUE pgconn_send_query(int argc, VALUE *argv, VALUE self) { PGconn *conn = pg_get_pgconn(self); int result; - VALUE command, params, in_res_fmt; - VALUE param, param_type, param_value, param_format; - VALUE param_value_tmp; - VALUE sym_type, sym_value, sym_format; - VALUE gc_array; + VALUE command, in_res_fmt; VALUE error; - int i=0; int nParams; - Oid *paramTypes; - char ** paramValues; - int *paramLengths; - int *paramFormats; int resultFormat; + struct query_params_data paramsData; - rb_scan_args(argc, argv, "12", &command, &params, &in_res_fmt); + rb_scan_args(argc, argv, "13", &command, &paramsData.params, &in_res_fmt, &paramsData.typemap); + paramsData.with_types = 1; Check_Type(command, T_STRING); /* If called with no parameters, use PQsendQuery */ - if(NIL_P(params)) { - if(gvl_PQsendQuery(conn,StringValuePtr(command)) == 0) { + if(NIL_P(paramsData.params)) { + if(gvl_PQsendQuery(conn,StringValueCStr(command)) == 0) { error = rb_exc_new2(rb_eUnableToSend, PQerrorMessage(conn)); rb_iv_set(error, "@connection", self); rb_exc_raise(error); } return Qnil; } /* If called with parameters, and optionally result_format, * use PQsendQueryParams */ - Check_Type(params, T_ARRAY); - if(NIL_P(in_res_fmt)) { - resultFormat = 0; - } - else { - resultFormat = NUM2INT(in_res_fmt); - } + pgconn_query_assign_typemap( self, &paramsData ); + resultFormat = NIL_P(in_res_fmt) ? 0 : NUM2INT(in_res_fmt); + nParams = alloc_query_params( &paramsData ); - gc_array = rb_ary_new(); - rb_gc_register_address(&gc_array); - sym_type = ID2SYM(rb_intern("type")); - sym_value = ID2SYM(rb_intern("value")); - sym_format = ID2SYM(rb_intern("format")); - nParams = (int)RARRAY_LEN(params); - paramTypes = ALLOC_N(Oid, nParams); - paramValues = ALLOC_N(char *, nParams); - paramLengths = ALLOC_N(int, nParams); - paramFormats = ALLOC_N(int, nParams); - for(i = 0; i < nParams; i++) { - param = rb_ary_entry(params, i); - if (TYPE(param) == T_HASH) { - param_type = rb_hash_aref(param, sym_type); - param_value_tmp = rb_hash_aref(param, sym_value); - if(param_value_tmp == Qnil) - param_value = param_value_tmp; - else - param_value = rb_obj_as_string(param_value_tmp); - param_format = rb_hash_aref(param, sym_format); - } - else { - param_type = INT2NUM(0); - if(param == Qnil) - param_value = param; - else - param_value = rb_obj_as_string(param); - param_format = INT2NUM(0); - } + result = gvl_PQsendQueryParams(conn, StringValueCStr(command), nParams, paramsData.types, + (const char * const *)paramsData.values, paramsData.lengths, paramsData.formats, resultFormat); - if(param_type == Qnil) - paramTypes[i] = 0; - else - paramTypes[i] = NUM2INT(param_type); + free_query_params( &paramsData ); - if(param_value == Qnil) { - paramValues[i] = NULL; - paramLengths[i] = 0; - } - else { - Check_Type(param_value, T_STRING); - /* make sure param_value doesn't get freed by the GC */ - rb_ary_push(gc_array, param_value); - paramValues[i] = StringValuePtr(param_value); - paramLengths[i] = (int)RSTRING_LEN(param_value); - } - - if(param_format == Qnil) - paramFormats[i] = 0; - else - paramFormats[i] = NUM2INT(param_format); - } - - result = gvl_PQsendQueryParams(conn, StringValuePtr(command), nParams, paramTypes, - (const char * const *)paramValues, paramLengths, paramFormats, resultFormat); - - rb_gc_unregister_address(&gc_array); - - xfree(paramTypes); - xfree(paramValues); - xfree(paramLengths); - xfree(paramFormats); - if(result == 0) { error = rb_exc_new2(rb_eUnableToSend, PQerrorMessage(conn)); rb_iv_set(error, "@connection", self); rb_exc_raise(error); } @@ -1792,18 +1887,17 @@ Check_Type(in_paramtypes, T_ARRAY); nParams = (int)RARRAY_LEN(in_paramtypes); paramTypes = ALLOC_N(Oid, nParams); for(i = 0; i < nParams; i++) { param = rb_ary_entry(in_paramtypes, i); - Check_Type(param, T_FIXNUM); if(param == Qnil) paramTypes[i] = 0; else - paramTypes[i] = NUM2INT(param); + paramTypes[i] = NUM2UINT(param); } } - result = gvl_PQsendPrepare(conn, StringValuePtr(name), StringValuePtr(command), + result = gvl_PQsendPrepare(conn, StringValueCStr(name), StringValueCStr(command), nParams, paramTypes); xfree(paramTypes); if(result == 0) { @@ -1814,11 +1908,11 @@ return Qnil; } /* * call-seq: - * conn.send_query_prepared( statement_name [, params, result_format ] ) + * conn.send_query_prepared( statement_name [, params, result_format[, type_map ]] ) * -> nil * * Execute prepared named statement specified by _statement_name_ * asynchronously, and returns immediately. * On failure, it raises a PG::Error. @@ -1836,101 +1930,48 @@ * inside the SQL query. The 0th element of the +params+ array is bound * to $1, the 1st element is bound to $2, etc. +nil+ is treated as +NULL+. * * The optional +result_format+ should be 0 for text results, 1 * for binary. + * + * type_map can be a PG::TypeMap derivation (such as PG::BasicTypeMapForQueries). + * This will type cast the params form various Ruby types before transmission + * based on the encoders defined by the type map. When a type encoder is used + * the format and oid of a given bind parameter are retrieved from the encoder + * instead out of the hash form described above. + * */ static VALUE pgconn_send_query_prepared(int argc, VALUE *argv, VALUE self) { PGconn *conn = pg_get_pgconn(self); int result; - VALUE name, params, in_res_fmt; - VALUE param, param_value, param_format; - VALUE param_value_tmp; - VALUE sym_value, sym_format; - VALUE gc_array; + VALUE name, in_res_fmt; VALUE error; - int i = 0; int nParams; - char ** paramValues; - int *paramLengths; - int *paramFormats; int resultFormat; + struct query_params_data paramsData; - rb_scan_args(argc, argv, "12", &name, &params, &in_res_fmt); + rb_scan_args(argc, argv, "13", &name, &paramsData.params, &in_res_fmt, &paramsData.typemap); + paramsData.with_types = 0; Check_Type(name, T_STRING); - if(NIL_P(params)) { - params = rb_ary_new2(0); + if(NIL_P(paramsData.params)) { + paramsData.params = rb_ary_new2(0); resultFormat = 0; } - else { - Check_Type(params, T_ARRAY); - } + pgconn_query_assign_typemap( self, &paramsData ); - if(NIL_P(in_res_fmt)) { - resultFormat = 0; - } - else { - resultFormat = NUM2INT(in_res_fmt); - } + resultFormat = NIL_P(in_res_fmt) ? 0 : NUM2INT(in_res_fmt); + nParams = alloc_query_params( &paramsData ); - gc_array = rb_ary_new(); - rb_gc_register_address(&gc_array); - sym_value = ID2SYM(rb_intern("value")); - sym_format = ID2SYM(rb_intern("format")); - nParams = (int)RARRAY_LEN(params); - paramValues = ALLOC_N(char *, nParams); - paramLengths = ALLOC_N(int, nParams); - paramFormats = ALLOC_N(int, nParams); - for(i = 0; i < nParams; i++) { - param = rb_ary_entry(params, i); - if (TYPE(param) == T_HASH) { - param_value_tmp = rb_hash_aref(param, sym_value); - if(param_value_tmp == Qnil) - param_value = param_value_tmp; - else - param_value = rb_obj_as_string(param_value_tmp); - param_format = rb_hash_aref(param, sym_format); - } - else { - if(param == Qnil) - param_value = param; - else - param_value = rb_obj_as_string(param); - param_format = INT2NUM(0); - } - - if(param_value == Qnil) { - paramValues[i] = NULL; - paramLengths[i] = 0; - } - else { - Check_Type(param_value, T_STRING); - /* make sure param_value doesn't get freed by the GC */ - rb_ary_push(gc_array, param_value); - paramValues[i] = StringValuePtr(param_value); - paramLengths[i] = (int)RSTRING_LEN(param_value); - } - - if(param_format == Qnil) - paramFormats[i] = 0; - else - paramFormats[i] = NUM2INT(param_format); - } - - result = gvl_PQsendQueryPrepared(conn, StringValuePtr(name), nParams, - (const char * const *)paramValues, paramLengths, paramFormats, + result = gvl_PQsendQueryPrepared(conn, StringValueCStr(name), nParams, + (const char * const *)paramsData.values, paramsData.lengths, paramsData.formats, resultFormat); - rb_gc_unregister_address(&gc_array); + free_query_params( &paramsData ); - xfree(paramValues); - xfree(paramLengths); - xfree(paramFormats); - if(result == 0) { error = rb_exc_new2(rb_eUnableToSend, PQerrorMessage(conn)); rb_iv_set(error, "@connection", self); rb_exc_raise(error); } @@ -1948,11 +1989,11 @@ pgconn_send_describe_prepared(VALUE self, VALUE stmt_name) { VALUE error; PGconn *conn = pg_get_pgconn(self); /* returns 0 on failure */ - if(gvl_PQsendDescribePrepared(conn,StringValuePtr(stmt_name)) == 0) { + if(gvl_PQsendDescribePrepared(conn,StringValueCStr(stmt_name)) == 0) { error = rb_exc_new2(rb_eUnableToSend, PQerrorMessage(conn)); rb_iv_set(error, "@connection", self); rb_exc_raise(error); } return Qnil; @@ -1970,11 +2011,11 @@ pgconn_send_describe_portal(VALUE self, VALUE portal) { VALUE error; PGconn *conn = pg_get_pgconn(self); /* returns 0 on failure */ - if(gvl_PQsendDescribePortal(conn,StringValuePtr(portal)) == 0) { + if(gvl_PQsendDescribePortal(conn,StringValueCStr(portal)) == 0) { error = rb_exc_new2(rb_eUnableToSend, PQerrorMessage(conn)); rb_iv_set(error, "@connection", self); rb_exc_raise(error); } return Qnil; @@ -2195,14 +2236,12 @@ hash = rb_hash_new(); relname = rb_tainted_str_new2(notification->relname); be_pid = INT2NUM(notification->be_pid); extra = rb_tainted_str_new2(notification->extra); -#ifdef M17N_SUPPORTED - ENCODING_SET( relname, rb_enc_to_index(pg_conn_enc_get( conn )) ); - ENCODING_SET( extra, rb_enc_to_index(pg_conn_enc_get( conn )) ); -#endif + PG_ENCODING_SET_NOCHECK( relname, ENCODING_GET(self) ); + PG_ENCODING_SET_NOCHECK( extra, ENCODING_GET(self) ); rb_hash_aset(hash, sym_relname, relname); rb_hash_aset(hash, sym_be_pid, be_pid); rb_hash_aset(hash, sym_extra, extra); @@ -2467,20 +2506,16 @@ /* Return nil if the select timed out */ if ( !pnotification ) return Qnil; relname = rb_tainted_str_new2( pnotification->relname ); -#ifdef M17N_SUPPORTED - ENCODING_SET( relname, rb_enc_to_index(pg_conn_enc_get( conn )) ); -#endif + PG_ENCODING_SET_NOCHECK( relname, ENCODING_GET(self) ); be_pid = INT2NUM( pnotification->be_pid ); #ifdef HAVE_ST_NOTIFY_EXTRA if ( *pnotification->extra ) { extra = rb_tainted_str_new2( pnotification->extra ); -#ifdef M17N_SUPPORTED - ENCODING_SET( extra, rb_enc_to_index(pg_conn_enc_get( conn )) ); -#endif + PG_ENCODING_SET_NOCHECK( extra, ENCODING_GET(self) ); } #endif PQfreemem( pnotification ); if ( rb_block_given_p() ) @@ -2490,37 +2525,81 @@ } /* * call-seq: - * conn.put_copy_data( buffer ) -> Boolean + * conn.put_copy_data( buffer [, encoder] ) -> Boolean * * Transmits _buffer_ as copy data to the server. * Returns true if the data was sent, false if it was * not sent (false is only possible if the connection * is in nonblocking mode, and this command would block). * + * encoder can be a PG::Coder derivation (typically PG::TestEncoder::CopyRow). + * This encodes the received data fields from an Array of Strings. Optionally + * the encoder can type cast the fields form various Ruby types in one step, + * if PG::TestEncoder::CopyRow#type_map is set accordingly. + * * Raises an exception if an error occurs. * * See also #copy_data. * */ static VALUE -pgconn_put_copy_data(self, buffer) - VALUE self, buffer; +pgconn_put_copy_data(int argc, VALUE *argv, VALUE self) { int ret; - VALUE error; - PGconn *conn = pg_get_pgconn(self); + int len; + t_pg_connection *this = pg_get_connection_safe( self ); + VALUE value; + VALUE buffer = Qnil; + VALUE encoder; + VALUE intermediate; + t_pg_coder *p_coder = NULL; + + rb_scan_args( argc, argv, "11", &value, &encoder ); + + if( NIL_P(encoder) ){ + if( NIL_P(this->encoder_for_put_copy_data) ){ + buffer = value; + } else { + p_coder = DATA_PTR( this->encoder_for_put_copy_data ); + } + } else if( rb_obj_is_kind_of(encoder, rb_cPG_Coder) ) { + Data_Get_Struct( encoder, t_pg_coder, p_coder ); + } else { + rb_raise( rb_eTypeError, "wrong encoder type %s (expected some kind of PG::Coder)", + rb_obj_classname( encoder ) ); + } + + if( p_coder ){ + t_pg_coder_enc_func enc_func; + + enc_func = pg_coder_enc_func( p_coder ); + len = enc_func( p_coder, value, NULL, &intermediate ); + + if( len == -1 ){ + /* The intermediate value is a String that can be used directly. */ + buffer = intermediate; + } else { + buffer = rb_str_new(NULL, len); + len = enc_func( p_coder, value, RSTRING_PTR(buffer), &intermediate); + rb_str_set_len( buffer, len ); + } + } + Check_Type(buffer, T_STRING); - ret = gvl_PQputCopyData(conn, RSTRING_PTR(buffer), (int)RSTRING_LEN(buffer)); + ret = gvl_PQputCopyData(this->pgconn, RSTRING_PTR(buffer), RSTRING_LENINT(buffer)); if(ret == -1) { - error = rb_exc_new2(rb_ePGerror, PQerrorMessage(conn)); + VALUE error = rb_exc_new2(rb_ePGerror, PQerrorMessage(this->pgconn)); rb_iv_set(error, "@connection", self); rb_exc_raise(error); } + RB_GC_GUARD(intermediate); + RB_GC_GUARD(buffer); + return (ret) ? Qtrue : Qfalse; } /* * call-seq: @@ -2546,11 +2625,11 @@ PGconn *conn = pg_get_pgconn(self); if (rb_scan_args(argc, argv, "01", &str) == 0) error_message = NULL; else - error_message = StringValuePtr(str); + error_message = StringValueCStr(str); ret = gvl_PQputCopyEnd(conn, error_message); if(ret == -1) { error = rb_exc_new2(rb_ePGerror, PQerrorMessage(conn)); rb_iv_set(error, "@connection", self); @@ -2559,50 +2638,71 @@ return (ret) ? Qtrue : Qfalse; } /* * call-seq: - * conn.get_copy_data( [ async = false ] ) -> String + * conn.get_copy_data( [ async = false [, decoder = nil ]] ) -> String * * Return a string containing one row of data, +nil+ * if the copy is done, or +false+ if the call would * block (only possible if _async_ is true). * + * decoder can be a PG::Coder derivation (typically PG::TestDecoder::CopyRow). + * This decodes the received data fields as Array of Strings. Optionally + * the decoder can type cast the fields to various Ruby types in one step, + * if PG::TestDecoder::CopyRow#type_map is set accordingly. + * * See also #copy_data. * */ static VALUE pgconn_get_copy_data(int argc, VALUE *argv, VALUE self ) { VALUE async_in; VALUE error; - VALUE result_str; + VALUE result; int ret; - int async; char *buffer; - PGconn *conn = pg_get_pgconn(self); + VALUE decoder; + t_pg_coder *p_coder = NULL; + t_pg_connection *this = pg_get_connection_safe( self ); - if (rb_scan_args(argc, argv, "01", &async_in) == 0) - async = 0; - else - async = (async_in == Qfalse || async_in == Qnil) ? 0 : 1; + rb_scan_args(argc, argv, "02", &async_in, &decoder); - ret = gvl_PQgetCopyData(conn, &buffer, async); + if( NIL_P(decoder) ){ + if( !NIL_P(this->decoder_for_get_copy_data) ){ + p_coder = DATA_PTR( this->decoder_for_get_copy_data ); + } + } else if( rb_obj_is_kind_of(decoder, rb_cPG_Coder) ) { + Data_Get_Struct( decoder, t_pg_coder, p_coder ); + } else { + rb_raise( rb_eTypeError, "wrong decoder type %s (expected some kind of PG::Coder)", + rb_obj_classname( decoder ) ); + } + + ret = gvl_PQgetCopyData(this->pgconn, &buffer, RTEST(async_in)); if(ret == -2) { /* error */ - error = rb_exc_new2(rb_ePGerror, PQerrorMessage(conn)); + error = rb_exc_new2(rb_ePGerror, PQerrorMessage(this->pgconn)); rb_iv_set(error, "@connection", self); rb_exc_raise(error); } if(ret == -1) { /* No data left */ return Qnil; } if(ret == 0) { /* would block */ return Qfalse; } - result_str = rb_tainted_str_new(buffer, ret); + + if( p_coder ){ + t_pg_coder_dec_func dec_func = pg_coder_dec_func( p_coder, p_coder->format ); + result = dec_func( p_coder, buffer, ret, 0, 0, ENCODING_GET(self) ); + } else { + result = rb_tainted_str_new(buffer, ret); + } + PQfreemem(buffer); - return result_str; + return result; } /* * call-seq: * conn.set_error_verbosity( verbosity ) -> Fixnum @@ -2635,10 +2735,11 @@ { VALUE fileno; FILE *new_fp; int old_fd, new_fd; VALUE new_file; + t_pg_connection *this = pg_get_connection_safe( self ); if(rb_respond_to(stream,rb_intern("fileno")) == Qfalse) rb_raise(rb_eArgError, "stream does not respond to method: fileno"); fileno = rb_funcall(stream, rb_intern("fileno"), 0); @@ -2657,13 +2758,13 @@ if(new_fp == NULL) rb_raise(rb_eArgError, "stream is not writable"); new_file = rb_funcall(rb_cIO, rb_intern("new"), 1, INT2NUM(new_fd)); - rb_iv_set(self, "@trace_stream", new_file); + this->trace_stream = new_file; - PQtrace(pg_get_pgconn(self), new_fp); + PQtrace(this->pgconn, new_fp); return Qnil; } /* * call-seq: @@ -2672,37 +2773,34 @@ * Disables the message tracing. */ static VALUE pgconn_untrace(VALUE self) { - VALUE trace_stream; - PQuntrace(pg_get_pgconn(self)); - trace_stream = rb_iv_get(self, "@trace_stream"); - rb_funcall(trace_stream, rb_intern("close"), 0); - rb_iv_set(self, "@trace_stream", Qnil); + t_pg_connection *this = pg_get_connection_safe( self ); + + PQuntrace(this->pgconn); + rb_funcall(this->trace_stream, rb_intern("close"), 0); + this->trace_stream = Qnil; return Qnil; } /* * Notice callback proxy function -- delegate the callback to the * currently-registered Ruby notice_receiver object. */ void -notice_receiver_proxy(void *arg, const PGresult *result) +notice_receiver_proxy(void *arg, const PGresult *pgresult) { - VALUE proc; VALUE self = (VALUE)arg; + t_pg_connection *this = pg_get_connection( self ); - if ((proc = rb_iv_get(self, "@notice_receiver")) != Qnil) { - VALUE val = Data_Wrap_Struct(rb_cPGresult, NULL, NULL, (PGresult*)result); -#ifdef M17N_SUPPORTED - PGconn *conn = pg_get_pgconn( self ); - rb_encoding *enc = pg_conn_enc_get( conn ); - ENCODING_SET( val, rb_enc_to_index(enc) ); -#endif - rb_funcall(proc, rb_intern("call"), 1, val); + if (this->notice_receiver != Qnil) { + VALUE result = pg_new_result_autoclear( (PGresult *)pgresult, self ); + + rb_funcall(this->notice_receiver, rb_intern("call"), 1, result); + pg_result_clear( result ); } return; } /* @@ -2736,31 +2834,31 @@ */ static VALUE pgconn_set_notice_receiver(VALUE self) { VALUE proc, old_proc; - PGconn *conn = pg_get_pgconn(self); + t_pg_connection *this = pg_get_connection_safe( self ); /* If default_notice_receiver is unset, assume that the current * notice receiver is the default, and save it to a global variable. * This should not be a problem because the default receiver is * always the same, so won't vary among connections. */ if(default_notice_receiver == NULL) - default_notice_receiver = PQsetNoticeReceiver(conn, NULL, NULL); + default_notice_receiver = PQsetNoticeReceiver(this->pgconn, NULL, NULL); - old_proc = rb_iv_get(self, "@notice_receiver"); + old_proc = this->notice_receiver; if( rb_block_given_p() ) { proc = rb_block_proc(); - PQsetNoticeReceiver(conn, gvl_notice_receiver_proxy, (void *)self); + PQsetNoticeReceiver(this->pgconn, gvl_notice_receiver_proxy, (void *)self); } else { /* if no block is given, set back to default */ proc = Qnil; - PQsetNoticeReceiver(conn, default_notice_receiver, NULL); + PQsetNoticeReceiver(this->pgconn, default_notice_receiver, NULL); } - rb_iv_set(self, "@notice_receiver", proc); + this->notice_receiver = proc; return old_proc; } /* @@ -2768,21 +2866,17 @@ * currently-registered Ruby notice_processor object. */ void notice_processor_proxy(void *arg, const char *message) { - VALUE proc; VALUE self = (VALUE)arg; + t_pg_connection *this = pg_get_connection( self ); - if ((proc = rb_iv_get(self, "@notice_processor")) != Qnil) { + if (this->notice_receiver != Qnil) { VALUE message_str = rb_tainted_str_new2(message); -#ifdef M17N_SUPPORTED - PGconn *conn = pg_get_pgconn( self ); - rb_encoding *enc = pg_conn_enc_get( conn ); - ENCODING_SET( message_str, rb_enc_to_index(enc) ); -#endif - rb_funcall(proc, rb_intern("call"), 1, message_str); + PG_ENCODING_SET_NOCHECK( message_str, ENCODING_GET(self) ); + rb_funcall(this->notice_receiver, rb_intern("call"), 1, message_str); } return; } /* @@ -2800,31 +2894,31 @@ */ static VALUE pgconn_set_notice_processor(VALUE self) { VALUE proc, old_proc; - PGconn *conn = pg_get_pgconn(self); + t_pg_connection *this = pg_get_connection_safe( self ); /* If default_notice_processor is unset, assume that the current * notice processor is the default, and save it to a global variable. * This should not be a problem because the default processor is * always the same, so won't vary among connections. */ if(default_notice_processor == NULL) - default_notice_processor = PQsetNoticeProcessor(conn, NULL, NULL); + default_notice_processor = PQsetNoticeProcessor(this->pgconn, NULL, NULL); - old_proc = rb_iv_get(self, "@notice_processor"); + old_proc = this->notice_receiver; if( rb_block_given_p() ) { proc = rb_block_proc(); - PQsetNoticeProcessor(conn, gvl_notice_processor_proxy, (void *)self); + PQsetNoticeProcessor(this->pgconn, gvl_notice_processor_proxy, (void *)self); } else { /* if no block is given, set back to default */ proc = Qnil; - PQsetNoticeProcessor(conn, default_notice_processor, NULL); + PQsetNoticeProcessor(this->pgconn, default_notice_processor, NULL); } - rb_iv_set(self, "@notice_processor", proc); + this->notice_receiver = proc; return old_proc; } /* @@ -2852,13 +2946,16 @@ { PGconn *conn = pg_get_pgconn( self ); Check_Type(str, T_STRING); - if ( (PQsetClientEncoding(conn, StringValuePtr(str))) == -1 ) { - rb_raise(rb_ePGerror, "invalid encoding name: %s",StringValuePtr(str)); + if ( (PQsetClientEncoding(conn, StringValueCStr(str))) == -1 ) { + rb_raise(rb_ePGerror, "invalid encoding name: %s",StringValueCStr(str)); } +#ifdef M17N_SUPPORTED + pgconn_set_internal_encoding_index( self ); +#endif return Qnil; } /* @@ -2933,40 +3030,28 @@ char *str = StringValuePtr(in_str); /* result size at most NAMEDATALEN*2 plus surrounding * double-quotes. */ char buffer[NAMEDATALEN*2+2]; unsigned int i=0,j=0; -#ifdef M17N_SUPPORTED - rb_encoding* enc; -#endif + unsigned int str_len = RSTRING_LENINT(in_str); - UNUSED( self ); - - if(strlen(str) >= NAMEDATALEN) { + if(str_len >= NAMEDATALEN) { rb_raise(rb_eArgError, "Input string is longer than NAMEDATALEN-1 (%d)", NAMEDATALEN-1); } buffer[j++] = '"'; - for(i = 0; i < strlen(str) && str[i]; i++) { + for(i = 0; i < str_len && str[i]; i++) { if(str[i] == '"') buffer[j++] = '"'; buffer[j++] = str[i]; } buffer[j++] = '"'; ret = rb_str_new(buffer,j); OBJ_INFECT(ret, in_str); + PG_ENCODING_SET_NOCHECK(ret, ENCODING_GET( rb_obj_class(self) == rb_cPGconn ? self : in_str )); -#ifdef M17N_SUPPORTED - if ( rb_obj_class(self) == rb_cPGconn ) { - enc = pg_conn_enc_get( pg_get_pgconn(self) ); - } else { - enc = rb_enc_get(in_str); - } - rb_enc_associate(ret, enc); -#endif - return ret; } static void * @@ -3115,11 +3200,11 @@ lo_oid = lo_creat(conn, mode); if (lo_oid == 0) rb_raise(rb_ePGerror, "lo_creat failed"); - return INT2FIX(lo_oid); + return UINT2NUM(lo_oid); } /* * call-seq: * conn.lo_create( oid ) -> Fixnum @@ -3130,17 +3215,17 @@ static VALUE pgconn_locreate(VALUE self, VALUE in_lo_oid) { Oid ret, lo_oid; PGconn *conn = pg_get_pgconn(self); - lo_oid = NUM2INT(in_lo_oid); + lo_oid = NUM2UINT(in_lo_oid); ret = lo_create(conn, lo_oid); if (ret == InvalidOid) rb_raise(rb_ePGerror, "lo_create failed"); - return INT2FIX(ret); + return UINT2NUM(ret); } /* * call-seq: * conn.lo_import(file) -> Fixnum @@ -3156,15 +3241,15 @@ PGconn *conn = pg_get_pgconn(self); Check_Type(filename, T_STRING); - lo_oid = lo_import(conn, StringValuePtr(filename)); + lo_oid = lo_import(conn, StringValueCStr(filename)); if (lo_oid == 0) { rb_raise(rb_ePGerror, "%s", PQerrorMessage(conn)); } - return INT2FIX(lo_oid); + return UINT2NUM(lo_oid); } /* * call-seq: * conn.lo_export( oid, file ) -> nil @@ -3173,19 +3258,16 @@ */ static VALUE pgconn_loexport(VALUE self, VALUE lo_oid, VALUE filename) { PGconn *conn = pg_get_pgconn(self); - int oid; + Oid oid; Check_Type(filename, T_STRING); - oid = NUM2INT(lo_oid); - if (oid < 0) { - rb_raise(rb_ePGerror, "invalid large object oid %d",oid); - } + oid = NUM2UINT(lo_oid); - if (lo_export(conn, oid, StringValuePtr(filename)) < 0) { + if (lo_export(conn, oid, StringValueCStr(filename)) < 0) { rb_raise(rb_ePGerror, "%s", PQerrorMessage(conn)); } return Qnil; } @@ -3206,11 +3288,11 @@ int fd, mode; VALUE nmode, selfid; PGconn *conn = pg_get_pgconn(self); rb_scan_args(argc, argv, "11", &selfid, &nmode); - lo_oid = NUM2INT(selfid); + lo_oid = NUM2UINT(selfid); if(NIL_P(nmode)) mode = INV_READ; else mode = NUM2INT(nmode); @@ -3373,24 +3455,29 @@ */ static VALUE pgconn_lounlink(VALUE self, VALUE in_oid) { PGconn *conn = pg_get_pgconn(self); - int oid = NUM2INT(in_oid); + Oid oid = NUM2UINT(in_oid); - if (oid < 0) - rb_raise(rb_ePGerror, "invalid oid %d",oid); - if(lo_unlink(conn,oid) < 0) rb_raise(rb_ePGerror,"lo_unlink failed"); return Qnil; } #ifdef M17N_SUPPORTED +void +pgconn_set_internal_encoding_index( VALUE self ) +{ + PGconn *conn = pg_get_pgconn(self); + rb_encoding *enc = pg_conn_enc_get( conn ); + PG_ENCODING_SET_NOCHECK( self, rb_enc_to_index(enc)); +} + /* * call-seq: * conn.internal_encoding -> Encoding * * defined in Ruby 1.9 or later. @@ -3427,15 +3514,16 @@ * * +nil+ - sets the client_encoding to SQL_ASCII. */ static VALUE pgconn_internal_encoding_set(VALUE self, VALUE enc) { + VALUE enc_inspect; if (NIL_P(enc)) { pgconn_set_client_encoding( self, rb_usascii_str_new_cstr("SQL_ASCII") ); return enc; } - else if ( TYPE(enc) == T_STRING && strcasecmp("JOHAB", RSTRING_PTR(enc)) == 0 ) { + else if ( TYPE(enc) == T_STRING && strcasecmp("JOHAB", StringValueCStr(enc)) == 0 ) { pgconn_set_client_encoding(self, rb_usascii_str_new_cstr("JOHAB")); return enc; } else { rb_encoding *rbenc = rb_to_encoding( enc ); @@ -3444,14 +3532,16 @@ if ( PQsetClientEncoding(pg_get_pgconn( self ), name) == -1 ) { VALUE server_encoding = pgconn_external_encoding( self ); rb_raise( rb_eEncCompatError, "incompatible character encodings: %s and %s", rb_enc_name(rb_to_encoding(server_encoding)), name ); } + pgconn_set_internal_encoding_index( self ); return enc; } - rb_raise( rb_ePGerror, "unknown encoding: %s", RSTRING_PTR(rb_inspect(enc)) ); + enc_inspect = rb_inspect(enc); + rb_raise( rb_ePGerror, "unknown encoding: %s", StringValueCStr(enc_inspect) ); return Qnil; } @@ -3464,25 +3554,22 @@ * The <tt>SQL_ASCII</tt> encoding is mapped to to <tt>ASCII_8BIT</tt>. */ static VALUE pgconn_external_encoding(VALUE self) { - PGconn *conn = pg_get_pgconn( self ); - VALUE encoding = rb_iv_get( self, "@external_encoding" ); + t_pg_connection *this = pg_get_connection_safe( self ); rb_encoding *enc = NULL; const char *pg_encname = NULL; /* Use cached value if found */ - if ( RTEST(encoding) ) return encoding; + if ( RTEST(this->external_encoding) ) return this->external_encoding; - pg_encname = PQparameterStatus( conn, "server_encoding" ); + pg_encname = PQparameterStatus( this->pgconn, "server_encoding" ); enc = pg_get_pg_encname_as_rb_encoding( pg_encname ); - encoding = rb_enc_from_encoding( enc ); + this->external_encoding = rb_enc_from_encoding( enc ); - rb_iv_set( self, "@external_encoding", encoding ); - - return encoding; + return this->external_encoding; } /* @@ -3503,24 +3590,224 @@ if (( enc = rb_default_internal_encoding() )) { encname = pg_get_rb_encoding_as_pg_encoding( enc ); if ( PQsetClientEncoding(conn, encname) != 0 ) rb_warn( "Failed to set the default_internal encoding to %s: '%s'", encname, PQerrorMessage(conn) ); + pgconn_set_internal_encoding_index( self ); return rb_enc_from_encoding( enc ); } else { + pgconn_set_internal_encoding_index( self ); return Qnil; } } #endif /* M17N_SUPPORTED */ +/* + * call-seq: + * res.type_map_for_queries = typemap + * + * Set the default TypeMap that is used for type casts of query bind parameters. + * + * +typemap+ can be: + * * a kind of PG::TypeMap + * * +nil+ - to type cast all query params by #to_str. + * + */ +static VALUE +pgconn_type_map_for_queries_set(VALUE self, VALUE typemap) +{ + t_pg_connection *this = pg_get_connection( self ); + if( typemap != Qnil ){ + if ( !rb_obj_is_kind_of(typemap, rb_cTypeMap) ) { + rb_raise( rb_eTypeError, "wrong argument type %s (expected kind of PG::TypeMap)", + rb_obj_classname( typemap ) ); + } + Check_Type(typemap, T_DATA); + } + this->type_map_for_queries = typemap; + return typemap; +} + +/* + * call-seq: + * res.type_map_for_queries -> TypeMap + * + * Returns the default TypeMap that is currently set for type casts of query + * bind parameters. + * + * Returns either: + * * a kind of PG::TypeMap or + * * +nil+ - when no type map is set. + * + */ +static VALUE +pgconn_type_map_for_queries_get(VALUE self) +{ + t_pg_connection *this = pg_get_connection( self ); + + return this->type_map_for_queries; +} + +/* + * call-seq: + * res.type_map_for_results = typemap + * + * Set the default TypeMap that is used for type casts of result values. + * + * +typemap+ can be: + * * a kind of PG::TypeMap + * * +nil+ - to type cast all result values to String. + * + */ +static VALUE +pgconn_type_map_for_results_set(VALUE self, VALUE typemap) +{ + t_pg_connection *this = pg_get_connection( self ); + + if( typemap != Qnil ){ + if ( !rb_obj_is_kind_of(typemap, rb_cTypeMap) ) { + rb_raise( rb_eTypeError, "wrong argument type %s (expected kind of PG::TypeMap)", + rb_obj_classname( typemap ) ); + } + Check_Type(typemap, T_DATA); + } + this->type_map_for_results = typemap; + + return typemap; +} + +/* + * call-seq: + * res.type_map_for_results -> TypeMap + * + * Returns the default TypeMap that is currently set for type casts of result values. + * + * Returns either: + * * a kind of PG::TypeMap or + * * +nil+ - when no type map is set. + * + */ +static VALUE +pgconn_type_map_for_results_get(VALUE self) +{ + t_pg_connection *this = pg_get_connection( self ); + + return this->type_map_for_results; +} + + +/* + * call-seq: + * res.encoder_for_put_copy_data = encoder + * + * Set the default coder that is used for type casting of parameters + * to #put_copy_data . + * + * +encoder+ can be: + * * a kind of PG::Coder + * * +nil+ - disable type encoding, data must be a String. + * + */ +static VALUE +pgconn_encoder_for_put_copy_data_set(VALUE self, VALUE typemap) +{ + t_pg_connection *this = pg_get_connection( self ); + + if( typemap != Qnil ){ + if ( !rb_obj_is_kind_of(typemap, rb_cPG_Coder) ) { + rb_raise( rb_eTypeError, "wrong argument type %s (expected kind of PG::Coder)", + rb_obj_classname( typemap ) ); + } + Check_Type(typemap, T_DATA); + } + this->encoder_for_put_copy_data = typemap; + + return typemap; +} + +/* + * call-seq: + * res.encoder_for_put_copy_data -> PG::Coder + * + * Returns the default coder object that is currently set for type casting of parameters + * to #put_copy_data . + * + * Returns either: + * * a kind of PG::Coder + * * +nil+ - type encoding is disabled, returned data will be a String. + * + */ +static VALUE +pgconn_encoder_for_put_copy_data_get(VALUE self) +{ + t_pg_connection *this = pg_get_connection( self ); + + return this->encoder_for_put_copy_data; +} + +/* + * call-seq: + * res.decoder_for_get_copy_data = decoder + * + * Set the default coder that is used for type casting of received data + * by #get_copy_data . + * + * +decoder+ can be: + * * a kind of PG::Coder + * * +nil+ - disable type decoding, returned data will be a String. + * + */ +static VALUE +pgconn_decoder_for_get_copy_data_set(VALUE self, VALUE typemap) +{ + t_pg_connection *this = pg_get_connection( self ); + + if( typemap != Qnil ){ + if ( !rb_obj_is_kind_of(typemap, rb_cPG_Coder) ) { + rb_raise( rb_eTypeError, "wrong argument type %s (expected kind of PG::Coder)", + rb_obj_classname( typemap ) ); + } + Check_Type(typemap, T_DATA); + } + this->decoder_for_get_copy_data = typemap; + + return typemap; +} + +/* + * call-seq: + * res.decoder_for_get_copy_data -> PG::Coder + * + * Returns the default coder object that is currently set for type casting of received + * data by #get_copy_data . + * + * Returns either: + * * a kind of PG::Coder + * * +nil+ - type encoding is disabled, returned data will be a String. + * + */ +static VALUE +pgconn_decoder_for_get_copy_data_get(VALUE self) +{ + t_pg_connection *this = pg_get_connection( self ); + + return this->decoder_for_get_copy_data; +} + + void init_pg_connection() { + s_id_encode = rb_intern("encode"); + sym_type = ID2SYM(rb_intern("type")); + sym_format = ID2SYM(rb_intern("format")); + sym_value = ID2SYM(rb_intern("value")); + rb_cPGconn = rb_define_class_under( rb_mPG, "Connection", rb_cObject ); rb_include_module(rb_cPGconn, rb_mPGconstants); /****** PG::Connection CLASS METHODS ******/ rb_define_alloc_func( rb_cPGconn, pgconn_s_allocate ); @@ -3556,11 +3843,13 @@ rb_define_method(rb_cPGconn, "user", pgconn_user, 0); rb_define_method(rb_cPGconn, "pass", pgconn_pass, 0); rb_define_method(rb_cPGconn, "host", pgconn_host, 0); rb_define_method(rb_cPGconn, "port", pgconn_port, 0); rb_define_method(rb_cPGconn, "tty", pgconn_tty, 0); +#ifdef HAVE_PQCONNINFO rb_define_method(rb_cPGconn, "conninfo", pgconn_conninfo, 0); +#endif rb_define_method(rb_cPGconn, "options", pgconn_options, 0); rb_define_method(rb_cPGconn, "status", pgconn_status, 0); rb_define_method(rb_cPGconn, "transaction_status", pgconn_transaction_status, 0); rb_define_method(rb_cPGconn, "parameter_status", pgconn_parameter_status, 1); rb_define_method(rb_cPGconn, "protocol_version", pgconn_protocol_version, 0); @@ -3617,11 +3906,11 @@ /****** PG::Connection INSTANCE METHODS: NOTIFY ******/ rb_define_method(rb_cPGconn, "notifies", pgconn_notifies, 0); /****** PG::Connection INSTANCE METHODS: COPY ******/ - rb_define_method(rb_cPGconn, "put_copy_data", pgconn_put_copy_data, 1); + rb_define_method(rb_cPGconn, "put_copy_data", pgconn_put_copy_data, -1); rb_define_method(rb_cPGconn, "put_copy_end", pgconn_put_copy_end, -1); rb_define_method(rb_cPGconn, "get_copy_data", pgconn_get_copy_data, -1); /****** PG::Connection INSTANCE METHODS: Control Functions ******/ rb_define_method(rb_cPGconn, "set_error_verbosity", pgconn_set_error_verbosity, 1); @@ -3678,7 +3967,15 @@ rb_define_method(rb_cPGconn, "internal_encoding=", pgconn_internal_encoding_set, 1); rb_define_method(rb_cPGconn, "external_encoding", pgconn_external_encoding, 0); rb_define_method(rb_cPGconn, "set_default_encoding", pgconn_set_default_encoding, 0); #endif /* M17N_SUPPORTED */ + rb_define_method(rb_cPGconn, "type_map_for_queries=", pgconn_type_map_for_queries_set, 1); + rb_define_method(rb_cPGconn, "type_map_for_queries", pgconn_type_map_for_queries_get, 0); + rb_define_method(rb_cPGconn, "type_map_for_results=", pgconn_type_map_for_results_set, 1); + rb_define_method(rb_cPGconn, "type_map_for_results", pgconn_type_map_for_results_get, 0); + rb_define_method(rb_cPGconn, "encoder_for_put_copy_data=", pgconn_encoder_for_put_copy_data_set, 1); + rb_define_method(rb_cPGconn, "encoder_for_put_copy_data", pgconn_encoder_for_put_copy_data_get, 0); + rb_define_method(rb_cPGconn, "decoder_for_get_copy_data=", pgconn_decoder_for_get_copy_data_set, 1); + rb_define_method(rb_cPGconn, "decoder_for_get_copy_data", pgconn_decoder_for_get_copy_data_get, 0); }