Pgpool-II version
4.8devel (787313f)
Description
A statement-level Describe for a nonexistent named prepared statement is a well-formed extended-protocol request with a defined PostgreSQL error response. PostgreSQL returns SQLSTATE 26000; in a pipeline segment it ignores later commands until Sync, and the client observes PIPELINE_ABORTED followed by PIPELINE_SYNC. Pgpool instead terminates the frontend connection.
Pgpool dispatches frontend message D directly to Describe(). In its statement branch, Describe() looks up a prior SQL PREPARE or protocol Parse in Pgpool's sent-message cache:
/* Prepared Statement */
if (*contents == 'S')
{
/* <snip> */
msg = pool_get_sent_message('Q', contents + 1, POOL_SENT_MESSAGE_CREATED);
if (!msg)
msg = pool_get_sent_message('P', contents + 1, POOL_SENT_MESSAGE_CREATED);
if (!msg)
ereport(FATAL,
(return_code(2),
errmsg("unable to execute Describe"),
errdetail("unable to get the parse message")));
}
The reproducer has neither a prior SQL PREPARE nor a protocol Parse for missing_prepared, so neither lookup finds a cache entry. Pgpool raises its local FATAL before PostgreSQL can perform the corresponding statement lookup, so the client receives neither the backend error nor the pipeline recovery results for the later command and Sync.
Reproduce
- Start PostgreSQL 16 and Pgpool-II.
- Run the following with the direct and Pgpool DSNs supplied by the environment:
#!/usr/bin/env python3
import select
import time
from psycopg.pq import DiagnosticField, ExecStatus, PGconn
def result_name(result):
name = ExecStatus(result.status).name
if result.status == ExecStatus.FATAL_ERROR:
code = result.error_field(DiagnosticField.SQLSTATE) or b"?"
name += f"[{code.decode(errors='replace')}]"
return name
def drain(conn):
results = []
deadline = time.monotonic() + 5
while time.monotonic() < deadline:
try:
conn.flush()
conn.consume_input()
except Exception as exc:
return results + [f"CONNECTION_LOST: {exc}"]
while (result := conn.get_result()) is not None:
results.append(result_name(result))
if results and results[-1] == "PIPELINE_SYNC":
return results
select.select([conn.socket], [], [], 0.05)
return results + ["TIMEOUT"]
def run(label, dsn):
conn = PGconn.connect(dsn.encode())
try:
if conn.status != 0:
raise RuntimeError(conn.error_message.decode(errors="replace"))
conn.nonblocking = 1
conn.enter_pipeline_mode()
conn.send_query_params(b"SELECT 1", None)
conn.send_describe_prepared(b"missing_prepared")
conn.send_query_params(b"SELECT 2", None)
conn.pipeline_sync()
print(f"{label}: {drain(conn)}")
except Exception as exc:
print(f"{label}: {type(exc).__name__}: {exc}")
finally:
conn.finish()
run("direct", DIRECT_DSN)
run("pgpool", PGPOOL_DSN)
Expected behavior
With the same pipeline segment, the client should observe FATAL_ERROR[26000] for Describe, PIPELINE_ABORTED for SELECT 2, and PIPELINE_SYNC at the Sync boundary.
Actual behavior
Direct PostgreSQL prints:
direct: ['TUPLES_OK', 'FATAL_ERROR[26000]', 'PIPELINE_ABORTED', 'PIPELINE_SYNC']
Pgpool prints:
pgpool: ['TUPLES_OK', 'FATAL_ERROR[XX000]', 'CONNECTION_LOST']
Pgpool emits its own XX000 error and closes the frontend connection before the Describe reaches PostgreSQL. The client receives neither PIPELINE_ABORTED nor PIPELINE_SYNC.
Pgpool-II version
4.8devel(787313f)Description
A statement-level
Describefor a nonexistent named prepared statement is a well-formed extended-protocol request with a defined PostgreSQL error response. PostgreSQL returns SQLSTATE26000; in a pipeline segment it ignores later commands untilSync, and the client observesPIPELINE_ABORTEDfollowed byPIPELINE_SYNC. Pgpool instead terminates the frontend connection.Pgpool dispatches frontend message
Ddirectly toDescribe(). In its statement branch,Describe()looks up a prior SQLPREPAREor protocolParsein Pgpool's sent-message cache:The reproducer has neither a prior SQL
PREPAREnor a protocolParseformissing_prepared, so neither lookup finds a cache entry. Pgpool raises its localFATALbefore PostgreSQL can perform the corresponding statement lookup, so the client receives neither the backend error nor the pipeline recovery results for the later command andSync.Reproduce
Expected behavior
With the same pipeline segment, the client should observe
FATAL_ERROR[26000]forDescribe,PIPELINE_ABORTEDforSELECT 2, andPIPELINE_SYNCat theSyncboundary.Actual behavior
Direct PostgreSQL prints:
Pgpool prints:
Pgpool emits its own
XX000error and closes the frontend connection before theDescribereaches PostgreSQL. The client receives neitherPIPELINE_ABORTEDnorPIPELINE_SYNC.