Skip to content

Commit

Permalink
Add PG::Connection#close_prepared and siblings
Browse files Browse the repository at this point in the history
which are new in PostgreSQL-17
  • Loading branch information
larskanis committed Nov 24, 2024
1 parent 269116d commit e3219c5
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 1 deletion.
8 changes: 8 additions & 0 deletions ext/gvl_wrappers.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@

#include "pg.h"


#ifdef HAVE_PQSETCHUNKEDROWSMODE
PGresult *PQclosePrepared(PGconn *conn, const char *stmtName){return NULL;}
PGresult *PQclosePortal(PGconn *conn, const char *portalName){return NULL;}
int PQsendClosePrepared(PGconn *conn, const char *stmtName){return 0;}
int PQsendClosePortal(PGconn *conn, const char *portalName){return 0;}
#endif

#ifdef ENABLE_GVL_UNLOCK
FOR_EACH_BLOCKING_FUNCTION( DEFINE_GVL_WRAPPER_STRUCT );
FOR_EACH_BLOCKING_FUNCTION( DEFINE_GVL_SKELETON );
Expand Down
16 changes: 16 additions & 0 deletions ext/gvl_wrappers.h
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,12 @@
#define FOR_EACH_PARAM_OF_PQdescribePortal(param) \
param(PGconn *, conn)

#define FOR_EACH_PARAM_OF_PQclosePrepared(param) \
param(PGconn *, conn)

#define FOR_EACH_PARAM_OF_PQclosePortal(param) \
param(PGconn *, conn)

#define FOR_EACH_PARAM_OF_PQgetResult(param)

#define FOR_EACH_PARAM_OF_PQputCopyData(param) \
Expand Down Expand Up @@ -196,6 +202,12 @@
#define FOR_EACH_PARAM_OF_PQsendDescribePortal(param) \
param(PGconn *, conn)

#define FOR_EACH_PARAM_OF_PQsendClosePrepared(param) \
param(PGconn *, conn)

#define FOR_EACH_PARAM_OF_PQsendClosePortal(param) \
param(PGconn *, conn)

#define FOR_EACH_PARAM_OF_PQsetClientEncoding(param) \
param(PGconn *, conn)

Expand Down Expand Up @@ -225,6 +237,8 @@
function(PQprepare, GVL_TYPE_NONVOID, PGresult *, const Oid *, paramTypes) \
function(PQdescribePrepared, GVL_TYPE_NONVOID, PGresult *, const char *, stmtName) \
function(PQdescribePortal, GVL_TYPE_NONVOID, PGresult *, const char *, portalName) \
function(PQclosePrepared, GVL_TYPE_NONVOID, PGresult *, const char *, stmtName) \
function(PQclosePortal, GVL_TYPE_NONVOID, PGresult *, const char *, portalName) \
function(PQgetResult, GVL_TYPE_NONVOID, PGresult *, PGconn *, conn) \
function(PQputCopyData, GVL_TYPE_NONVOID, int, int, nbytes) \
function(PQputCopyEnd, GVL_TYPE_NONVOID, int, const char *, errormsg) \
Expand All @@ -236,6 +250,8 @@
function(PQsendQueryPrepared, GVL_TYPE_NONVOID, int, int, resultFormat) \
function(PQsendDescribePrepared, GVL_TYPE_NONVOID, int, const char *, stmt) \
function(PQsendDescribePortal, GVL_TYPE_NONVOID, int, const char *, portal) \
function(PQsendClosePrepared, GVL_TYPE_NONVOID, int, const char *, stmt) \
function(PQsendClosePortal, GVL_TYPE_NONVOID, int, const char *, portal) \
function(PQsetClientEncoding, GVL_TYPE_NONVOID, int, const char *, encoding) \
function(PQisBusy, GVL_TYPE_NONVOID, int, PGconn *, conn) \
function(PQencryptPasswordConn, GVL_TYPE_NONVOID, char *, const char *, algorithm) \
Expand Down
167 changes: 166 additions & 1 deletion ext/pg_connection.c
Original file line number Diff line number Diff line change
Expand Up @@ -1567,6 +1567,54 @@ pgconn_sync_describe_portal(VALUE self, VALUE stmt_name)
}


