From 166744dd51562491b3a26fab4dcce577de6e3360 Mon Sep 17 00:00:00 2001 From: Yorhel Date: Fri, 7 Feb 2025 18:30:33 +0100 Subject: [PATCH] pg: Rework txn implementation + statement config API I liked the Perl implementation of transactions, but managing state between Perl and C is a bit cumbersome, so I've moved the whole thing into C. Also added a few statement configuration methods that currently don't do anything yet. --- FU.xs | 112 +++++++++++++++------- FU/PG.pm | 121 ++++++++++++------------ Makefile.PL | 1 + c/common.c | 4 +- c/pgconn.c | 257 ++++++++++++++++++++++++++++++++++---------------- c/pgtypes.c | 4 +- t/pgconnect.t | 22 ++--- 7 files changed, 335 insertions(+), 186 deletions(-) diff --git a/FU.xs b/FU.xs index 13cdca2..d700b3f 100644 --- a/FU.xs +++ b/FU.xs @@ -16,13 +16,21 @@ #define FUPG_CONN_COOKIE \ - if (c->cookie) fu_confess("Invalid attempt to run a query on the top-level connection while a transaction object exists") + if (c->cookie) fu_confess("Invalid operation on the top-level connection while a transaction object exists") + +#define FUPG_TXN_COOKIE \ + if (!t->cookie) fu_confess("Invalid operation on a transaction that has already been marked as done"); \ + if (t->cookie != t->conn->cookie) fu_confess("Invalid operation on transaction while a subtransaction object exists") #define FUPG_ST_COOKIE \ if (st->cookie != st->conn->cookie) fu_confess("Invalid cross-transaction operation on statement object") -typedef fupg_conn *fupg_txn; - +#define FUPG_STFLAGS do {\ + if (!ix) ix = FUPG_CACHE;\ + if (items == 1 || SvTRUE(ST(1))) x->stflags |= ix; \ + else x->stflags &= ~ix; \ + XSRETURN(1); \ + } while(0) MODULE = FU @@ -32,7 +40,7 @@ PROTOTYPES: DISABLE TYPEMAP: <conn); ST(0) = c->self; -void _set_cookie(fupg_conn *c, UV cookie) - CODE: - c->cookie = cookie; - -UV _get_cookie(fupg_conn *c) - CODE: - RETVAL = c->cookie; - OUTPUT: - RETVAL - void status(fupg_conn *c) CODE: - ST(0) = sv_2mortal(newSVpv(fupg_status(c), 0)); + ST(0) = sv_2mortal(newSVpv(fupg_conn_status(c), 0)); + +void cache(fupg_conn *x, ...) + ALIAS: + FU::PG::conn::text_params = FUPG_TEXT_PARAMS + FU::PG::conn::text_results = FUPG_TEXT_RESULTS + FU::PG::conn::text = FUPG_TEXT + CODE: + FUPG_STFLAGS; + +void disconnect(fupg_conn *c) + CODE: + fupg_conn_disconnect(c); + +void DESTROY(fupg_conn *c) + CODE: + fupg_conn_destroy(c); + +void txn(fupg_conn *c) + CODE: + FUPG_CONN_COOKIE; + ST(0) = fupg_conn_txn(c); void exec(fupg_conn *c, SV *sv) CODE: @@ -116,34 +136,62 @@ void exec(fupg_conn *c, SV *sv) void q(fupg_conn *c, SV *sv, ...) CODE: 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); + ST(0) = fupg_q(aTHX_ c, c->stflags, SvPVutf8_nolen(sv), ax, items); MODULE = FU PACKAGE = FU::PG::txn -void exec(fupg_txn c, SV *sv) +void DESTROY(fupg_txn *t) CODE: - ST(0) = fupg_exec(aTHX_ c, SvPVutf8_nolen(sv)); + fupg_txn_destroy(t); -void q(fupg_txn c, SV *sv, ...) +void cache(fupg_txn *x, ...) + ALIAS: + FU::PG::txn::text_params = FUPG_TEXT_PARAMS + FU::PG::txn::text_results = FUPG_TEXT_RESULTS + FU::PG::txn::text = FUPG_TEXT CODE: - ST(0) = fupg_q(aTHX_ c, SvPVutf8_nolen(sv), ax, items); + FUPG_STFLAGS; + +void status(fupg_txn *t) + CODE: + ST(0) = sv_2mortal(newSVpv(fupg_txn_status(t), 0)); + +void txn(fupg_txn *t) + CODE: + FUPG_TXN_COOKIE; + ST(0) = fupg_txn_txn(t); + +void commit(fupg_txn *t) + CODE: + FUPG_TXN_COOKIE; + fupg_txn_commit(t); + +void rollback(fupg_txn *t) + CODE: + FUPG_TXN_COOKIE; + fupg_txn_rollback(t); + +void exec(fupg_txn *t, SV *sv) + CODE: + FUPG_TXN_COOKIE; + ST(0) = fupg_exec(aTHX_ t->conn, SvPVutf8_nolen(sv)); + +void q(fupg_txn *t, SV *sv, ...) + CODE: + FUPG_TXN_COOKIE; + ST(0) = fupg_q(aTHX_ t->conn, t->stflags, SvPVutf8_nolen(sv), ax, items); MODULE = FU PACKAGE = FU::PG::st -void text_results(fupg_st *st, ...) +void cache(fupg_st *x, ...) + ALIAS: + FU::PG::st::text_params = FUPG_TEXT_PARAMS + FU::PG::st::text_results = FUPG_TEXT_RESULTS + FU::PG::st::text = FUPG_TEXT CODE: - st->text_results = items == 1 || SvTRUE(ST(1)); - XSRETURN(1); + FUPG_STFLAGS; void params(fupg_st *st) CODE: diff --git a/FU/PG.pm b/FU/PG.pm index d7cd69d..c730ad7 100644 --- a/FU/PG.pm +++ b/FU/PG.pm @@ -6,61 +6,8 @@ _load_libpq(); package FU::PG::conn { sub lib_version { FU::PG::lib_version() } - - sub txn($c) { - $c->exec('BEGIN'); - $c->_set_cookie(++$FU::PG::txn::COUNTER); - bless [$c, $FU::PG::txn::COUNTER, undef], 'FU::PG::txn'; - } }; -package FU::PG::txn { - use Carp 'confess'; - - my $COUNTER = 0; - - # Arrayref: - # 0: $conn - # 1: $cookie, a snapshot of $COUNTER that identifies this transaction, used - # to match commands against transactions. Set to undef when this - # transaction is 'done' but the object is still alive. - # 2: $parent, undef if this is a top-level transaction. - - sub commit($t) { - confess "Unable to commit transaction that has already finished" if !$t->[1]; - $t->exec($t->[2] ? "RELEASE SAVEPOINT fupg_$t->[1]" : 'COMMIT'); - $t->[1] = undef; - } - - sub rollback($t) { - confess "Unable to rollback transaction that has already finished" if !$t->[1]; - $t->exec($t->[2] ? "ROLLBACK TO SAVEPOINT fupg_$t->[1]" : 'ROLLBACK'); - $t->[1] = undef; - } - - sub txn($t) { - confess "Unable to create sub-transaction when current transaction has already finished" if !$t->[1]; - $COUNTER++; - $t->exec("SAVEPOINT fupg_$COUNTER"); - $t->[0]->_set_cookie($COUNTER); - bless [$t->[0], $COUNTER, $t], 'FU::PG::txn'; - } - - sub status($t) { - my $cs = $t->[0]->status; - return $cs if $cs eq 'bad' || ($t->[1] && $t->[0]->_get_cookie != $t->[1]); - return $cs eq 'txn_error' ? 'error' : $t->[1] ? 'idle' : 'done'; - } - - sub DESTROY($t) { - # 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); - } -} - package FU::PG::error { use overload '""' => sub($e, @) { $e->{full_message} }; } @@ -163,6 +110,16 @@ Connection is dead or otherwise unusable. =back +=item B<< $conn->cache($enable) >> + +=item B<< $conn->text_params($enable) >> + +=item B<< $conn->text_results($enable) >> + +=item B<< $conn->text($enable) >> + +Set the default settings for new statements created with B<< $conn->q() >>. + =item B<< $conn->disconnect >> Close the connection. Any active transactions are rolled back and any further @@ -198,8 +155,30 @@ used. =back -Statement objects returned by C<< $conn->q() >> can be inspected with the -following two methods: +Statement objects returned by C<< $conn->q() >> support the following +configuration parameters: + +=over + +=item B<< $st->cache($enable) >> + +Enable or disable caching of the prepared statement for this particular query. + +=item B<< $st->text_params($enable) >> + +Enable or disable sending bind parameters in the text format. + +=item B<< $st->text_results($enable) >> + +Enable or disable receiving query results in the text format. + +=item B<< $st->text($enable) >> + +Shorthand for setting C and C at the same time. + +=back + +Statement objects can be inspected with the following two methods: =over @@ -264,6 +243,13 @@ multiple columns with the same name. =back +The only time you actually need to assign a statement object to a variable is +when you want to inspect C or C, in all other cases you can +chain the methods for more concise code. For example: + + my @cols = $conn->q('SELECT a, b FROM table')->cache(0)->text->rowa; + + =head2 Transactions This module provides a convenient and safe API for I and @@ -298,12 +284,16 @@ Transaction methods: =over -=item B<< $txn->exec(..) >> and B<< $txn->q(..) >> +=item B<< $txn->exec(..) >> + +=item 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 >> +=item B<< $txn->commit >> + +=item B<< $txn->rollback >> Commit or abort the transaction. Any attempts to run queries on this transaction object after this call will throw an error. @@ -311,6 +301,21 @@ 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->cache($enable) >> + +=item B<< $txn->text_params($enable) >> + +=item B<< $txn->text_results($enable) >> + +=item B<< $txn->text($enable) >> + +Set the default settings for new statements created with B<< $txn->q() >>. + +These settings are inherited from the main connection when the transaction is +created. Subtransactions inherit these settings from their parent transaction. +Changing these settings within a transaction does not affect the main +connection or any already existing subtransactions. + =item B<< $txn->txn >> Create a subtransaction within the current transaction. A subtransaction works @@ -386,7 +391,7 @@ your database encoding is UTF-8. Non-UTF-8 databases are still supported with the text format by setting `client_encoding=utf8` as part of the connection string or by manually switching to it after C: - my $conn = FU::PG->connect(""); + my $conn = FU::PG->connect("")->text; $conn->exec('SET client_encoding=utf8'); (But you're missing out on most features this module has to offer if you're diff --git a/Makefile.PL b/Makefile.PL index 86cce2c..cb7d1e8 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -3,6 +3,7 @@ use Config; os_unsupported if $^O eq 'MSWin32'; # I don't know on which OS'es the code will work exactly, but this one I can easily rule out. os_unsupported if $Config{ivsize} < 8; +os_unsupported if $Config{byteorder} != 12345678; # pgtypes.c assumes we're little-endian os_unsupported if $Config{usequadmath}; WriteMakefile( diff --git a/c/common.c b/c/common.c index 0379ed0..dd47578 100644 --- a/c/common.c +++ b/c/common.c @@ -1,11 +1,11 @@ /* Because I don't know how to use sv_setref_pv() correctly. */ -static SV *fupg_selfobj_(pTHX_ SV **self, void *obj, const char *klass) { +static SV *fu_selfobj_(pTHX_ SV **self, void *obj, const char *klass) { *self = newSViv(PTR2IV(obj)); return sv_bless(sv_2mortal(newRV_noinc(*self)), gv_stashpv(klass, GV_ADD)); } /* Write a blessed SV to obj->self and returns a mortal ref to it */ -#define fupg_selfobj(obj, klass) fupg_selfobj_(aTHX_ &((obj)->self), obj, klass) +#define fu_selfobj(obj, klass) fu_selfobj_(aTHX_ &((obj)->self), obj, klass) diff --git a/c/pgconn.c b/c/pgconn.c index cb725ed..a08fef3 100644 --- a/c/pgconn.c +++ b/c/pgconn.c @@ -1,11 +1,55 @@ +#define FUPG_CACHE 1 +#define FUPG_TEXT_PARAMS 2 +#define FUPG_TEXT_RESULTS 4 +#define FUPG_TEXT (FUPG_TEXT_PARAMS|FUPG_TEXT_RESULTS) + typedef struct { SV *self; PGconn *conn; UV prep_counter; + UV cookie_counter; UV cookie; /* currently active transaction object; 0 = none active */ + int stflags; } fupg_conn; +struct fupg_txn { + SV *self; + struct fupg_txn *parent; + fupg_conn *conn; + UV cookie; /* 0 means done */ + int stflags; + char rollback_cmd[64]; +}; +typedef struct fupg_txn fupg_txn; + +typedef struct { + /* Set in $conn->q() */ + SV *self; /* (unused, but whatever) */ + fupg_conn *conn; /* has a refcnt on conn->self */ + UV cookie; + char *query; + SV **bind; + int nbind; + int stflags; + /* Set during prepare */ + int prepared; + char name[32]; + PGresult *describe; + /* Set during execute */ + int nparam; + int nfields; + char **param; + const fupg_type **recv; + void **recvctx; + PGresult *result; +} fupg_st; + + + + +/* Utilities */ + static SV *fupg_conn_errsv(PGconn *conn, const char *action) { dTHX; HV *hv = newHV(); @@ -15,12 +59,14 @@ static SV *fupg_conn_errsv(PGconn *conn, const char *action) { return fu_croak_hv(hv, "FU::PG::error", "FATAL: %s", PQerrorMessage(conn)); } +__attribute__((noreturn)) static void fupg_conn_croak(fupg_conn *c, const char *action) { dTHX; croak_sv(fupg_conn_errsv(c->conn, action)); } /* Takes ownership of the PGresult and croaks. */ +__attribute__((noreturn)) static void fupg_result_croak(PGresult *r, const char *action, const char *query) { dTHX; HV *hv = newHV(); @@ -70,40 +116,6 @@ static void fupg_result_croak(PGresult *r, const char *action, const char *query ); } -static SV *fupg_connect(pTHX_ const char *str) { - PGconn *conn = PQconnectdb(str); - if (PQstatus(conn) != CONNECTION_OK) { - SV *sv = fupg_conn_errsv(conn, "connect"); - PQfinish(conn); - croak_sv(sv); - } - - fupg_conn *c = safecalloc(1, sizeof(fupg_conn)); - c->conn = conn; - return fupg_selfobj(c, "FU::PG::conn"); -} - -static const char *fupg_status(fupg_conn *c) { - if (PQstatus(c->conn) == CONNECTION_BAD) return "bad"; - switch (PQtransactionStatus(c->conn)) { - case PQTRANS_IDLE: return c->cookie ? "txn_done" : "idle"; - case PQTRANS_ACTIVE: return "active"; /* can't happen, we don't do async */ - case PQTRANS_INTRANS: return "txn_idle"; - case PQTRANS_INERROR: return "txn_error"; - default: return "unknown"; - } -} - -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); -} - static SV *fupg_exec_result(pTHX_ PGresult *r) { SV *ret = &PL_sv_undef; char *tup = PQcmdTuples(r); @@ -115,6 +127,130 @@ static SV *fupg_exec_result(pTHX_ PGresult *r) { return ret; } +static void fupg_exec_ok(pTHX_ fupg_conn *c, const char *sql) { + PGresult *r = PQexec(c->conn, sql); + if (!r) fupg_conn_croak(c, "exec"); + if (PQresultStatus(r) != PGRES_COMMAND_OK) fupg_result_croak(r, "exec", sql); + PQclear(r); +} + + + + +/* Connection & transaction handling */ + +static SV *fupg_connect(pTHX_ const char *str) { + PGconn *conn = PQconnectdb(str); + if (PQstatus(conn) != CONNECTION_OK) { + SV *sv = fupg_conn_errsv(conn, "connect"); + PQfinish(conn); + croak_sv(sv); + } + + fupg_conn *c = safecalloc(1, sizeof(fupg_conn)); + c->conn = conn; + return fu_selfobj(c, "FU::PG::conn"); +} + +static const char *fupg_conn_status(fupg_conn *c) { + if (PQstatus(c->conn) == CONNECTION_BAD) return "bad"; + switch (PQtransactionStatus(c->conn)) { + case PQTRANS_IDLE: return c->cookie ? "txn_done" : "idle"; + case PQTRANS_ACTIVE: return "active"; /* can't happen, we don't do async */ + case PQTRANS_INTRANS: return "txn_idle"; + case PQTRANS_INERROR: return "txn_error"; + default: return "unknown"; + } +} + +static void fupg_conn_disconnect(fupg_conn *c) { + PQfinish(c->conn); + c->conn = NULL; +} + +static void fupg_conn_destroy(fupg_conn *c) { + PQfinish(c->conn); + safefree(c); +} + +static SV *fupg_conn_txn(pTHX_ fupg_conn *c) { + fupg_exec_ok(c, "BEGIN"); + fupg_txn *t = safecalloc(1, sizeof(fupg_txn)); + t->conn = c; + t->cookie = c->cookie = ++c->cookie_counter; + t->stflags = c->stflags; + strcpy(t->rollback_cmd, "ROLLBACK"); + SvREFCNT_inc(c->self); + return fu_selfobj(t, "FU::PG::txn"); +} + +static SV *fupg_txn_txn(pTHX_ fupg_txn *t) { + char cmd[64]; + UV cookie = ++t->conn->cookie_counter; + snprintf(cmd, sizeof(cmd), "SAVEPOINT fupg_%"UVuf, cookie); + fupg_exec_ok(t->conn, cmd); + + fupg_txn *n = safecalloc(1, sizeof(fupg_txn)); + n->conn = t->conn; + n->parent = t; + n->cookie = t->conn->cookie = cookie; + n->stflags = t->stflags; + snprintf(n->rollback_cmd, sizeof(n->rollback_cmd), "ROLLBACK TO SAVEPOINT fupg_%"UVuf, cookie); + SvREFCNT_inc(t->self); + return fu_selfobj(n, "FU::PG::txn"); +} + +static const char *fupg_txn_status(fupg_txn *t) { + if (PQstatus(t->conn->conn) == CONNECTION_BAD) return "bad"; + if (!t->cookie) return "done"; + int a = t->cookie == t->conn->cookie; + switch (PQtransactionStatus(t->conn->conn)) { + case PQTRANS_IDLE: return "done"; + case PQTRANS_ACTIVE: return "active"; + case PQTRANS_INTRANS: return a ? "idle" : "txn_idle"; + case PQTRANS_INERROR: return a ? "error" : "txn_error"; + default: return "unknown"; + } +} + +static void fupg_txn_commit(pTHX_ fupg_txn *t) { + char cmd[64]; + if (t->parent) snprintf(cmd, sizeof(cmd), "RELEASE SAVEPOINT fupg_%"UVuf, t->cookie); + else strcpy(cmd, "COMMIT"); + t->cookie = 0; + fupg_exec_ok(t->conn, cmd); +} + +static void fupg_txn_rollback(pTHX_ fupg_txn *t) { + t->cookie = 0; + fupg_exec_ok(t->conn, t->rollback_cmd); +} + +static void fupg_txn_destroy(pTHX_ fupg_txn *t) { + if (t->cookie) { + PGresult *r = PQexec(t->conn->conn, t->rollback_cmd); + /* 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. */ + if (!r || PQresultStatus(r) != PGRES_COMMAND_OK) + fupg_conn_disconnect(t->conn); + PQclear(r); + } + if (t->parent) { + t->conn->cookie = t->parent->cookie; + SvREFCNT_dec(t->parent->self); + } else { + t->conn->cookie = 0; + SvREFCNT_dec(t->conn->self); + } + safefree(t); +} + + + + +/* Querying */ + static SV *fupg_exec(pTHX_ fupg_conn *c, const char *sql) { PGresult *r = PQexec(c->conn, sql); if (!r) fupg_conn_croak(c, "exec"); @@ -129,54 +265,11 @@ static SV *fupg_exec(pTHX_ fupg_conn *c, const char *sql) { return ret; } -/* Validate a FU::PG::txn object and extract the connection */ -static fupg_conn *fupg_get_transaction(pTHX_ SV *sv) { - if (!sv_derived_from(sv, "FU::PG::txn")) goto invalid; - sv = SvRV(sv); - if (SvTYPE(sv) != SVt_PVAV) goto invalid; - AV *av = (AV *)sv; - - SV **v = av_fetch(av, 0, 0); - if (!v || !*v) goto invalid; - fupg_conn *c = (fupg_conn *)SvIVX(SvRV(*v)); - - v = av_fetch(av, 1, 0); - if (!v || !*v) goto invalid; - if (!SvOK(*v)) fu_confess("Invalid attempt to run a query on a transaction that has already finished"); - if (c->cookie != SvUV(*v)) fu_confess("Invalid cross-transaction operation"); - return c; -invalid: - fu_confess("invalid transaction object"); -} - -typedef struct { - /* Set in $conn->q() */ - SV *self; - fupg_conn *conn; /* has a refcnt on conn->self */ - UV cookie; - char *query; - SV **bind; - int nbind; - bool text_params; - bool text_results; - /* Set during prepare */ - int prepared; - char name[32]; - PGresult *describe; - /* Set during execute */ - int nparam; - int nfields; - char **param; - const fupg_type **recv; - void **recvctx; - PGresult *result; -} fupg_st; - -static SV *fupg_q(pTHX_ fupg_conn *c, const char *query, I32 ax, I32 argc) { +static SV *fupg_q(pTHX_ fupg_conn *c, int stflags, const char *query, I32 ax, I32 argc) { fupg_st *st = safecalloc(1, sizeof(fupg_st)); st->conn = c; st->cookie = c->cookie; - st->text_params = st->text_results = true; /* TODO: default to false */ + st->stflags = stflags; SvREFCNT_inc(c->self); st->query = savepv(query); @@ -190,7 +283,7 @@ static SV *fupg_q(pTHX_ fupg_conn *c, const char *query, I32 ax, I32 argc) { } } - return fupg_selfobj(st, "FU::PG::st"); + return fu_selfobj(st, "FU::PG::st"); } static void fupg_st_prepare(pTHX_ fupg_st *st) { @@ -287,7 +380,7 @@ static void fupg_st_execute(pTHX_ fupg_st *st) { * improvement */ PGresult *r = PQexecPrepared(st->conn->conn, st->name, st->nparam, (const char * const*)st->param, - NULL, NULL, st->text_results ? 0 : 1); + NULL, NULL, st->stflags & FUPG_TEXT_RESULTS ? 0 : 1); if (!r) fupg_conn_croak(st->conn , "exec"); switch (PQresultStatus(r)) { case PGRES_COMMAND_OK: @@ -300,7 +393,7 @@ static void fupg_st_execute(pTHX_ fupg_st *st) { st->recv = safecalloc(st->nfields, sizeof(*st->recv)); st->recvctx = safecalloc(st->nfields, sizeof(*st->recvctx)); for (i=0; infields; i++) { - st->recv[i] = fupg_type_lookup(st->text_results ? 0 : PQftype(r, i)); + st->recv[i] = fupg_type_lookup(st->stflags & FUPG_TEXT_RESULTS ? 0 : PQftype(r, i)); if (!st->recv[i]) fu_confess("Unable to receive query results of type %u", PQftype(r, i)); } diff --git a/c/pgtypes.c b/c/pgtypes.c index 35eaf52..6cf40e4 100644 --- a/c/pgtypes.c +++ b/c/pgtypes.c @@ -3,7 +3,9 @@ typedef void (*fupg_send_fn)(pTHX_ SV *, fustr *, void *); /* Receive function, takes a binary string and should return a Perl value. - * libpq guarantees that the given buffer is aligned to MAXIMUM_ALIGNOF. */ + * libpq guarantees that the given buffer is aligned to MAXIMUM_ALIGNOF. + * For fixed-length types, the recv function is only called after verifying + * that the input buffer has the correct length. */ typedef SV *(*fupg_recv_fn)(pTHX_ const char *, int, void *); typedef struct { diff --git a/t/pgconnect.t b/t/pgconnect.t index 588bef7..ccd848e 100644 --- a/t/pgconnect.t +++ b/t/pgconnect.t @@ -17,7 +17,7 @@ okerr FATAL => connect => qr/missing "=" after "invalid"/; ok FU::PG::lib_version() > 100000; -my $conn = FU::PG->connect($ENV{FU_TEST_DB}); +my $conn = FU::PG->connect($ENV{FU_TEST_DB})->text; $conn->_debug_trace(0); is ref $conn, 'FU::PG::conn'; @@ -153,11 +153,11 @@ subtest 'txn', sub { like $@, qr/Invalid cross-transaction/; ok !eval { $conn->exec('SELECT 1'); 1 }; - like $@, qr/Invalid attempt to run a query/; + like $@, qr/Invalid operation on the top-level connection/; ok !eval { $conn->q('SELECT 1'); 1 }; - like $@, qr/Invalid attempt to run a query/; + like $@, qr/Invalid operation on the top-level connection/; ok !eval { $conn->txn; 1 }; - like $@, qr/Invalid attempt to run a query/; + like $@, qr/Invalid operation on the top-level connection/; $txn->exec('INSERT INTO fupg_tst VALUES (1)'); $sst = $txn->q('SELECT 1'); @@ -169,18 +169,18 @@ subtest 'txn', sub { is $txn->status, 'done'; ok !eval { $txn->rollback; 1 }; - like $@, qr/Unable to rollback/; + like $@, qr/Invalid operation on a transaction that has already been marked as done/; ok !eval { $txn->commit; 1 }; - like $@, qr/Unable to commit/; + like $@, qr/Invalid operation on a transaction that has already been marked as done/; ok !eval { $txn->txn; 1 }; - like $@, qr/Unable to create/; + like $@, qr/Invalid operation on a transaction that has already been marked as done/; ok !eval { $txn->exec('select 1'); 1 }; - like $@, qr/Invalid attempt to run a query/; + like $@, qr/Invalid operation on a transaction that has already been marked as done/; ok !eval { $txn->q('select 1'); 1 }; - like $@, qr/Invalid attempt to run a query/; + like $@, qr/Invalid operation on a transaction that has already been marked as done/; ok !eval { $conn->exec('SELECT 1'); 1 }; - like $@, qr/Invalid attempt to run a query/; + like $@, qr/Invalid operation on the top-level connection/; } is $conn->status, 'idle'; is $st->val, 1; @@ -214,7 +214,7 @@ subtest 'txn', sub { ok !eval { $sub->exec('SELEXT'); 1 }; ok !eval { $txn->rollback; 1 }; - like $@, qr/Invalid cross-transaction/; + like $@, qr/Invalid operation on transaction/; is $conn->status, 'txn_error'; is $txn->status, 'txn_error';