branch: elpa/pg
commit f8a63e17266802144f96648be79e8755eebc381f
Author: Lucius <[email protected]>
Commit: Lucius <[email protected]>
Expose ReadyForQuery transaction status
---
CHANGELOG.md | 6 ++++++
doc/src/API.md | 9 ++++++++-
pg.el | 44 +++++++++++++++++++++++++-------------------
test/test-pg.el | 16 ++++++++++++++++
4 files changed, 55 insertions(+), 20 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 56157d3634..079ce7ceb1 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
# Changelog
+## [Unreleased]
+
+- Expose the latest `ReadyForQuery` transaction status on connections via
+ `pgcon-transaction-status`.
+
+
## [0.67] - 2026-06-07
- Implement support for PostgreSQL variant Datahike with pg-datahike adapter.
diff --git a/doc/src/API.md b/doc/src/API.md
index f960618b44..8bd05ab9e5 100644
--- a/doc/src/API.md
+++ b/doc/src/API.md
@@ -107,6 +107,14 @@ PostgreSQL (e.g. `keepalives_interval`). The supported
paramspec keywords and th
environment variables are the same as for pg-connect/string (see above).
+ (pgcon-transaction-status con) -> status
+
+Return the transaction status reported by the latest `ReadyForQuery` message
on connection `CON`.
+The value is one of the raw PostgreSQL protocol status bytes: `?I` for idle,
`?T` for in a
+transaction block, or `?E` for in a failed transaction block. The value is
`nil` before the first
+`ReadyForQuery` message has been received.
+
+
(pg-exec con &rest sql) -> pgresult
Concatenate the SQL strings and send to the PostgreSQL backend over connection
`CON`. Retrieve the
@@ -292,4 +300,3 @@ to disable the library's type coercion facility. Default is
`t`.
>
> You can omit the port argument if you chose 5432 as the local end of the
> tunnel, since pg.el
> defaults to this value.
-
diff --git a/pg.el b/pg.el
index 377d370acd..fbf88a0108 100644
--- a/pg.el
+++ b/pg.el
@@ -298,7 +298,10 @@ SQL queries. To avoid this overhead on establishing a
connection, remove
:accessor pgcon-connect-info)
(connect-plist
:initform nil
- :accessor pgcon-connect-plist)))
+ :accessor pgcon-connect-plist)
+ (transaction-status
+ :initform nil
+ :accessor pgcon-transaction-status)))
(defun make-pgcon (&rest args)
(apply #'make-instance (cons 'pgcon args)))
@@ -330,6 +333,14 @@ SQL queries. To avoid this overhead on establishing a
connection, remove
(with-current-buffer (process-buffer (pgcon-process con))
pgcon--busy))
+(defun pg--set-transaction-status (con status)
+ "Store the latest ReadyForQuery transaction STATUS for CON.
+STATUS is one of ?I, ?T or ?E, as defined by the PostgreSQL wire
+protocol."
+ (setf (pgcon-transaction-status con) status)
+ (when (eql ?E status)
+ (message "PostgreSQL ReadyForQuery message with error status")))
+
(defun pg-enable-query-log (con)
"Enable logging of PostgreSQL queries on connection CON.
Queries are logged to a buffer identified by `pgcon-query-log'."
@@ -642,10 +653,12 @@ presented to the user."
(let ((c (pg--read-char con)))
(unless (member c '(?Z ?E))
(message "Unexpected message type after ErrorMsg (error was %s): %s"
e c)
- (pg--unread-char con)))
- ;; Read message length then status, which we discard.
- (pg--read-net-int con 4)
- (pg--read-char con))
+ (pg--unread-char con))
+ ;; Read message length then status.
+ (pg--read-net-int con 4)
+ (let ((status (pg--read-char con)))
+ (when (eql ?Z c)
+ (pg--set-transaction-status con status)))))
(let ((msg (format "%s%s: %s (%s)"
(pgerror-severity e)
(if context (concat " " context) "")
@@ -796,8 +809,7 @@ Uses database DBNAME, user USER and password PASSWORD."
(let ((_msglen (pg--read-net-int con 4))
(status (pg--read-char con)))
;; status is 'I' or 'T' or 'E', Idle or InTransaction or Error
- (when (eql ?E status)
- (message "PostgreSQL ReadyForQuery message with error status"))
+ (pg--set-transaction-status con status)
(and (not pg-disable-type-coercion)
(zerop (hash-table-count (pgcon-parser-by-oid con)))
(pg-initialize-parsers con))
@@ -1723,8 +1735,7 @@ Return a result structure which can be decoded using
`pg-result'."
(let ((_msglen (pg--read-net-int con 4))
(status (pg--read-char con)))
;; status is 'I' or 'T' or 'E', Idle or InTransaction or Error
- (when (eql ?E status)
- (message "PostgreSQL ReadyForQuery message with error
status"))
+ (pg--set-transaction-status con status)
(setf (pgresult-tuples result) (nreverse tuples))
(setf (pgresult-attributes result) attributes)
(pg-connection-set-busy con nil)
@@ -2108,8 +2119,7 @@ Returns a pgresult structure (see function `pg-result')."
(let ((_msglen (pg--read-net-int con 4))
(status (pg--read-char con)))
;; status is 'I' or 'T' or 'E', Idle or InTransaction or Error
- (when (eql ?E status)
- (message "PostgreSQL ReadyForQuery message with error status"))
+ (pg--set-transaction-status con status)
(setf (pgresult-tuples result) (nreverse tuples))
(pg-connection-set-busy con nil)
(cl-return-from pg-fetch result)))
@@ -2228,8 +2238,7 @@ Uses PostgreSQL connection CON."
(let ((_msglen (pg--read-net-int con 4))
(status (pg--read-char con)))
;; status is 'I' or 'T' or 'E'
- (when (eql ?E status)
- (message "PostgreSQL ReadyForQuery message with error status"))
+ (pg--set-transaction-status con status)
(cl-return-from pg-close-portal nil)))
(t
@@ -2368,8 +2377,7 @@ can be decoded using `pg-result'."
(?Z
(let ((_msglen (pg--read-net-int con 4))
(status (pg--read-char con)))
- (when (eql ?E status)
- (message "PostgreSQL ReadyForQuery message with error status"))
+ (pg--set-transaction-status con status)
(pg--trim-connection-buffers con)
(pg-connection-set-busy con nil)
(cl-return-from pg-copy-from-buffer result)))
@@ -2494,8 +2502,7 @@ can be decoded using `pg-result', but with data in BUF."
(?Z
(let ((_msglen (pg--read-net-int con 4))
(status (pg--read-char con)))
- (when (eql ?E status)
- (message "PostgreSQL ReadyForQuery message with error status"))
+ (pg--set-transaction-status con status)
(pg--trim-connection-buffers con)
(pg-connection-set-busy con nil)
(cl-return-from pg-copy-to-buffer result)))
@@ -2560,8 +2567,7 @@ can be decoded using `pg-result', but with data in BUF."
(?Z
(let ((_msglen (pg--read-net-int con 4))
(status (pg--read-char con)))
- (when (eql ?E status)
- (message "PostgreSQL ReadyForQuery message with error status"))
+ (pg--set-transaction-status con status)
(pg-connection-set-busy con nil)
(cl-return-from pg-sync nil)))
diff --git a/test/test-pg.el b/test/test-pg.el
index 817f4d159b..ba38dc81e5 100755
--- a/test/test-pg.el
+++ b/test/test-pg.el
@@ -373,6 +373,7 @@
(unless (member (pgcon-server-variant con) '(clickhouse risingwave
stoolap arcadedb pgsqlite picodata))
(pg-vector-setup con))
(pgtest-add #'pg-test-basic)
+ (pgtest-add #'pg-test-transaction-status)
(pgtest-add #'pg-test-extended)
(pgtest-add #'pg-test-insert)
(pgtest-add #'pg-test-edge-cases)
@@ -807,6 +808,21 @@
;;
https://github.com/postgres/postgres/blob/master/src/test/regress/sql/insert.sql
;;
;; https://github.com/denodrivers/postgres/blob/main/tests/data_types_test.ts
+(defun pg-test-transaction-status (con)
+ (should (eql ?I (pgcon-transaction-status con)))
+ (pg-exec con "SELECT 1")
+ (should (eql ?I (pgcon-transaction-status con)))
+ (when (eq 'postgresql (pgcon-server-variant con))
+ (unwind-protect
+ (progn
+ (pg-exec con "BEGIN")
+ (should (eql ?T (pgcon-transaction-status con)))
+ (should-error (pg-exec con "SELECT 1/0") :type 'pg-error)
+ (should (eql ?E (pgcon-transaction-status con))))
+ (ignore-errors (pg-exec con "ROLLBACK")))
+ (should (eql ?I (pgcon-transaction-status con)))))
+
+
(defun pg-test-basic (con)
(cl-labels ((row (sql) (pg-result (pg-exec con sql) :tuple 0))
(scalar (sql) (cl-first (pg-result (pg-exec con sql) :tuple 0))))