From 8444e287c12676adee6bd27328bb9cea261f46d1 Mon Sep 17 00:00:00 2001
From: Tatsuo Ishii <ishii@postgresql.org>
Date: Thu, 24 Jul 2025 10:34:59 +0900
Subject: [PATCH v3] Allow to accept simple query even if extended queries do
 not end.

Recently pgJDBC has started to issue simple query without ending
extended query protocol sequences with sync. This brought a disaster
to pgpool. So pgpool refused to accept it.
https://git.postgresql.org/gitweb/?p=pgpool2.git;a=commit;h=240c668d120065534b1d298d6facc86839fcbab9

However the situation got worse. Previously pgJDBC issued a simple
query without ending extended protocol only when "autosave=always"
option is given. But it was reported that pgJDBC has started to use it
extensively.
https://www.postgresql.org/message-id/CAGXsc%2Baoabb2xxyfckrHfTx4da8%3Ds1L9ai%2BY%2BuAS4cBPRMQD2A%40mail.gmail.com

So this commit deals with the situation.

When a simple query arrives without finishing extended protocol
sequences, before calling SimpleQuery(), ProcessBackendResponse() is
called to process pending extended query protocol replies from backend
such as parse, bind or command complete messages, instead of rejecting
the simple query. After this, SimpleQuery() can process the simple
query as usual.

Author: Tatsuo Ishii <ishii@postgresql.org>
Reported-by: Emond Papegaaij <emond.papegaaij@gmail.com>
Discussion: https://www.postgresql.org/message-id/CAGXsc%2Baoabb2xxyfckrHfTx4da8%3Ds1L9ai%2BY%2BuAS4cBPRMQD2A%40mail.gmail.com
---
 src/protocol/pool_proto_modules.c             | 61 +++++++++++++------
 .../082.guard_against_bad_protocol/test.sh    |  5 +-
 2 files changed, 47 insertions(+), 19 deletions(-)

diff --git a/src/protocol/pool_proto_modules.c b/src/protocol/pool_proto_modules.c
index 11befe979..fdb41bbf0 100644
--- a/src/protocol/pool_proto_modules.c
+++ b/src/protocol/pool_proto_modules.c
@@ -221,23 +221,6 @@ SimpleQuery(POOL_CONNECTION *frontend,
 	/* save last query string for logging purpose */
 	strlcpy(query_string_buffer, contents, sizeof(query_string_buffer));
 
-	/*
-	 * Check if extended query protocol message ended.  If not, reject the
-	 * query and raise an error to terminate the session to avoid hanging up.
-	 * However if we are processing a reset query (frontend == NULL), we skip
-	 * the check as we don't want to raise a error.
-	 */
-	if (SL_MODE)
-	{
-		if (frontend != NULL &&
-			(pool_is_doing_extended_query_message() ||
-			 pool_pending_message_head_message()))
-
-			ereport(FATAL,
-					(errmsg("simple query \"%s\" arrived before ending an extended query message",
-							query_string_buffer)));
-	}
-
 	/* show ps status */
 	query_ps_status(contents, backend);
 
@@ -2930,6 +2913,44 @@ ProcessFrontendResponse(POOL_CONNECTION *frontend,
 				ereport(LOG,
 						(errmsg("Query message from frontend."),
 						 errdetail("query: \"%s\"", contents)));
+
+			/*
+			 * Check if extended query protocol message ended. If not, process
+			 * any pending response from backend using
+			 * ProcessBackendResponse(). However if we are processing a reset
+			 * query (frontend == NULL), we skip the check as we don't need to
+			 * care about any pending response from backend.
+			 */
+			if (SL_MODE)
+			{
+				int			state;
+				short		num_fields;
+
+				if (frontend != NULL &&
+					(pool_is_doing_extended_query_message() ||
+					 pool_pending_message_exists()))
+				{
+					ereport(DEBUG1,
+							(errmsg("simple query \"%s\" arrived before ending an extended query message",
+									contents)));
+
+					/* if pending message exists, process it */
+					while (pool_pending_message_exists())
+					{
+						/*
+						 * read_kind_from_backend requires that query is
+						 * inprogress and doing extended query state because
+						 * it needs to refer to proper query context in
+						 * session context.
+						 */
+						pool_set_query_in_progress();
+						pool_set_doing_extended_query_message();
+						/* process pending responses from backend */
+						ProcessBackendResponse(frontend, backend, &state, &num_fields);
+					}
+				}
+				pool_unset_doing_extended_query_message();
+			}
 			status = SimpleQuery(frontend, backend, len, contents);
 			break;
 
@@ -3080,6 +3101,12 @@ ProcessFrontendResponse(POOL_CONNECTION *frontend,
 	return status;
 }
 
+/*
+ * Read one backend response and process it.
+ *
+ * state: used for processing reset query
+ * num_fields: used in V2 protocol
+ */
 POOL_STATUS
 ProcessBackendResponse(POOL_CONNECTION *frontend,
 					   POOL_CONNECTION_POOL *backend,
diff --git a/src/test/regression/tests/082.guard_against_bad_protocol/test.sh b/src/test/regression/tests/082.guard_against_bad_protocol/test.sh
index f1d16e6f3..c4f6e90b4 100755
--- a/src/test/regression/tests/082.guard_against_bad_protocol/test.sh
+++ b/src/test/regression/tests/082.guard_against_bad_protocol/test.sh
@@ -22,6 +22,7 @@ echo -n "creating test environment..."
 $PGPOOL_SETUP || exit 1
 echo "done."
 echo "backend_weight1=0" >> etc/pgpool.conf
+echo "client_min_messages=debug1" >> etc/pgpool.conf
 source ./bashrc.ports
 ./startall
 wait_for_pgpool_startup
@@ -29,8 +30,8 @@ wait_for_pgpool_startup
 # test1:
 # Wait for 1 seconds before pgproto ended.
 # Usually 1 seconds should be enough to finish pgproto.
-# If test suceeded, pgpool emits an error message:
-# "FATAL:  simple query "SAVEPOINT PGJDBC_AUTOSAVE" arrived before ending an extended query message"
+# If the test suceeds, pgpool emits a debug message:
+# "simple query "SAVEPOINT PGJDBC_AUTOSAVE" arrived before ending an extended query message"
 # grep command below should catch the message.
 timeout 1 $PGPROTO -d $PGDATABASE -p $PGPOOL_PORT -f ../pgproto.data |& grep 'simple query "SAVEPOINT PGJDBC_AUTOSAVE" arrived '
 
-- 
2.25.1