#ifdef HAVE_PQSETCHUNKEDROWSMODE
/*
* call-seq:
* conn.sync_close_prepared( stmt_name ) -> PG::Result
*
* This function has the same behavior as #async_close_prepared, but is implemented using the synchronous command processing API of libpq.
* See #async_exec for the differences between the two API variants.
* It's not recommended to use explicit sync or async variants but #close_prepared instead, unless you have a good reason to do so.
*
* Available since PostgreSQL-17.
*/
static VALUE
pgconn_sync_close_prepared(VALUE self, VALUE stmt_name)
{
PGresult *result;
VALUE rb_pgresult;
t_pg_connection *this = pg_get_connection_safe( self );
const char *stmt = NIL_P(stmt_name) ? NULL : pg_cstr_enc(stmt_name, this->enc_idx);
result = gvl_PQclosePrepared(this->pgconn, stmt);
rb_pgresult = pg_new_result(result, self);
pg_result_check(rb_pgresult);
return rb_pgresult;
}

/*
* call-seq:
* conn.sync_close_portal( portal_name ) -> PG::Result
*
* This function has the same behavior as #async_close_portal, but is implemented using the synchronous command processing API of libpq.
* See #async_exec for the differences between the two API variants.
* It's not recommended to use explicit sync or async variants but #close_portal instead, unless you have a good reason to do so.
*
* Available since PostgreSQL-17.
*/
static VALUE
pgconn_sync_close_portal(VALUE self, VALUE stmt_name)
{
PGresult *result;
VALUE rb_pgresult;
t_pg_connection *this = pg_get_connection_safe( self );
const char *stmt = NIL_P(stmt_name) ? NULL : pg_cstr_enc(stmt_name, this->enc_idx);
result = gvl_PQclosePortal(this->pgconn, stmt);
rb_pgresult = pg_new_result(result, self);
pg_result_check(rb_pgresult);
return rb_pgresult;
}
#endif

/*
* call-seq:
* conn.make_empty_pgresult( status ) -> PG::Result
Expand Down Expand Up @@ -2140,12 +2188,56 @@ pgconn_send_describe_portal(VALUE self, VALUE portal)
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)
pg_raise_conn_error( rb_eUnableToSend, self, "%s", PQerrorMessage(this->pgconn));
pg_raise_conn_error( rb_eUnableToSend, self, "PQsendDescribePortal %s", PQerrorMessage(this->pgconn));

pgconn_wait_for_flush( self );
return Qnil;
}

#ifdef HAVE_PQSETCHUNKEDROWSMODE
/*
* call-seq:
* conn.send_close_prepared( statement_name ) -> nil
*
* Asynchronously send _command_ to the server. Does not block.
* Use in combination with +conn.get_result+.
*
* Available since PostgreSQL-17.
*/
static VALUE
pgconn_send_close_prepared(VALUE self, VALUE stmt_name)
{
t_pg_connection *this = pg_get_connection_safe( self );
/* returns 0 on failure */
if(gvl_PQsendClosePrepared(this->pgconn, pg_cstr_enc(stmt_name, this->enc_idx)) == 0)
pg_raise_conn_error( rb_eUnableToSend, self, "PQsendClosePrepared %s", PQerrorMessage(this->pgconn));

pgconn_wait_for_flush( self );
return Qnil;
}


