pg: Add transaction & subtransaction support
Was expecting the implementation of this to get overly complicated and brittle, but using a counter-based cookie and doing parts of it in Perl made it pretty easy actually. Pretty happy with how this turned out so far. TODO: documentation -.-
This commit is contained in:
parent
9d5905e3b4
commit
171afc0268
5 changed files with 276 additions and 10 deletions
|
|
@ -13,6 +13,8 @@ typedef enum {
|
|||
} ExecStatusType;
|
||||
typedef enum { PQERRORS_TERSE, PQERRORS_DEFAULT, PQERRORS_VERBOSE, PQERRORS_SQLSTATE } PGVerbosity;
|
||||
typedef enum { PQSHOW_CONTEXT_NEVER, PQSHOW_CONTEXT_ERRORS, PQSHOW_CONTEXT_ALWAYS } PGContextVisibility;
|
||||
typedef enum { CONNECTION_OK, CONNECTION_BAD } ConnStatusType; /* There's more, but they're irrelevant to us */
|
||||
typedef enum { PQTRANS_IDLE, PQTRANS_ACTIVE, PQTRANS_INTRANS, PQTRANS_INERROR, PQTRANS_UNKNOWN } PGTransactionStatusType;
|
||||
|
||||
#define PG_DIAG_SEVERITY 'S'
|
||||
#define PG_DIAG_SEVERITY_NONLOCALIZED 'V'
|
||||
|
|
@ -62,8 +64,9 @@ typedef enum { PQSHOW_CONTEXT_NEVER, PQSHOW_CONTEXT_ERRORS, PQSHOW_CONTEXT_ALWAY
|
|||
X(PQresultStatus, ExecStatusType, const PGresult *) \
|
||||
X(PQresultVerboseErrorMessage, char *, const PGresult *, PGVerbosity, PGContextVisibility) \
|
||||
X(PQserverVersion, int, const PGconn *) \
|
||||
X(PQstatus, int, const PGconn *) \
|
||||
X(PQstatus, ConnStatusType, const PGconn *) \
|
||||
X(PQtrace, void, PGconn *, FILE *) \
|
||||
X(PQtransactionStatus, PGTransactionStatusType, const PGconn *) \
|
||||
X(PQuntrace, void, PGconn *)
|
||||
|
||||
#define X(n, r, ...) static r (*n)(__VA_ARGS__);
|
||||
|
|
|
|||
38
c/pgconn.c
38
c/pgconn.c
|
|
@ -2,6 +2,7 @@ typedef struct {
|
|||
SV *self;
|
||||
PGconn *conn;
|
||||
UV prep_counter;
|
||||
UV cookie; /* currently active transaction object; 0 = none active */
|
||||
} fupg_conn;
|
||||
|
||||
|
||||
|
|
@ -71,7 +72,7 @@ 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) != 0) {
|
||||
if (PQstatus(conn) != CONNECTION_OK) {
|
||||
SV *sv = fupg_conn_errsv(conn, "connect");
|
||||
PQfinish(conn);
|
||||
croak_sv(sv);
|
||||
|
|
@ -82,6 +83,17 @@ static SV *fupg_connect(pTHX_ const char *str) {
|
|||
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_destroy(fupg_conn *c) {
|
||||
PQfinish(c->conn);
|
||||
safefree(c);
|
||||
|
|
@ -112,6 +124,26 @@ 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)) croak("Invalid attempt to run a query on a transaction that has already finished");
|
||||
if (c->cookie != SvUV(*v)) croak("Invalid cross-transaction operation");
|
||||
return c;
|
||||
invalid:
|
||||
croak("invalid transaction object");
|
||||
}
|
||||
|
||||
/* Read a Perl value from a PGresult.
|
||||
* Currently assumes text format and just creates a PV. */
|
||||
static SV *fupg_val(pTHX_ const PGresult *r, int row, int col) {
|
||||
|
|
@ -123,6 +155,7 @@ typedef struct {
|
|||
/* Set in $conn->q() */
|
||||
SV *self;
|
||||
fupg_conn *conn; /* has a refcnt on conn->self */
|
||||
UV cookie;
|
||||
char *query;
|
||||
SV **bind;
|
||||
int bindn;
|
||||
|
|
@ -139,6 +172,7 @@ typedef struct {
|
|||
static SV *fupg_q(pTHX_ fupg_conn *c, const char *query, I32 ax, I32 argc) {
|
||||
fupg_st *st = safecalloc(1, sizeof(fupg_st));
|
||||
st->conn = c;
|
||||
st->cookie = c->cookie;
|
||||
SvREFCNT_inc(c->self);
|
||||
|
||||
st->query = savepv(query);
|
||||
|
|
@ -249,7 +283,6 @@ static void fupg_st_execute(pTHX_ fupg_st *st) {
|
|||
PGresult *r = PQexecPrepared(st->conn->conn, st->name, st->paramn, (const char * const*)st->param, NULL, NULL, 0);
|
||||
if (!r) fupg_conn_croak(st->conn , "exec");
|
||||
switch (PQresultStatus(r)) {
|
||||
case PGRES_EMPTY_QUERY:
|
||||
case PGRES_COMMAND_OK:
|
||||
case PGRES_TUPLES_OK: break;
|
||||
default: fupg_result_croak(r, "exec", st->query);
|
||||
|
|
@ -334,5 +367,4 @@ static void fupg_st_destroy(fupg_st *st) {
|
|||
|
||||
/* TODO: $st->alla, allh, flat, kvv, kva, kvh */
|
||||
/* TODO: Prepared statement caching */
|
||||
/* TODO: Transactions */
|
||||
/* TODO: Binary format fetching & type handling */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue