This is an automated email from the ASF dual-hosted git repository.

lidavidm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git


The following commit(s) were added to refs/heads/main by this push:
     new 7616599b0 fix(c/driver/postgresql): drain all results post-query 
(#4710)
7616599b0 is described below

commit 7616599b07fabf679854cc956b86ddc4fd87bf48
Author: David Li <[email protected]>
AuthorDate: Mon Aug 24 12:10:02 2026 +0900

    fix(c/driver/postgresql): drain all results post-query (#4710)
    
    Closes #4695.
---
 c/driver/postgresql/connection.cc                  |  2 +-
 c/driver/postgresql/statement.cc                   | 12 +++++--
 python/adbc_driver_postgresql/tests/test_dbapi.py  |  6 ++--
 .../adbc_driver_postgresql/tests/test_lowlevel.py  | 40 ++++++++++++++++++++++
 4 files changed, 54 insertions(+), 6 deletions(-)

diff --git a/c/driver/postgresql/connection.cc 
b/c/driver/postgresql/connection.cc
index 262c047d7..f4a59aa71 100644
--- a/c/driver/postgresql/connection.cc
+++ b/c/driver/postgresql/connection.cc
@@ -503,7 +503,7 @@ AdbcStatusCode PostgresConnection::EnsureTransaction(struct 
AdbcError* error) {
     return ADBC_STATUS_OK;
   }
   auto txstatus = PQtransactionStatus(conn_);
-  if (txstatus == PQTRANS_ACTIVE || txstatus == PQTRANS_INTRANS) {
+  if (txstatus == PQTRANS_INTRANS) {
     return ADBC_STATUS_OK;
   } else if (txstatus == PQTRANS_INERROR) {
     InternalAdbcSetError(error,
diff --git a/c/driver/postgresql/statement.cc b/c/driver/postgresql/statement.cc
index 37e61979c..91e39e93f 100644
--- a/c/driver/postgresql/statement.cc
+++ b/c/driver/postgresql/statement.cc
@@ -96,14 +96,20 @@ int TupleReader::GetCopyData() {
     PQclear(result_);
     result_ = PQgetResult(conn_);
     const ExecStatusType pq_status = PQresultStatus(result_);
+    int errno_result = ENODATA;
     if (pq_status != PGRES_COMMAND_OK) {
       status_ = MakeStatus(result_, "[libpq] Execution error [{}]: {}",
                            PQresStatus(pq_status), 
PQresultErrorMessage(result_))
                     .ToAdbc(&error_);
-      return InternalAdbcStatusCodeToErrno(status_);
-    } else {
-      return ENODATA;
+      errno_result = InternalAdbcStatusCodeToErrno(status_);
+    }
+
+    // Drain remaining responses
+    PQclear(result_);
+    while ((result_ = PQgetResult(conn_)) != nullptr) {
+      PQclear(result_);
     }
+    return errno_result;
   }
 
   data_.size_bytes = get_copy_res;
diff --git a/python/adbc_driver_postgresql/tests/test_dbapi.py 
b/python/adbc_driver_postgresql/tests/test_dbapi.py
index 4f6456047..8109243d7 100644
--- a/python/adbc_driver_postgresql/tests/test_dbapi.py
+++ b/python/adbc_driver_postgresql/tests/test_dbapi.py
@@ -575,11 +575,13 @@ def test_txn_status(postgres: dbapi.Connection) -> None:
 
     assert status() == "idle"
     postgres.rollback()
-    assert status() == "idle"
+    assert status() == "idle"  # because txn is lazily started
 
     with postgres.cursor() as cur:
         cur.execute("SELECT 1")
-        assert status() == "active"
+        assert status() == "active"  # because result is unread
+        cur.fetchall()
+        assert status() == "intrans"
         postgres.commit()
         assert status() == "idle"
         cur.execute("SELECT 1")
diff --git a/python/adbc_driver_postgresql/tests/test_lowlevel.py 
b/python/adbc_driver_postgresql/tests/test_lowlevel.py
index 98a355bb1..92e4659db 100644
--- a/python/adbc_driver_postgresql/tests/test_lowlevel.py
+++ b/python/adbc_driver_postgresql/tests/test_lowlevel.py
@@ -55,3 +55,43 @@ def test_failed_connection() -> None:
         adbc_driver_manager.OperationalError, match=".*libpq.*Failed to 
connect.*"
     ):
         adbc_driver_postgresql.connect("invalid")
+
+
[email protected]("drain", [False, True])
+def test_transaction(postgres_uri: str, drain: bool) -> None:
+    # regression test for https://github.com/apache/arrow-adbc/issues/4695
+    status = adbc_driver_postgresql.ConnectionOptions.TRANSACTION_STATUS.value
+    with adbc_driver_postgresql.connect(postgres_uri) as db:
+        with adbc_driver_manager.AdbcConnection(db) as conn:
+            with adbc_driver_manager.AdbcStatement(conn) as stmt:
+                stmt.set_sql_query("DROP TABLE IF EXISTS test_transaction")
+                stmt.execute_update()
+                stmt.set_sql_query("CREATE TABLE test_transaction (id INT)")
+                stmt.execute_update()
+
+        with adbc_driver_manager.AdbcConnection(db) as conn:
+            with adbc_driver_manager.AdbcStatement(conn) as stmt:
+                stmt.set_sql_query("SELECT COUNT(*) FROM test_transaction")
+                handle, _ = stmt.execute_query()
+                with pyarrow.RecordBatchReader._import_from_c(handle.address) 
as reader:
+                    if drain:
+                        result = reader.read_all()
+                        assert result[0][0].as_py() == 0
+            assert conn.get_option(status) == ("idle" if drain else "active")
+
+            conn.set_autocommit(False)
+            assert conn.get_option(status) == ("idle" if drain else "active")
+            with adbc_driver_manager.AdbcStatement(conn) as stmt:
+                stmt.set_sql_query("INSERT INTO test_transaction (id) VALUES 
(1)")
+                stmt.execute_update()
+            assert conn.get_option(status) == "intrans"
+            conn.rollback()
+            assert conn.get_option(status) == "idle"
+
+        with adbc_driver_manager.AdbcConnection(db) as conn:
+            with adbc_driver_manager.AdbcStatement(conn) as stmt:
+                stmt.set_sql_query("SELECT COUNT(*) FROM test_transaction")
+                handle, _ = stmt.execute_query()
+                with pyarrow.RecordBatchReader._import_from_c(handle.address) 
as reader:
+                    result = reader.read_all()
+                assert result[0][0].as_py() == 0

Reply via email to