/*
* call-seq:
* conn.send_close_portal( portal_name ) -> nil
*
* Asynchronously send _command_ to the server. Does not block.
* Use in combination with +conn.get_result+.
*
* Available since PostgreSQL-17.
*/
static VALUE
pgconn_send_close_portal(VALUE self, VALUE portal)
{
t_pg_connection *this = pg_get_connection_safe( self );
/* returns 0 on failure */
if(gvl_PQsendClosePortal(this->pgconn, pg_cstr_enc(portal, this->enc_idx)) == 0)
pg_raise_conn_error( rb_eUnableToSend, self, "PQsendClosePortal %s", PQerrorMessage(this->pgconn));

pgconn_wait_for_flush( self );
return Qnil;
}
#endif

static VALUE
pgconn_sync_get_result(VALUE self)
Expand Down Expand Up @@ -3536,6 +3628,67 @@ pgconn_async_describe_prepared(VALUE self, VALUE stmt_name)
return rb_pgresult;
}

#ifdef HAVE_PQSETCHUNKEDROWSMODE
/*
* call-seq:
* conn.close_prepared( statement_name ) -> PG::Result
*
* Submits a request to close the specified prepared statement, and waits for completion.
* close_prepared allows an application to close a previously prepared statement.
* Closing a statement releases all of its associated resources on the server and allows its name to be reused.
*
* statement_name can be "" or +nil+ to reference the unnamed statement.
* It is fine if no statement exists with this name, in that case the operation is a no-op.
* On success, a PG::Result with status PGRES_COMMAND_OK is returned.
*
* See also corresponding {libpq function}[https://www.postgresql.org/docs/current/libpq-exec.html#LIBPQ-PQCLOSEPREPARED].
*
* Available since PostgreSQL-17.
*/
static VALUE
pgconn_async_close_prepared(VALUE self, VALUE stmt_name)
{
VALUE rb_pgresult = Qnil;

pgconn_discard_results( self );
pgconn_send_close_prepared( self, stmt_name );
rb_pgresult = pgconn_async_get_last_result( self );

if ( rb_block_given_p() ) {
return rb_ensure( rb_yield, rb_pgresult, pg_result_clear, rb_pgresult );
}
return rb_pgresult;
}

/*
* call-seq:
* conn.close_portal( portal_name ) -> PG::Result
*
* Submits a request to close the specified portal, and waits for completion.
*
* close_portal allows an application to trigger a close of a previously created portal.
* Closing a portal releases all of its associated resources on the server and allows its name to be reused.
* (pg does not provide any direct access to portals, but you can use this function to close a cursor created with a DECLARE CURSOR SQL command.)
*
* See also corresponding {libpq function}[https://www.postgresql.org/docs/current/libpq-exec.html#LIBPQ-PQCLOSEPORTAL].
*
* Available since PostgreSQL-17.
*/
static VALUE
pgconn_async_close_portal(VALUE self, VALUE portal)
{
VALUE rb_pgresult = Qnil;

pgconn_discard_results( self );
pgconn_send_close_portal( self, portal );
rb_pgresult = pgconn_async_get_last_result( self );

if ( rb_block_given_p() ) {
return rb_ensure( rb_yield, rb_pgresult, pg_result_clear, rb_pgresult );
}
return rb_pgresult;
}
#endif

