From 96aee880ce8bc75c8851006c8fabbfebfd326a0a Mon Sep 17 00:00:00 2001 From: Yorhel Date: Fri, 7 Feb 2025 10:49:47 +0100 Subject: [PATCH] pg: ->disconnect() and docs --- FU.xs | 4 ++ FU/PG.pm | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++++- c/pgconn.c | 7 ++- 3 files changed, 152 insertions(+), 3 deletions(-) diff --git a/FU.xs b/FU.xs index 8799efb..0abce30 100644 --- a/FU.xs +++ b/FU.xs @@ -117,6 +117,10 @@ void q(fupg_conn *c, SV *sv, ...) FUPG_CONN_COOKIE; ST(0) = fupg_q(aTHX_ c, SvPVutf8_nolen(sv), ax, items); +void disconnect(fupg_conn *c) + CODE: + fupg_disconnect(c); + void DESTROY(fupg_conn *c) CODE: fupg_destroy(c); diff --git a/FU/PG.pm b/FU/PG.pm index 0dd398b..bfadbd4 100644 --- a/FU/PG.pm +++ b/FU/PG.pm @@ -53,7 +53,10 @@ package FU::PG::txn { } sub DESTROY($t) { - $t->rollback if $t->[1]; + # Can't really throw an error in DESTROY. If a rollback command fails, + # we're sufficiently screwed that the only sensible recourse is to + # disconnect and let any further operations throw an error. + eval { $t->rollback; 1 } || $t->[0]->disconnect if $t->[1]; $t->[0]->_set_cookie($t->[2] ? $t->[2][1] : 0); } } @@ -127,6 +130,43 @@ C<$major * 10000 + $minor>. For example, returns 170002 for PostgreSQL 17.2. Returns the libpq version in the same format as the C method. Also available directly as C. +=item B<< $conn->status >> + +Returns a string indicating the status of the connection. Note that this method +does not verify that the connection is still alive, the status is updated after +each command. Possible return values: + +=over + +=item idle + +Awaiting commands, not in a transaction. + +=item txn_idle + +Awaiting commands, inside a transaction. + +=item txn_done + +Idle, but a transaction object still exists. The connection is unusable until +that object goes out of scope. + +=item txn_error + +Inside a transaction that is in an error state. The transaction must be rolled +back in order to recover to a usable state. This happens automatically when the +transaction object goes out of scope. + +=item bad + +Connection is dead or otherwise unusable. + +=back + +=item B<< $conn->disconnect >> + +Close the connection. Any active transactions are rolled back and any further +attempts to use C<$conn> throw an error. =back @@ -226,7 +266,107 @@ multiple columns with the same name. =head2 Transactions -I +This module provides a convenient and safe API for I and +I. A new transaction can be started with C<< $conn->txn >>, +which returns an object that can be used to run commands inside the transaction +and control its fate. When the object goes out of scope, the transaction is +automatically rolled back if no explicit C<< $txn->commit >> has been +performed. Any attempts to run queries on the parent C<< $conn >> object will +fail while a transaction object is alive. + + { + # start a new transaction + my $txn = $conn->txn; + + # run queries + $txn->q('DELETE FROM books WHERE id = $1', 1)->exec; + + # run commands in a subtransaction + { + my $subtxn = $txn->txn; + # ... + } + + # commit + $txn->commit; + + # If $txn->commit has not been called, the transaction will be rolled back + # automatically when it goes out of scope. + } + +Transaction methods: + +=over + +=item B<< $txn->exec(..) >> and B<< $txn->q(..) >> + +Run a query inside the transaction. These work the same as the respective +methods on the parent C<$conn> object. + +=item B<< $txn->commit >> and B<< $txn->rollback >> + +Commit or abort the transaction. Any attempts to run queries on this +transaction object after this call will throw an error. + +Calling C is optional, the transaction is automatically rolled back +when the object goes out of scope. + +=item B<< $txn->txn >> + +Create a subtransaction within the current transaction. A subtransaction works +exactly the same as a top-level transaction. + +=item B<< $txn->status >> + +Like C<< $conn->status >>, but with the following status codes: + +=over + +=item idle + +Current transaction is active and awaiting commands. + +=item done + +Current transaction has either been committed or rolled back, further commands +will throw an error. + +=item error + +Current transaction is in error state and must be rolled back. + +=item txn_idle + +A subtransaction is active and awaiting commands. The current transaction is +not usable until the subtransaction goes out of scope. + +(This status code is also returned when the subtransaction is 'done', the +current implementation does not track subtransactions that closely) + +=item txn_error + +A subtransaction is in error state and awaiting to be rolled back. + +=item bad + +Connection is dead or otherwise unusable. + +=back + +=back + +Of course, if you prefer the old-fashioned manual approach to transaction +handling, that is still available: + + $conn->exec('BEGIN'); + # We're now inside a transaction + $conn->exec('COMMIT') or $conn->exec('ROLLBACK'); + +Just don't try to use transaction objects and manual transaction commands at +the same time, that won't end well. + +=back + =head2 Errors diff --git a/c/pgconn.c b/c/pgconn.c index b24a913..887a016 100644 --- a/c/pgconn.c +++ b/c/pgconn.c @@ -94,6 +94,11 @@ static const char *fupg_status(fupg_conn *c) { } } +static void fupg_disconnect(fupg_conn *c) { + PQfinish(c->conn); + c->conn = NULL; +} + static void fupg_destroy(fupg_conn *c) { PQfinish(c->conn); safefree(c); @@ -160,8 +165,8 @@ typedef struct { SV **bind; int bindn; /* Set during prepare */ - char name[32]; int prepared; + char name[32]; PGresult *describe; /* Set during execute */ int paramn;