Fix Describe() so that it does not abort with portal created by DECLARE.
authorTatsuo Ishii <ishii@sraoss.co.jp>
Thu, 28 Sep 2023 22:29:28 +0000 (07:29 +0900)
committerTatsuo Ishii <ishii@sraoss.co.jp>
Thu, 28 Sep 2023 22:29:28 +0000 (07:29 +0900)
When DECLARE foo CURSOR FOR is executed, a portal named "foo" is
automatically created by PostgreSQL. As the portal is not managed by
Pgpool-II, "Describe foo" message failed with "unable to execute
Describe. unable to get the bind message" error.

To fix this, make Describe() creates a dummy write query context so
that the describe message is sent to primary (streaming replication
mode) or all nodes (replication/snapshot isolation mode).

Problem analysis by Heather Lapointe.
Discussion: https://www.pgpool.net/pipermail/pgpool-general/2023-September/008995.html

src/protocol/pool_proto_modules.c

index b1ed1c499b5c8eac50b8a4a863dcc5804bd3ad0a..553aebe55869ddff9ef6aa8ba7c31249a509781e 100644 (file)
@@ -112,6 +112,8 @@ static bool multi_statement_query(char *buf);
 
 static void check_prepare(List *parse_tree_list, int len, char *contents);
 
+static POOL_QUERY_CONTEXT *create_dummy_query_context(void);
+
 /*
  * This is the workhorse of processing the pg_terminate_backend function to
  * make sure that the use of function should not trigger the backend node failover.
@@ -1784,14 +1786,22 @@ Describe(POOL_CONNECTION * frontend, POOL_CONNECTION_POOL * backend,
                                        (errmsg("Describe message from frontend."),
                                         errdetail("portal: \"%s\"", contents + 1)));
                msg = pool_get_sent_message('B', contents + 1, POOL_SENT_MESSAGE_CREATED);
-               if (!msg)
-                       ereport(FATAL,
-                                       (return_code(2),
-                                        errmsg("unable to execute Describe"),
-                                        errdetail("unable to get the bind message")));
        }
 
-       query_context = msg->query_context;
+       query_context = NULL;
+
+       if (msg)
+               query_context = msg->query_context;
+
+       else if (!msg && *contents == 'P')
+       {
+               /*
+                * It is possible that client wants to use the portal created by
+                * DECLARE CUSOR or pl/pgSQL function. If so, the describe message
+                * should only be sent to primary node.
+                */
+               query_context = create_dummy_query_context();
+       }
 
        if (query_context == NULL)
                ereport(FATAL,
@@ -4888,3 +4898,29 @@ check_prepare(List *parse_tree_list, int len, char *contents)
                pool_add_sent_message(message); /* add it to the sent message list */
        }
 }
+
+/*
+ * Create a dummy query context using get_dummy_insert_query_node().  The
+ * query destination is also set.  As a side effect, query in progress flag is
+ * set.
+ */
+static
+POOL_QUERY_CONTEXT *create_dummy_query_context(void)
+{
+       POOL_QUERY_CONTEXT *query_context;
+       Node    *node;
+       MemoryContext old_context;
+       char    *query = "UNKNOWN QUERY";
+
+       query_context = pool_init_query_context();
+       old_context = MemoryContextSwitchTo(query_context->memory_context);
+
+       node = get_dummy_insert_query_node();
+       pool_start_query(query_context, query, strlen(query), node);
+       pool_where_to_send(query_context, query_context->original_query,
+                                          query_context->parse_tree);
+
+       MemoryContextSwitchTo(old_context);
+
+       return query_context;
+}