/*
* call-seq:
Expand Down Expand Up @@ -4559,13 +4712,21 @@ init_pg_connection(void)
rb_define_method(rb_cPGconn, "sync_exec_prepared", pgconn_sync_exec_prepared, -1);
rb_define_method(rb_cPGconn, "sync_describe_prepared", pgconn_sync_describe_prepared, 1);
rb_define_method(rb_cPGconn, "sync_describe_portal", pgconn_sync_describe_portal, 1);
#ifdef HAVE_PQSETCHUNKEDROWSMODE
rb_define_method(rb_cPGconn, "sync_close_prepared", pgconn_sync_close_prepared, 1);
rb_define_method(rb_cPGconn, "sync_close_portal", pgconn_sync_close_portal, 1);
#endif

rb_define_method(rb_cPGconn, "exec", pgconn_async_exec, -1);
rb_define_method(rb_cPGconn, "exec_params", pgconn_async_exec_params, -1);
rb_define_method(rb_cPGconn, "prepare", pgconn_async_prepare, -1);
rb_define_method(rb_cPGconn, "exec_prepared", pgconn_async_exec_prepared, -1);
rb_define_method(rb_cPGconn, "describe_prepared", pgconn_async_describe_prepared, 1);
rb_define_method(rb_cPGconn, "describe_portal", pgconn_async_describe_portal, 1);
#ifdef HAVE_PQSETCHUNKEDROWSMODE
rb_define_method(rb_cPGconn, "close_prepared", pgconn_async_close_prepared, 1);
rb_define_method(rb_cPGconn, "close_portal", pgconn_async_close_portal, 1);
#endif

rb_define_alias(rb_cPGconn, "async_exec", "exec");
rb_define_alias(rb_cPGconn, "async_query", "async_exec");
Expand All @@ -4574,6 +4735,10 @@ init_pg_connection(void)
rb_define_alias(rb_cPGconn, "async_exec_prepared", "exec_prepared");
rb_define_alias(rb_cPGconn, "async_describe_prepared", "describe_prepared");
rb_define_alias(rb_cPGconn, "async_describe_portal", "describe_portal");
#ifdef HAVE_PQSETCHUNKEDROWSMODE
rb_define_alias(rb_cPGconn, "async_close_prepared", "close_prepared");
rb_define_alias(rb_cPGconn, "async_close_portal", "close_portal");
#endif

rb_define_method(rb_cPGconn, "make_empty_pgresult", pgconn_make_empty_pgresult, 1);
rb_define_method(rb_cPGconn, "escape_string", pgconn_s_escape, 1);
Expand Down
6 changes: 6 additions & 0 deletions lib/pg/connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -921,6 +921,12 @@ def ping(*args)
:encrypt_password => [:async_encrypt_password, :sync_encrypt_password],
}
private_constant :REDIRECT_METHODS
if PG::Connection.instance_methods.include? :async_close_prepared
REDIRECT_METHODS.merge!({
:close_prepared => [:async_close_prepared, :sync_close_prepared],
:close_portal => [:async_close_portal, :sync_close_portal],
})
end
PG.make_shareable(REDIRECT_METHODS)

def async_send_api=(enable)
Expand Down
17 changes: 17 additions & 0 deletions spec/pg/connection_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2369,6 +2369,7 @@ def wait_check_socket(conn)
@conn.prepare("weiß2", "VALUES(123)")
r = @conn.describe_prepared("weiß2".encode("utf-16be"))
expect( r.nfields ).to eq( 1 )
expect { @conn.prepare("weiß2", "VALUES(123)") }.to raise_error(PG::DuplicatePstatement)
end

it "should convert strings to #describe_portal" do
Expand All @@ -2377,6 +2378,22 @@ def wait_check_socket(conn)
expect( r.nfields ).to eq( 3 )
end

it "should convert strings to #close_prepared", :postgresql_17 do
@conn.prepare("weiß5", "VALUES(123)")
r = @conn.close_prepared("weiß5".encode("utf-16be"))
expect( r.nfields ).to eq( 1 )
@conn.prepare("weiß5", "VALUES(123)")
r = @conn.close_prepared("weiß5".encode("utf-16be"))
end

it "should convert strings to #close_portal", :postgresql_17 do
@conn.exec "DECLARE cörsör5 CURSOR FOR VALUES(1,2,3)"
r = @conn.close_portal("cörsör5".encode("utf-16le"))
expect( r.nfields ).to eq( 3 )
@conn.exec "DECLARE cörsör5 CURSOR FOR VALUES(1,2,3)"
r = @conn.close_portal("cörsör5".encode("utf-16le"))
end

it "should convert query string to #send_query" do
@conn.send_query("VALUES('grün')".encode("utf-16be"))
expect( @conn.get_last_result.values ).to eq( [['grün']] )
Expand Down

0 comments on commit e3219c5

Please sign in to comment.