Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package rubygem-pg for openSUSE:Factory checked in at 2022-08-09 15:26:47 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/rubygem-pg (Old) and /work/SRC/openSUSE:Factory/.rubygem-pg.new.1521 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "rubygem-pg" Tue Aug 9 15:26:47 2022 rev:42 rq:993509 version:1.4.2 Changes: -------- --- /work/SRC/openSUSE:Factory/rubygem-pg/rubygem-pg.changes 2022-04-30 22:52:53.272252667 +0200 +++ /work/SRC/openSUSE:Factory/.rubygem-pg.new.1521/rubygem-pg.changes 2022-08-09 15:27:02.989401882 +0200 @@ -1,0 +2,47 @@ +Thu Aug 4 13:22:33 UTC 2022 - Stephan Kulow <co...@suse.com> + +updated to version 1.4.2 + see installed History.rdoc + + == v1.4.2 [2022-07-27] Lars Kanis <l...@greiz-reinsdorf.de> + + Bugfixes: + + - Properly handle empty host parameter when connecting. #471 + - Update Windows fat binary gem to OpenSSL-1.1.1q. + + + == v1.4.1 [2022-06-24] Lars Kanis <l...@greiz-reinsdorf.de> + + Bugfixes: + + - Fix another ruby-2.7 keyword warning. #465 + - Allow PG::Error to be created without arguments. #466 + + + == v1.4.0 [2022-06-20] Lars Kanis <l...@greiz-reinsdorf.de> + + Added: + + - Add PG::Connection#hostaddr, present since PostgreSQL-12. #453 + - Add PG::Connection.conninfo_parse to wrap PQconninfoParse. #453 + + Bugfixes: + + - Try IPv6 and IPv4 addresses, if DNS resolves to both. #452 + - Re-add block-call semantics to PG::Connection.new accidently removed in pg-1.3.0. #454 + - Handle client error after all data consumed in #copy_data for output. #455 + - Avoid spurious keyword argument warning on Ruby 2.7. #456 + - Change connection setup to respect connect_timeout parameter. #459 + - Fix indefinite hang in case of connection error on Windows #458 + - Set connection attribute of PG::Error in various places where it was missing. #461 + - Fix transaction leak on early break/return. #463 + - Update Windows fat binary gem to OpenSSL-1.1.1o and PostgreSQL-14.4. + + Enhancements: + + - Don't flush at each put_copy_data call, but flush at get_result. #462 + + + +------------------------------------------------------------------- Old: ---- pg-1.3.5.gem New: ---- pg-1.4.2.gem ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ rubygem-pg.spec ++++++ --- /var/tmp/diff_new_pack.RpuyAr/_old 2022-08-09 15:27:03.549403482 +0200 +++ /var/tmp/diff_new_pack.RpuyAr/_new 2022-08-09 15:27:03.553403493 +0200 @@ -24,7 +24,7 @@ # Name: rubygem-pg -Version: 1.3.5 +Version: 1.4.2 Release: 0 %define mod_name pg %define mod_full_name %{mod_name}-%{version} ++++++ pg-1.3.5.gem -> pg-1.4.2.gem ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/History.rdoc new/History.rdoc --- old/History.rdoc 2022-03-31 14:39:47.000000000 +0200 +++ new/History.rdoc 2022-07-27 10:04:31.000000000 +0200 @@ -1,3 +1,43 @@ +== v1.4.2 [2022-07-27] Lars Kanis <l...@greiz-reinsdorf.de> + +Bugfixes: + +- Properly handle empty host parameter when connecting. #471 +- Update Windows fat binary gem to OpenSSL-1.1.1q. + + +== v1.4.1 [2022-06-24] Lars Kanis <l...@greiz-reinsdorf.de> + +Bugfixes: + +- Fix another ruby-2.7 keyword warning. #465 +- Allow PG::Error to be created without arguments. #466 + + +== v1.4.0 [2022-06-20] Lars Kanis <l...@greiz-reinsdorf.de> + +Added: + +- Add PG::Connection#hostaddr, present since PostgreSQL-12. #453 +- Add PG::Connection.conninfo_parse to wrap PQconninfoParse. #453 + +Bugfixes: + +- Try IPv6 and IPv4 addresses, if DNS resolves to both. #452 +- Re-add block-call semantics to PG::Connection.new accidently removed in pg-1.3.0. #454 +- Handle client error after all data consumed in #copy_data for output. #455 +- Avoid spurious keyword argument warning on Ruby 2.7. #456 +- Change connection setup to respect connect_timeout parameter. #459 +- Fix indefinite hang in case of connection error on Windows #458 +- Set connection attribute of PG::Error in various places where it was missing. #461 +- Fix transaction leak on early break/return. #463 +- Update Windows fat binary gem to OpenSSL-1.1.1o and PostgreSQL-14.4. + +Enhancements: + +- Don't flush at each put_copy_data call, but flush at get_result. #462 + + == v1.3.5 [2022-03-31] Lars Kanis <l...@greiz-reinsdorf.de> Bugfixes: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Rakefile.cross new/Rakefile.cross --- old/Rakefile.cross 2022-03-31 14:39:47.000000000 +0200 +++ new/Rakefile.cross 2022-07-27 10:04:31.000000000 +0200 @@ -31,8 +31,8 @@ self.host_platform = toolchain # Cross-compilation constants - self.openssl_version = ENV['OPENSSL_VERSION'] || '1.1.1m' - self.postgresql_version = ENV['POSTGRESQL_VERSION'] || '14.2' + self.openssl_version = ENV['OPENSSL_VERSION'] || '1.1.1q' + self.postgresql_version = ENV['POSTGRESQL_VERSION'] || '14.4' # Check if symlinks work in the current working directory. # This fails, if rake-compiler-dock is running on a Windows box. Binary files old/checksums.yaml.gz and new/checksums.yaml.gz differ Binary files old/checksums.yaml.gz.sig and new/checksums.yaml.gz.sig differ Binary files old/data.tar.gz.sig and new/data.tar.gz.sig differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ext/extconf.rb new/ext/extconf.rb --- old/ext/extconf.rb 2022-03-31 14:39:47.000000000 +0200 +++ new/ext/extconf.rb 2022-07-27 10:04:31.000000000 +0200 @@ -1,5 +1,3 @@ -# -*- encoding: utf-8 -*- - require 'pp' require 'mkmf' @@ -169,32 +167,3 @@ create_header() create_makefile( "pg_ext" ) - -def message!(important_message) - message important_message - if !$stdout.tty? && File.chardev?('/dev/tty') - File.open('/dev/tty', 'w') do |tty| - tty.print important_message - end - end -rescue -end - -if "2022-04-01" == Time.now.strftime("%Y-%m-%d") - message! <<-EOM -=================================================================== -HEADS UP! Prepare for pg-1.4.2022 ! ???????????? - -Now that psycopg3 has most of the features of ruby-pg, we plan to -switch to using it as our foundation for the next pg release. -It will run through pycall and requires a working python setup. -This will minimize our development and maintenance efforts, since -it allows us to use one and the same code base for both programming -languages - ruby and python. ???????? - -And we follow the recent merge of the Django and Rails teams! ?????? - -Stay up-to-date at https://github.com/ged/ruby-pg/issues/449 -=================================================================== -EOM -end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ext/pg_connection.c new/ext/pg_connection.c --- old/ext/pg_connection.c 2022-03-31 14:39:47.000000000 +0200 +++ new/ext/pg_connection.c 2022-07-27 10:04:31.000000000 +0200 @@ -24,12 +24,33 @@ static VALUE pgconn_wait_for_flush( VALUE self ); static void pgconn_set_internal_encoding_index( VALUE ); static const rb_data_type_t pg_connection_type; +static VALUE pgconn_async_flush(VALUE self); /* * Global functions */ /* + * Convenience function to raise connection errors + */ +#ifdef __GNUC__ +__attribute__((format(printf, 3, 4))) +#endif +static void +pg_raise_conn_error( VALUE klass, VALUE self, const char *format, ...) +{ + VALUE msg, error; + va_list ap; + + va_start(ap, format); + msg = rb_vsprintf(format, ap); + va_end(ap); + error = rb_exc_new_str(klass, msg); + rb_iv_set(error, "@connection", self); + rb_exc_raise(error); +} + +/* * Fetch the PG::Connection object data pointer. */ t_pg_connection * @@ -52,7 +73,7 @@ TypedData_Get_Struct( self, t_pg_connection, &pg_connection_type, this); if ( !this->pgconn ) - rb_raise( rb_eConnectionBad, "connection is closed" ); + pg_raise_conn_error( rb_eConnectionBad, self, "connection is closed"); return this; } @@ -70,8 +91,9 @@ t_pg_connection *this; TypedData_Get_Struct( self, t_pg_connection, &pg_connection_type, this); - if ( !this->pgconn ) - rb_raise( rb_eConnectionBad, "connection is closed" ); + if ( !this->pgconn ){ + pg_raise_conn_error( rb_eConnectionBad, self, "connection is closed"); + } return this->pgconn; } @@ -89,9 +111,8 @@ if ( RTEST(socket_io) ) { #if defined(_WIN32) - if( rb_w32_unwrap_io_handle(this->ruby_sd) ){ - rb_raise(rb_eConnectionBad, "Could not unwrap win32 socket handle"); - } + if( rb_w32_unwrap_io_handle(this->ruby_sd) ) + pg_raise_conn_error( rb_eConnectionBad, self, "Could not unwrap win32 socket handle"); #endif rb_funcall( socket_io, rb_intern("close"), 0 ); } @@ -254,7 +275,6 @@ { t_pg_connection *this; VALUE conninfo; - VALUE error; VALUE self = pgconn_s_allocate( klass ); this = pg_get_connection( self ); @@ -262,13 +282,10 @@ this->pgconn = gvl_PQconnectdb(StringValueCStr(conninfo)); if(this->pgconn == NULL) - rb_raise(rb_ePGerror, "PQconnectdb() unable to allocate structure"); + rb_raise(rb_ePGerror, "PQconnectdb() unable to allocate PGconn structure"); - 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); - } + if (PQstatus(this->pgconn) == CONNECTION_BAD) + pg_raise_conn_error( rb_eConnectionBad, self, "%s", PQerrorMessage(this->pgconn)); pgconn_set_default_encoding( self ); @@ -301,7 +318,6 @@ { VALUE rb_conn; VALUE conninfo; - VALUE error; t_pg_connection *this; /* @@ -314,13 +330,10 @@ this->pgconn = gvl_PQconnectStart( StringValueCStr(conninfo) ); if( this->pgconn == NULL ) - rb_raise(rb_ePGerror, "PQconnectStart() unable to allocate structure"); + rb_raise(rb_ePGerror, "PQconnectStart() unable to allocate PGconn structure"); - 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 ( PQstatus(this->pgconn) == CONNECTION_BAD ) + pg_raise_conn_error( rb_eConnectionBad, rb_conn, "%s", PQerrorMessage(this->pgconn)); if ( rb_block_given_p() ) { return rb_ensure( rb_yield, rb_conn, pgconn_finish, rb_conn ); @@ -376,6 +389,36 @@ return array; } +/* + * Document-method: PG::Connection.conninfo_parse + * + * call-seq: + * PG::Connection.conninfo_parse(conninfo_string) -> Array + * + * Returns parsed connection options from the provided connection string as an array of hashes. + * Each hash has the same keys as PG::Connection.conndefaults() . + * The values from the +conninfo_string+ are stored in the +:val+ key. + */ +static VALUE +pgconn_s_conninfo_parse(VALUE self, VALUE conninfo) +{ + VALUE array; + char *errmsg = NULL; + PQconninfoOption *options = PQconninfoParse(StringValueCStr(conninfo), &errmsg); + if(errmsg){ + VALUE error = rb_str_new_cstr(errmsg); + PQfreemem(errmsg); + rb_raise(rb_ePGerror, "%"PRIsVALUE, error); + } + array = pgconn_make_conninfo_array( options ); + + PQconninfoFree(options); + + UNUSED( self ); + + return array; +} + #ifdef HAVE_PQENCRYPTPASSWORDCONN static VALUE @@ -396,7 +439,7 @@ rval = rb_str_new2( encrypted ); PQfreemem( encrypted ); } else { - rb_raise(rb_ePGerror, "%s", PQerrorMessage(conn)); + pg_raise_conn_error( rb_ePGerror, self, "%s", PQerrorMessage(conn)); } return rval; @@ -537,7 +580,7 @@ { pgconn_close_socket_io( self ); if(gvl_PQresetStart(pg_get_pgconn(self)) == 0) - rb_raise(rb_eUnableToSend, "reset has failed"); + pg_raise_conn_error( rb_eUnableToSend, self, "reset has failed"); return Qnil; } @@ -607,7 +650,18 @@ * call-seq: * conn.host() * - * Returns the connected server name. + * Returns the server host name of the active connection. + * This can be a host name, an IP address, or a directory path if the connection is via Unix socket. + * (The path case can be distinguished because it will always be an absolute path, beginning with +/+ .) + * + * If the connection parameters specified both host and hostaddr, then +host+ will return the host information. + * If only hostaddr was specified, then that is returned. + * If multiple hosts were specified in the connection parameters, +host+ returns the host actually connected to. + * + * If there is an error producing the host information (perhaps if the connection has not been fully established or there was an error), it returns an empty string. + * + * If multiple hosts were specified in the connection parameters, it is not possible to rely on the result of +host+ until the connection is established. + * The status of the connection can be checked using the function Connection#status . */ static VALUE pgconn_host(VALUE self) @@ -617,6 +671,26 @@ return rb_str_new2(host); } +/* PQhostaddr() appeared in PostgreSQL-12 together with PQresultMemorySize() */ +#if defined(HAVE_PQRESULTMEMORYSIZE) +/* + * call-seq: + * conn.hostaddr() + * + * Returns the server IP address of the active connection. + * This can be the address that a host name resolved to, or an IP address provided through the hostaddr parameter. + * If there is an error producing the host information (perhaps if the connection has not been fully established or there was an error), it returns an empty string. + * + */ +static VALUE +pgconn_hostaddr(VALUE self) +{ + char *host = PQhostaddr(pg_get_pgconn(self)); + if (!host) return Qnil; + return rb_str_new2(host); +} +#endif + /* * call-seq: * conn.port() @@ -687,6 +761,9 @@ * PG::Constants::CONNECTION_BAD * * ... and other constants of kind PG::Constants::CONNECTION_* + * + * Example: + * PG.constants.grep(/CONNECTION_/).find{|c| PG.const_get(c) == conn.status} # => :CONNECTION_OK */ static VALUE pgconn_status(VALUE self) @@ -811,7 +888,8 @@ pg_deprecated(4, ("conn.socket is deprecated and should be replaced by conn.socket_io")); if( (sd = PQsocket(pg_get_pgconn(self))) < 0) - rb_raise(rb_eConnectionBad, "PQsocket() can't get socket descriptor"); + pg_raise_conn_error( rb_eConnectionBad, self, "PQsocket() can't get socket descriptor"); + return INT2NUM(sd); } @@ -839,14 +917,15 @@ VALUE socket_io = this->socket_io; if ( !RTEST(socket_io) ) { - if( (sd = PQsocket(this->pgconn)) < 0) - rb_raise(rb_eConnectionBad, "PQsocket() can't get socket descriptor"); + if( (sd = PQsocket(this->pgconn)) < 0){ + pg_raise_conn_error( rb_eConnectionBad, self, "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); - if( ruby_sd == -1 ){ - rb_raise(rb_eConnectionBad, "Could not wrap win32 socket handle"); - } + if( ruby_sd == -1 ) + pg_raise_conn_error( rb_eConnectionBad, self, "Could not wrap win32 socket handle"); + this->ruby_sd = ruby_sd; #else ruby_sd = sd; @@ -911,7 +990,7 @@ cancel = (struct pg_cancel*)PQgetCancel(conn); if(cancel == NULL) - rb_raise(rb_ePGerror,"Invalid connection!"); + pg_raise_conn_error( rb_ePGerror, self, "Invalid connection!"); if( cancel->be_pid != PQbackendPID(conn) ) rb_raise(rb_ePGerror,"Unexpected binary struct layout - please file a bug report at ruby-pg!"); @@ -1540,9 +1619,9 @@ if( !singleton ) { size = PQescapeStringConn(pg_get_pgconn(self), RSTRING_PTR(result), RSTRING_PTR(string), RSTRING_LEN(string), &error); - if(error) { - rb_raise(rb_ePGerror, "%s", PQerrorMessage(pg_get_pgconn(self))); - } + if(error) + pg_raise_conn_error( rb_ePGerror, self, "%s", PQerrorMessage(pg_get_pgconn(self))); + } else { size = PQescapeString(RSTRING_PTR(result), RSTRING_PTR(string), RSTRING_LEN(string)); } @@ -1638,7 +1717,6 @@ { t_pg_connection *this = pg_get_connection_safe( self ); char *escaped = NULL; - VALUE error; VALUE result = Qnil; int enc_idx = this->enc_idx; @@ -1649,12 +1727,8 @@ escaped = PQescapeLiteral(this->pgconn, RSTRING_PTR(string), RSTRING_LEN(string)); if (escaped == NULL) - { - error = rb_exc_new2(rb_ePGerror, PQerrorMessage(this->pgconn)); - rb_iv_set(error, "@connection", self); - rb_exc_raise(error); - return Qnil; - } + pg_raise_conn_error( rb_ePGerror, self, "%s", PQerrorMessage(this->pgconn)); + result = rb_str_new2(escaped); PQfreemem(escaped); PG_ENCODING_SET_NOCHECK(result, enc_idx); @@ -1677,7 +1751,6 @@ { t_pg_connection *this = pg_get_connection_safe( self ); char *escaped = NULL; - VALUE error; VALUE result = Qnil; int enc_idx = this->enc_idx; @@ -1688,12 +1761,8 @@ escaped = PQescapeIdentifier(this->pgconn, RSTRING_PTR(string), RSTRING_LEN(string)); if (escaped == NULL) - { - error = rb_exc_new2(rb_ePGerror, PQerrorMessage(this->pgconn)); - rb_iv_set(error, "@connection", self); - rb_exc_raise(error); - return Qnil; - } + pg_raise_conn_error( rb_ePGerror, self, "%s", PQerrorMessage(this->pgconn)); + result = rb_str_new2(escaped); PQfreemem(escaped); PG_ENCODING_SET_NOCHECK(result, enc_idx); @@ -1741,14 +1810,9 @@ pgconn_set_single_row_mode(VALUE self) { PGconn *conn = pg_get_pgconn(self); - VALUE error; if( PQsetSingleRowMode(conn) == 0 ) - { - error = rb_exc_new2(rb_ePGerror, PQerrorMessage(conn)); - rb_iv_set(error, "@connection", self); - rb_exc_raise(error); - } + pg_raise_conn_error( rb_ePGerror, self, "%s", PQerrorMessage(conn)); return self; } @@ -1772,15 +1836,12 @@ pgconn_send_query(int argc, VALUE *argv, VALUE self) { t_pg_connection *this = pg_get_connection_safe( self ); - VALUE error; /* If called with no or nil parameters, use PQexec for compatibility */ if ( argc == 1 || (argc >= 2 && argc <= 4 && NIL_P(argv[1]) )) { - if(gvl_PQsendQuery(this->pgconn, pg_cstr_enc(argv[0], this->enc_idx)) == 0) { - error = rb_exc_new2(rb_eUnableToSend, PQerrorMessage(this->pgconn)); - rb_iv_set(error, "@connection", self); - rb_exc_raise(error); - } + if(gvl_PQsendQuery(this->pgconn, pg_cstr_enc(argv[0], this->enc_idx)) == 0) + pg_raise_conn_error( rb_eUnableToSend, self, "%s", PQerrorMessage(this->pgconn)); + pgconn_wait_for_flush( self ); return Qnil; } @@ -1837,7 +1898,6 @@ t_pg_connection *this = pg_get_connection_safe( self ); int result; VALUE command, in_res_fmt; - VALUE error; int nParams; int resultFormat; struct query_params_data paramsData = { this->enc_idx }; @@ -1854,11 +1914,9 @@ free_query_params( ¶msData ); - if(result == 0) { - error = rb_exc_new2(rb_eUnableToSend, PQerrorMessage(this->pgconn)); - rb_iv_set(error, "@connection", self); - rb_exc_raise(error); - } + if(result == 0) + pg_raise_conn_error( rb_eUnableToSend, self, "%s", PQerrorMessage(this->pgconn)); + pgconn_wait_for_flush( self ); return Qnil; } @@ -1890,7 +1948,6 @@ int result; VALUE name, command, in_paramtypes; VALUE param; - VALUE error; int i = 0; int nParams = 0; Oid *paramTypes = NULL; @@ -1919,9 +1976,7 @@ xfree(paramTypes); if(result == 0) { - error = rb_exc_new2(rb_eUnableToSend, PQerrorMessage(this->pgconn)); - rb_iv_set(error, "@connection", self); - rb_exc_raise(error); + pg_raise_conn_error( rb_eUnableToSend, self, "%s", PQerrorMessage(this->pgconn)); } pgconn_wait_for_flush( self ); return Qnil; @@ -1965,7 +2020,6 @@ t_pg_connection *this = pg_get_connection_safe( self ); int result; VALUE name, in_res_fmt; - VALUE error; int nParams; int resultFormat; struct query_params_data paramsData = { this->enc_idx }; @@ -1987,11 +2041,9 @@ free_query_params( ¶msData ); - if(result == 0) { - error = rb_exc_new2(rb_eUnableToSend, PQerrorMessage(this->pgconn)); - rb_iv_set(error, "@connection", self); - rb_exc_raise(error); - } + if(result == 0) + pg_raise_conn_error( rb_eUnableToSend, self, "%s", PQerrorMessage(this->pgconn)); + pgconn_wait_for_flush( self ); return Qnil; } @@ -2006,14 +2058,11 @@ static VALUE pgconn_send_describe_prepared(VALUE self, VALUE stmt_name) { - VALUE error; t_pg_connection *this = pg_get_connection_safe( self ); /* returns 0 on failure */ - if(gvl_PQsendDescribePrepared(this->pgconn, pg_cstr_enc(stmt_name, this->enc_idx)) == 0) { - error = rb_exc_new2(rb_eUnableToSend, PQerrorMessage(this->pgconn)); - rb_iv_set(error, "@connection", self); - rb_exc_raise(error); - } + if(gvl_PQsendDescribePrepared(this->pgconn, pg_cstr_enc(stmt_name, this->enc_idx)) == 0) + pg_raise_conn_error( rb_eUnableToSend, self, "%s", PQerrorMessage(this->pgconn)); + pgconn_wait_for_flush( self ); return Qnil; } @@ -2029,14 +2078,11 @@ static VALUE pgconn_send_describe_portal(VALUE self, VALUE portal) { - VALUE error; t_pg_connection *this = pg_get_connection_safe( self ); /* returns 0 on failure */ - if(gvl_PQsendDescribePortal(this->pgconn, pg_cstr_enc(portal, this->enc_idx)) == 0) { - error = rb_exc_new2(rb_eUnableToSend, PQerrorMessage(this->pgconn)); - rb_iv_set(error, "@connection", self); - rb_exc_raise(error); - } + if(gvl_PQsendDescribePortal(this->pgconn, pg_cstr_enc(portal, this->enc_idx)) == 0) + pg_raise_conn_error( rb_eUnableToSend, self, "%s", PQerrorMessage(this->pgconn)); + pgconn_wait_for_flush( self ); return Qnil; } @@ -2069,18 +2115,15 @@ * or *notifies* to see if the state has changed. */ static VALUE -pgconn_consume_input(self) - VALUE self; +pgconn_consume_input(VALUE self) { - VALUE error; PGconn *conn = pg_get_pgconn(self); /* returns 0 on error */ if(PQconsumeInput(conn) == 0) { pgconn_close_socket_io(self); - error = rb_exc_new2(rb_eConnectionBad, PQerrorMessage(conn)); - rb_iv_set(error, "@connection", self); - rb_exc_raise(error); + pg_raise_conn_error( rb_eConnectionBad, self, "%s", PQerrorMessage(conn)); } + return Qnil; } @@ -2092,18 +2135,15 @@ * #get_result would block. Otherwise returns +false+. */ static VALUE -pgconn_is_busy(self) - VALUE self; +pgconn_is_busy(VALUE self) { return gvl_PQisBusy(pg_get_pgconn(self)) ? Qtrue : Qfalse; } static VALUE -pgconn_sync_setnonblocking(self, state) - VALUE self, state; +pgconn_sync_setnonblocking(VALUE self, VALUE state) { int arg; - VALUE error; PGconn *conn = pg_get_pgconn(self); if(state == Qtrue) arg = 1; @@ -2112,18 +2152,15 @@ else rb_raise(rb_eArgError, "Boolean value expected"); - if(PQsetnonblocking(conn, arg) == -1) { - error = rb_exc_new2(rb_ePGerror, PQerrorMessage(conn)); - rb_iv_set(error, "@connection", self); - rb_exc_raise(error); - } + if(PQsetnonblocking(conn, arg) == -1) + pg_raise_conn_error( rb_ePGerror, self, "%s", PQerrorMessage(conn)); + return Qnil; } static VALUE -pgconn_sync_isnonblocking(self) - VALUE self; +pgconn_sync_isnonblocking(VALUE self) { return PQisnonblocking(pg_get_pgconn(self)) ? Qtrue : Qfalse; } @@ -2132,14 +2169,10 @@ pgconn_sync_flush(VALUE self) { PGconn *conn = pg_get_pgconn(self); - int ret; - VALUE error; - ret = PQflush(conn); - if(ret == -1) { - error = rb_exc_new2(rb_ePGerror, PQerrorMessage(conn)); - rb_iv_set(error, "@connection", self); - rb_exc_raise(error); - } + int ret = PQflush(conn); + if(ret == -1) + pg_raise_conn_error( rb_ePGerror, self, "%s", PQerrorMessage(conn)); + return (ret) ? Qfalse : Qtrue; } @@ -2153,7 +2186,7 @@ cancel = PQgetCancel(pg_get_pgconn(self)); if(cancel == NULL) - rb_raise(rb_ePGerror,"Invalid connection!"); + pg_raise_conn_error( rb_ePGerror, self, "Invalid connection!"); ret = gvl_PQcancel(cancel, errbuf, sizeof(errbuf)); if(ret == 1) @@ -2362,7 +2395,14 @@ /* Is the given timeout valid? */ if( !ptimeout || (waittime.tv_sec >= 0 && waittime.tv_usec >= 0) ){ - VALUE socket_io = pgconn_socket_io(self); + VALUE socket_io; + + /* before we wait for data, make sure everything has been sent */ + pgconn_async_flush(self); + if ((retval=is_readable(conn))) + return retval; + + socket_io = pgconn_socket_io(self); /* Wait for the socket to become readable before checking again */ ret = pg_rb_io_wait(socket_io, RB_INT2NUM(PG_RUBY_IO_READABLE), wait_timeout); } else { @@ -2377,7 +2417,7 @@ /* Check for connection errors (PQisBusy is true on connection errors) */ if ( PQconsumeInput(conn) == 0 ){ pgconn_close_socket_io(self); - rb_raise( rb_eConnectionBad, "PQconsumeInput() %s", PQerrorMessage(conn) ); + pg_raise_conn_error(rb_eConnectionBad, self, "PQconsumeInput() %s", PQerrorMessage(conn)); } } @@ -2390,8 +2430,8 @@ * * Attempts to flush any queued output data to the server. * Returns +true+ if data is successfully flushed, +false+ - * if not (can only return +false+ if connection is - * nonblocking. + * if not. It can only return +false+ if connection is + * in nonblocking mode. * Raises PG::Error if some other failure occurred. */ static VALUE @@ -2527,11 +2567,9 @@ Check_Type(buffer, T_STRING); ret = gvl_PQputCopyData(this->pgconn, RSTRING_PTR(buffer), RSTRING_LENINT(buffer)); - if(ret == -1) { - VALUE error = rb_exc_new2(rb_ePGerror, PQerrorMessage(this->pgconn)); - rb_iv_set(error, "@connection", self); - rb_exc_raise(error); - } + if(ret == -1) + pg_raise_conn_error( rb_ePGerror, self, "%s", PQerrorMessage(this->pgconn)); + RB_GC_GUARD(intermediate); RB_GC_GUARD(buffer); @@ -2542,7 +2580,6 @@ pgconn_sync_put_copy_end(int argc, VALUE *argv, VALUE self) { VALUE str; - VALUE error; int ret; const char *error_message = NULL; t_pg_connection *this = pg_get_connection_safe( self ); @@ -2553,11 +2590,9 @@ error_message = pg_cstr_enc(str, this->enc_idx); ret = gvl_PQputCopyEnd(this->pgconn, error_message); - if(ret == -1) { - error = rb_exc_new2(rb_ePGerror, PQerrorMessage(this->pgconn)); - rb_iv_set(error, "@connection", self); - rb_exc_raise(error); - } + if(ret == -1) + pg_raise_conn_error( rb_ePGerror, self, "%s", PQerrorMessage(this->pgconn)); + return (ret) ? Qtrue : Qfalse; } @@ -2565,7 +2600,6 @@ pgconn_sync_get_copy_data(int argc, VALUE *argv, VALUE self ) { VALUE async_in; - VALUE error; VALUE result; int ret; char *buffer; @@ -2585,10 +2619,8 @@ } ret = gvl_PQgetCopyData(this->pgconn, &buffer, RTEST(async_in)); - if(ret == -2) { /* error */ - error = rb_exc_new2(rb_ePGerror, PQerrorMessage(this->pgconn)); - rb_iv_set(error, "@connection", self); - rb_exc_raise(error); + if(ret == -2){ /* error */ + pg_raise_conn_error( rb_ePGerror, self, "%s", PQerrorMessage(this->pgconn)); } if(ret == -1) { /* No data left */ return Qnil; @@ -2893,9 +2925,9 @@ Check_Type(str, T_STRING); - if ( (gvl_PQsetClientEncoding(conn, StringValueCStr(str))) == -1 ) { - rb_raise(rb_ePGerror, "%s", PQerrorMessage(conn)); - } + if ( (gvl_PQsetClientEncoding(conn, StringValueCStr(str))) == -1 ) + pg_raise_conn_error( rb_ePGerror, self, "%s", PQerrorMessage(conn)); + pgconn_set_internal_encoding_index( self ); return Qnil; @@ -3528,11 +3560,10 @@ { PGconn *conn = pg_get_pgconn(self); int res = PQenterPipelineMode(conn); - if( res == 1 ) { - return Qnil; - } else { - rb_raise(rb_ePGerror, "%s", PQerrorMessage(conn)); - } + if( res != 1 ) + pg_raise_conn_error( rb_ePGerror, self, "%s", PQerrorMessage(conn)); + + return Qnil; } /* @@ -3551,11 +3582,10 @@ { PGconn *conn = pg_get_pgconn(self); int res = PQexitPipelineMode(conn); - if( res == 1 ) { - return Qnil; - } else { - rb_raise(rb_ePGerror, "%s", PQerrorMessage(conn)); - } + if( res != 1 ) + pg_raise_conn_error( rb_ePGerror, self, "%s", PQerrorMessage(conn)); + + return Qnil; } @@ -3575,11 +3605,10 @@ { PGconn *conn = pg_get_pgconn(self); int res = PQpipelineSync(conn); - if( res == 1 ) { - return Qnil; - } else { - rb_raise(rb_ePGerror, "%s", PQerrorMessage(conn)); - } + if( res != 1 ) + pg_raise_conn_error( rb_ePGerror, self, "%s", PQerrorMessage(conn)); + + return Qnil; } /* @@ -3599,11 +3628,10 @@ { PGconn *conn = pg_get_pgconn(self); int res = PQsendFlushRequest(conn); - if( res == 1 ) { - return Qnil; - } else { - rb_raise(rb_ePGerror, "%s", PQerrorMessage(conn)); - } + if( res != 1 ) + pg_raise_conn_error( rb_ePGerror, self, "%s", PQerrorMessage(conn)); + + return Qnil; } #endif @@ -3634,7 +3662,7 @@ lo_oid = lo_creat(conn, mode); if (lo_oid == 0) - rb_raise(rb_ePGerror, "lo_creat failed"); + pg_raise_conn_error( rb_ePGerror, self, "lo_creat failed"); return UINT2NUM(lo_oid); } @@ -3655,7 +3683,7 @@ ret = lo_create(conn, lo_oid); if (ret == InvalidOid) - rb_raise(rb_ePGerror, "lo_create failed"); + pg_raise_conn_error( rb_ePGerror, self, "lo_create failed"); return UINT2NUM(ret); } @@ -3679,7 +3707,7 @@ lo_oid = lo_import(conn, StringValueCStr(filename)); if (lo_oid == 0) { - rb_raise(rb_ePGerror, "%s", PQerrorMessage(conn)); + pg_raise_conn_error( rb_ePGerror, self, "%s", PQerrorMessage(conn)); } return UINT2NUM(lo_oid); } @@ -3700,7 +3728,7 @@ oid = NUM2UINT(lo_oid); if (lo_export(conn, oid, StringValueCStr(filename)) < 0) { - rb_raise(rb_ePGerror, "%s", PQerrorMessage(conn)); + pg_raise_conn_error( rb_ePGerror, self, "%s", PQerrorMessage(conn)); } return Qnil; } @@ -3731,7 +3759,7 @@ mode = NUM2INT(nmode); if((fd = lo_open(conn, lo_oid, mode)) < 0) { - rb_raise(rb_ePGerror, "can't open large object: %s", PQerrorMessage(conn)); + pg_raise_conn_error( rb_ePGerror, self, "can't open large object: %s", PQerrorMessage(conn)); } return INT2FIX(fd); } @@ -3753,11 +3781,11 @@ Check_Type(buffer, T_STRING); if( RSTRING_LEN(buffer) < 0) { - rb_raise(rb_ePGerror, "write buffer zero string"); + pg_raise_conn_error( rb_ePGerror, self, "write buffer zero string"); } if((n = lo_write(conn, fd, StringValuePtr(buffer), RSTRING_LEN(buffer))) < 0) { - rb_raise(rb_ePGerror, "lo_write failed: %s", PQerrorMessage(conn)); + pg_raise_conn_error( rb_ePGerror, self, "lo_write failed: %s", PQerrorMessage(conn)); } return INT2FIX(n); @@ -3780,16 +3808,12 @@ VALUE str; char *buffer; - buffer = ALLOC_N(char, len); - if(buffer == NULL) - rb_raise(rb_eNoMemError, "ALLOC failed!"); - - if (len < 0){ - rb_raise(rb_ePGerror,"nagative length %d given", len); - } + if (len < 0) + pg_raise_conn_error( rb_ePGerror, self, "negative length %d given", len); + buffer = ALLOC_N(char, len); if((ret = lo_read(conn, lo_desc, buffer, len)) < 0) - rb_raise(rb_ePGerror, "lo_read failed"); + pg_raise_conn_error( rb_ePGerror, self, "lo_read failed"); if(ret == 0) { xfree(buffer); @@ -3819,7 +3843,7 @@ int ret; if((ret = lo_lseek(conn, lo_desc, NUM2INT(offset), NUM2INT(whence))) < 0) { - rb_raise(rb_ePGerror, "lo_lseek failed"); + pg_raise_conn_error( rb_ePGerror, self, "lo_lseek failed"); } return INT2FIX(ret); @@ -3839,7 +3863,7 @@ int lo_desc = NUM2INT(in_lo_desc); if((position = lo_tell(conn, lo_desc)) < 0) - rb_raise(rb_ePGerror,"lo_tell failed"); + pg_raise_conn_error( rb_ePGerror, self, "lo_tell failed"); return INT2FIX(position); } @@ -3858,7 +3882,7 @@ size_t len = NUM2INT(in_len); if(lo_truncate(conn,lo_desc,len) < 0) - rb_raise(rb_ePGerror,"lo_truncate failed"); + pg_raise_conn_error( rb_ePGerror, self, "lo_truncate failed"); return Qnil; } @@ -3876,7 +3900,7 @@ int lo_desc = NUM2INT(in_lo_desc); if(lo_close(conn,lo_desc) < 0) - rb_raise(rb_ePGerror,"lo_close failed"); + pg_raise_conn_error( rb_ePGerror, self, "lo_close failed"); return Qnil; } @@ -3894,7 +3918,7 @@ Oid oid = NUM2UINT(in_oid); if(lo_unlink(conn,oid) < 0) - rb_raise(rb_ePGerror,"lo_unlink failed"); + pg_raise_conn_error( rb_ePGerror, self, "lo_unlink failed"); return Qnil; } @@ -4328,6 +4352,7 @@ rb_define_singleton_method(rb_cPGconn, "quote_ident", pgconn_s_quote_ident, 1); rb_define_singleton_method(rb_cPGconn, "connect_start", pgconn_s_connect_start, -1); rb_define_singleton_method(rb_cPGconn, "conndefaults", pgconn_s_conndefaults, 0); + rb_define_singleton_method(rb_cPGconn, "conninfo_parse", pgconn_s_conninfo_parse, 1); rb_define_singleton_method(rb_cPGconn, "sync_ping", pgconn_s_sync_ping, -1); rb_define_singleton_method(rb_cPGconn, "sync_connect", pgconn_s_sync_connect, -1); @@ -4345,6 +4370,9 @@ 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); +#if defined(HAVE_PQRESULTMEMORYSIZE) + rb_define_method(rb_cPGconn, "hostaddr", pgconn_hostaddr, 0); +#endif rb_define_method(rb_cPGconn, "port", pgconn_port, 0); rb_define_method(rb_cPGconn, "tty", pgconn_tty, 0); rb_define_method(rb_cPGconn, "conninfo", pgconn_conninfo, 0); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ext/pg_result.c new/ext/pg_result.c --- old/ext/pg_result.c 2022-03-31 14:39:47.000000000 +0200 +++ new/ext/pg_result.c 2022-07-27 10:04:31.000000000 +0200 @@ -1476,10 +1476,10 @@ pgresult = gvl_PQgetResult(pgconn); if( pgresult == NULL ) - rb_raise( rb_eNoResultError, "no result received - possibly an intersection with another result retrieval"); + rb_raise( rb_eNoResultError, "no result received - possibly an intersection with another query"); if( nfields != PQnfields(pgresult) ) - rb_raise( rb_eInvalidChangeOfResultFields, "number of fields must not change in single row mode"); + rb_raise( rb_eInvalidChangeOfResultFields, "number of fields changed in single row mode from %d to %d - this is a sign for intersection with another query", nfields, PQnfields(pgresult)); this->pgresult = pgresult; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/pg/connection.rb new/lib/pg/connection.rb --- old/lib/pg/connection.rb 2022-03-31 14:39:47.000000000 +0200 +++ new/lib/pg/connection.rb 2022-07-27 10:04:31.000000000 +0200 @@ -46,37 +46,6 @@ hash.map { |k,v| "#{k}=#{quote_connstr(v)}" }.join( ' ' ) end - # Decode a connection string to Hash options - # - # Value are properly unquoted and unescaped. - def self.connect_string_to_hash( str ) - options = {} - key = nil - value = String.new - str.scan(/\G\s*(?>([^\s\\\']+)\s*=\s*|([^\s\\\']+)|'((?:[^\'\\]|\\.)*)'|(\\.?)|(\S))(\s|\z)?/m) do - |k, word, sq, esc, garbage, sep| - raise ArgumentError, "unterminated quoted string in connection info string: #{str.inspect}" if garbage - if k - key = k - else - value << (word || (sq || esc).gsub(/\\(.)/, '\\1')) - end - if sep - raise ArgumentError, "missing = after #{value.inspect}" unless key - options[key.to_sym] = value - key = nil - value = String.new - end - end - options - end - - # URI defined in RFC3986 - # This regexp is modified to allow host to specify multiple comma separated components captured as <hostports> and to disallow comma in hostnames. - # Taken from: https://github.com/ruby/ruby/blob/be04006c7d2f9aeb7e9d8d09d945b3a9c7850202/lib/uri/rfc3986_parser.rb#L6 - HOST_AND_PORT = /(?<hostport>(?<host>(?<IP-literal>\[(?:(?<IPv6address>(?:\h{1,4}:){6}(?<ls32>\h{1,4}:\h{1,4}|(?<IPv4address>(?<dec-octet>[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d)\.\g<dec-octet>\.\g<dec-octet>\.\g<dec-octet>))|::(?:\h{1,4}:){5}\g<ls32>|\h{1,4}?::(?:\h{1,4}:){4}\g<ls32>|(?:(?:\h{1,4}:)?\h{1,4})?::(?:\h{1,4}:){3}\g<ls32>|(?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g<ls32>|(?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g<ls32>|(?:(?:\h{1,4}:){,4}\h{1,4})?::\g<ls32>|(?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4}|(?:(?:\h{1,4}:){,6}\h{1,4})?::)|(?<IPvFuture>v\h+\.[!$&-.0-;=A-Z_a-z~]+))\])|\g<IPv4address>|(?<reg-name>(?:%\h\h|[-\.!$&-+0-9;=A-Z_a-z~])+))?(?::(?<port>\d*))?)/ - POSTGRESQL_URI = /\A(?<URI>(?<scheme>[A-Za-z][+\-.0-9A-Za-z]*):(?<hier-part>\/\/(?<authority>(?:(?<userinfo>(?:%\h\h|[!$&-.0-;=A-Z_a-z~])*)@)?(?<hostports>#{HOST_AND_PORT}(?:,\g<hostport>)*))(?<path-abempty>(?:\/(?<segment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*))*)|(?<path-absolute>\/(?:(?<segment-nz>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])+)(?:\/\g<segment>)*)?)|(?<path-rootless>\g<segment-nz>(?:\/\g<segment>)*)|(?<path-empty>))(?:\?(?<query>[^#]*))?(?:\#(?<fragment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*))?)\z/ - # Parse the connection +args+ into a connection-parameter string. # See PG::Connection.new for valid arguments. # @@ -87,91 +56,43 @@ # * URI object # * positional arguments # - # The method adds the option "hostaddr" and "fallback_application_name" if they aren't already set. - # The URI and the options string is passed through and "hostaddr" as well as "fallback_application_name" - # are added to the end. - def self::parse_connect_args( *args ) + # The method adds the option "fallback_application_name" if it isn't already set. + # It returns a connection string with "key=value" pairs. + def self.parse_connect_args( *args ) hash_arg = args.last.is_a?( Hash ) ? args.pop.transform_keys(&:to_sym) : {} - option_string = "" iopts = {} if args.length == 1 case args.first - when URI, POSTGRESQL_URI - uri = args.first.to_s - uri_match = POSTGRESQL_URI.match(uri) - if uri_match['query'] - iopts = URI.decode_www_form(uri_match['query']).to_h.transform_keys(&:to_sym) - end - # extract "host1,host2" from "host1:5432,host2:5432" - iopts[:host] = uri_match['hostports'].split(',', -1).map do |hostport| - hostmatch = /\A#{HOST_AND_PORT}\z/.match(hostport) - hostmatch['IPv6address'] || hostmatch['IPv4address'] || hostmatch['reg-name']&.gsub(/%(\h\h)/){ $1.hex.chr } - end.join(',') - oopts = {} - when /=/ - # Option string style - option_string = args.first.to_s - iopts = connect_string_to_hash(option_string) - oopts = {} + when URI, /=/, /:\/\// + # Option or URL string style + conn_string = args.first.to_s + iopts = PG::Connection.conninfo_parse(conn_string).each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] } else # Positional parameters (only host given) iopts[CONNECT_ARGUMENT_ORDER.first.to_sym] = args.first - oopts = iopts.dup end else - # Positional parameters + # Positional parameters with host and more max = CONNECT_ARGUMENT_ORDER.length raise ArgumentError, - "Extra positional parameter %d: %p" % [ max + 1, args[max] ] if args.length > max + "Extra positional parameter %d: %p" % [ max + 1, args[max] ] if args.length > max CONNECT_ARGUMENT_ORDER.zip( args ) do |(k,v)| iopts[ k.to_sym ] = v if v end iopts.delete(:tty) # ignore obsolete tty parameter - oopts = iopts.dup end iopts.merge!( hash_arg ) - oopts.merge!( hash_arg ) - - # Resolve DNS in Ruby to avoid blocking state while connecting, when it ... - if (host=iopts[:host]) && !iopts[:hostaddr] - hostaddrs = host.split(",", -1).map do |mhost| - if !mhost.empty? && !mhost.start_with?("/") && # isn't UnixSocket - # isn't a path on Windows - (RUBY_PLATFORM !~ /mingw|mswin/ || mhost !~ /\A\w:[\/\\]/) - - if Fiber.respond_to?(:scheduler) && - Fiber.scheduler && - RUBY_VERSION < '3.1.' - - # Use a second thread to avoid blocking of the scheduler. - # `IPSocket.getaddress` isn't fiber aware before ruby-3.1. - Thread.new{ IPSocket.getaddress(mhost) rescue '' }.value - else - IPSocket.getaddress(mhost) rescue '' - end - end - end - oopts[:hostaddr] = hostaddrs.join(",") if hostaddrs.any? - end if !iopts[:fallback_application_name] - oopts[:fallback_application_name] = $0.sub( /^(.{30}).{4,}(.{30})$/ ){ $1+"..."+$2 } + iopts[:fallback_application_name] = $0.sub( /^(.{30}).{4,}(.{30})$/ ){ $1+"..."+$2 } end - if uri - uri += uri_match['query'] ? "&" : "?" - uri += URI.encode_www_form( oopts ) - return uri - else - option_string += ' ' unless option_string.empty? && oopts.empty? - return option_string + connect_hash_to_string(oopts) - end + return connect_hash_to_string(iopts) end - # call-seq: # conn.copy_data( sql [, coder] ) {|sql_result| ... } -> PG::Result # @@ -241,7 +162,7 @@ # ["more", "data", "to", "copy"] def copy_data( sql, coder=nil ) - raise PG::NotInBlockingMode, "copy_data can not be used in nonblocking mode" if nonblocking? + raise PG::NotInBlockingMode.new("copy_data can not be used in nonblocking mode", connection: self) if nonblocking? res = exec( sql ) case res.result_status @@ -273,11 +194,15 @@ yield res rescue Exception => err cancel - while get_copy_data + begin + while get_copy_data + end + rescue PG::Error + # Ignore error in cleanup to avoid losing original exception end while get_result end - raise + raise err else res = get_last_result if !res || res.result_status != PGRES_COMMAND_OK @@ -285,7 +210,7 @@ end while get_result end - raise PG::NotAllCopyDataRetrieved, "Not all COPY data retrieved" + raise PG::NotAllCopyDataRetrieved.new("Not all COPY data retrieved", connection: self) end res ensure @@ -310,16 +235,17 @@ # and a +COMMIT+ at the end of the block, or # +ROLLBACK+ if any exception occurs. def transaction + rollback = false exec "BEGIN" - res = yield(self) + yield(self) rescue Exception + rollback = true cancel if transaction_status == PG::PQTRANS_ACTIVE block exec "ROLLBACK" raise - else - exec "COMMIT" - res + ensure + exec "COMMIT" unless rollback end ### Returns an array of Hashes with connection defaults. See ::conndefaults @@ -482,10 +408,10 @@ # See also #copy_data. # def put_copy_data(buffer, encoder=nil) - until sync_put_copy_data(buffer, encoder) - flush + until res=sync_put_copy_data(buffer, encoder) + res = flush end - flush + res end alias async_put_copy_data put_copy_data @@ -545,6 +471,7 @@ def reset reset_start async_connect_or_reset(:reset_poll) + self end alias async_reset reset @@ -613,28 +540,62 @@ private def async_connect_or_reset(poll_meth) # Track the progress of the connection, waiting for the socket to become readable/writable before polling it + + if (timeo = conninfo_hash[:connect_timeout].to_i) && timeo > 0 + # Lowest timeout is 2 seconds - like in libpq + timeo = [timeo, 2].max + stop_time = timeo + Process.clock_gettime(Process::CLOCK_MONOTONIC) + end + poll_status = PG::PGRES_POLLING_WRITING until poll_status == PG::PGRES_POLLING_OK || poll_status == PG::PGRES_POLLING_FAILED - # If the socket needs to read, wait 'til it becomes readable to poll again - case poll_status - when PG::PGRES_POLLING_READING - socket_io.wait_readable + timeout = stop_time&.-(Process.clock_gettime(Process::CLOCK_MONOTONIC)) + event = if !timeout || timeout >= 0 + # If the socket needs to read, wait 'til it becomes readable to poll again + case poll_status + when PG::PGRES_POLLING_READING + if defined?(IO::READABLE) # ruby-3.0+ + socket_io.wait(IO::READABLE | IO::PRIORITY, timeout) + else + IO.select([socket_io], nil, [socket_io], timeout) + end - # ...and the same for when the socket needs to write - when PG::PGRES_POLLING_WRITING - socket_io.wait_writable + # ...and the same for when the socket needs to write + when PG::PGRES_POLLING_WRITING + if defined?(IO::WRITABLE) # ruby-3.0+ + # Use wait instead of wait_readable, since connection errors are delivered as + # exceptional/priority events on Windows. + socket_io.wait(IO::WRITABLE | IO::PRIORITY, timeout) + else + # io#wait on ruby-2.x doesn't wait for priority, so fallback to IO.select + IO.select(nil, [socket_io], [socket_io], timeout) + end + end + end + # connection to server at "localhost" (127.0.0.1), port 5433 failed: timeout expired (PG::ConnectionBad) + # connection to server on socket "/var/run/postgresql/.s.PGSQL.5433" failed: No such file or directory + unless event + if self.class.send(:host_is_named_pipe?, host) + connhost = "on socket \"#{host}\"" + elsif respond_to?(:hostaddr) + connhost = "at \"#{host}\" (#{hostaddr}), port #{port}" + else + connhost = "at \"#{host}\", port #{port}" + end + raise PG::ConnectionBad.new("connection to server #{connhost} failed: timeout expired", connection: self) end # Check to see if it's finished or failed yet poll_status = send( poll_meth ) + @last_status = status unless [PG::CONNECTION_BAD, PG::CONNECTION_OK].include?(status) end unless status == PG::CONNECTION_OK msg = error_message finish - raise PG::ConnectionBad, msg + raise PG::ConnectionBad.new(msg, connection: self) end # Set connection to nonblocking to handle all blocking states in ruby. @@ -642,8 +603,6 @@ sync_setnonblocking(true) self.flush_data = true set_default_encoding - - self end class << self @@ -698,13 +657,17 @@ # connection will have its +client_encoding+ set accordingly. # # Raises a PG::Error if the connection fails. - def new(*args, **kwargs) - conn = self.connect_start(*args, **kwargs ) or - raise(PG::Error, "Unable to create a new connection") - - raise(PG::ConnectionBad, conn.error_message) if conn.status == PG::CONNECTION_BAD + def new(*args) + conn = connect_to_hosts(*args) - conn.send(:async_connect_or_reset, :connect_poll) + if block_given? + begin + return yield conn + ensure + conn.finish + end + end + conn end alias async_connect new alias connect new @@ -712,6 +675,99 @@ alias setdb new alias setdblogin new + private def connect_to_hosts(*args) + option_string = parse_connect_args(*args) + iopts = PG::Connection.conninfo_parse(option_string).each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] } + iopts = PG::Connection.conndefaults.each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }.merge(iopts) + + errors = [] + if iopts[:hostaddr] + # hostaddr is provided -> no need to resolve hostnames + ihostaddrs = iopts[:hostaddr].split(",", -1) + + ihosts = iopts[:host].split(",", -1) if iopts[:host] + raise PG::ConnectionBad, "could not match #{ihosts.size} host names to #{ihostaddrs.size} hostaddr values" if ihosts && ihosts.size != ihostaddrs.size + + iports = iopts[:port].split(",", -1) + iports = iports * ihostaddrs.size if iports.size == 1 + raise PG::ConnectionBad, "could not match #{iports.size} port numbers to #{ihostaddrs.size} hosts" if iports.size != ihostaddrs.size + + # Try to connect to each hostaddr with separate timeout + ihostaddrs.each_with_index do |ihostaddr, idx| + oopts = iopts.merge(hostaddr: ihostaddr, port: iports[idx]) + oopts[:host] = ihosts[idx] if ihosts + c = connect_internal(oopts, errors) + return c if c + end + elsif iopts[:host] && !iopts[:host].empty? + # Resolve DNS in Ruby to avoid blocking state while connecting, when it ... + ihosts = iopts[:host].split(",", -1) + + iports = iopts[:port].split(",", -1) + iports = iports * ihosts.size if iports.size == 1 + raise PG::ConnectionBad, "could not match #{iports.size} port numbers to #{ihosts.size} hosts" if iports.size != ihosts.size + + ihosts.each_with_index do |mhost, idx| + unless host_is_named_pipe?(mhost) + addrs = if Fiber.respond_to?(:scheduler) && + Fiber.scheduler && + RUBY_VERSION < '3.1.' + + # Use a second thread to avoid blocking of the scheduler. + # `TCPSocket.gethostbyname` isn't fiber aware before ruby-3.1. + Thread.new{ Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue [''] }.value + else + Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue [''] + end + + # Try to connect to each host with separate timeout + addrs.each do |addr| + oopts = iopts.merge(hostaddr: addr, host: mhost, port: iports[idx]) + c = connect_internal(oopts, errors) + return c if c + end + else + # No hostname to resolve (UnixSocket) + oopts = iopts.merge(host: mhost, port: iports[idx]) + c = connect_internal(oopts, errors) + return c if c + end + end + else + # No host given + return connect_internal(iopts) + end + raise PG::ConnectionBad, errors.join("\n") + end + + private def connect_internal(opts, errors=nil) + begin + conn = self.connect_start(opts) or + raise(PG::Error, "Unable to create a new connection") + + raise PG::ConnectionBad.new(conn.error_message, connection: self) if conn.status == PG::CONNECTION_BAD + + conn.send(:async_connect_or_reset, :connect_poll) + rescue PG::ConnectionBad => err + if errors && !(conn && [PG::CONNECTION_AWAITING_RESPONSE].include?(conn.instance_variable_get(:@last_status))) + # Seems to be no authentication error -> try next host + errors << err + return nil + else + # Probably an authentication error + raise + end + end + conn + end + + private def host_is_named_pipe?(host_string) + host_string.empty? || host_string.start_with?("/") || # it's UnixSocket? + host_string.start_with?("@") || # it's UnixSocket in the abstract namespace? + # it's a path on Windows? + (RUBY_PLATFORM =~ /mingw|mswin/ && host_string =~ /\A([\/\\]|\w:[\/\\])/) + end + # call-seq: # PG::Connection.ping(connection_hash) -> Integer # PG::Connection.ping(connection_string) -> Integer diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/pg/exceptions.rb new/lib/pg/exceptions.rb --- old/lib/pg/exceptions.rb 2022-03-31 14:39:47.000000000 +0200 +++ new/lib/pg/exceptions.rb 2022-07-27 10:04:31.000000000 +0200 @@ -6,7 +6,13 @@ module PG - class Error < StandardError; end + class Error < StandardError + def initialize(msg=nil, connection: nil, result: nil) + @connection = connection + @result = result + super(msg) + end + end end # module PG diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/pg/version.rb new/lib/pg/version.rb --- old/lib/pg/version.rb 2022-03-31 14:39:47.000000000 +0200 +++ new/lib/pg/version.rb 2022-07-27 10:04:31.000000000 +0200 @@ -1,4 +1,4 @@ module PG # Library version - VERSION = '1.3.5' + VERSION = '1.4.2' end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/pg.rb new/lib/pg.rb --- old/lib/pg.rb 2022-03-31 14:39:47.000000000 +0200 +++ new/lib/pg.rb 2022-07-27 10:04:31.000000000 +0200 @@ -59,14 +59,14 @@ # Get the PG library version. # # +include_buildnum+ is no longer used and any value passed will be ignored. - def self::version_string( include_buildnum=nil ) - return "%s %s" % [ self.name, VERSION ] + def self.version_string( include_buildnum=nil ) + "%s %s" % [ self.name, VERSION ] end ### Convenience alias for PG::Connection.new. - def self::connect( *args, **kwargs ) - return PG::Connection.new( *args, **kwargs ) + def self.connect( *args, &block ) + Connection.new( *args, &block ) end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/metadata new/metadata --- old/metadata 2022-03-31 14:39:47.000000000 +0200 +++ new/metadata 2022-07-27 10:04:31.000000000 +0200 @@ -1,7 +1,7 @@ --- !ruby/object:Gem::Specification name: pg version: !ruby/object:Gem::Version - version: 1.3.5 + version: 1.4.2 platform: ruby authors: - Michael Granger @@ -36,7 +36,7 @@ oL1mUdzB8KrZL4/WbG5YNX6UTtJbIOu9qEFbBAy4/jtIkJX+dlNoFwd4GXQW1YNO nA== -----END CERTIFICATE----- -date: 2022-03-31 00:00:00.000000000 Z +date: 2022-07-27 00:00:00.000000000 Z dependencies: [] description: Pg is the Ruby interface to the PostgreSQL RDBMS. It works with PostgreSQL 9.3 and later. @@ -179,7 +179,7 @@ - !ruby/object:Gem::Version version: '0' requirements: [] -rubygems_version: 3.2.15 +rubygems_version: 3.3.7 signing_key: specification_version: 4 summary: Pg is the Ruby interface to the PostgreSQL RDBMS Binary files old/metadata.gz.sig and new/metadata.gz.sig differ