v1-0002-Support-portal-meta-command-in-psql.patch
application/octet-stream
Filename: v1-0002-Support-portal-meta-command-in-psql.patch
Type: application/octet-stream
Part: 0
From a0cb40cd12649e277fad0cc1ddebbd6a08cac3a6 Mon Sep 17 00:00:00 2001
From: reshke <reshke@double.cloud>
Date: Wed, 17 Dec 2025 06:04:52 +0000
Subject: [PATCH v1] Support \portal meta command in psql.
---
src/bin/psql/command.c | 36 +++++++++++++++++++++
src/bin/psql/common.c | 3 +-
src/bin/psql/settings.h | 2 ++
src/bin/psql/tab-complete.in.c | 2 +-
src/test/regress/expected/psql.out | 31 ++++++++++++++++++
src/test/regress/expected/psql_pipeline.out | 21 ++++++++++++
src/test/regress/sql/psql.sql | 14 ++++++++
src/test/regress/sql/psql_pipeline.sql | 12 +++++++
8 files changed, 119 insertions(+), 2 deletions(-)
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 4a2976dddf0..759bdca772c 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -124,6 +124,8 @@ static backslashResult exec_command_print(PsqlScanState scan_state, bool active_
static backslashResult exec_command_parse(PsqlScanState scan_state, bool active_branch,
const char *cmd);
static backslashResult exec_command_password(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_portal(PsqlScanState scan_state, bool active_branch,
+ const char *cmd);
static backslashResult exec_command_prompt(PsqlScanState scan_state, bool active_branch,
const char *cmd);
static backslashResult exec_command_pset(PsqlScanState scan_state, bool active_branch);
@@ -425,6 +427,8 @@ exec_command(const char *cmd,
status = exec_command_parse(scan_state, active_branch, cmd);
else if (strcmp(cmd, "password") == 0)
status = exec_command_password(scan_state, active_branch);
+ else if (strcmp(cmd, "portal") == 0)
+ status = exec_command_portal(scan_state, active_branch, cmd);
else if (strcmp(cmd, "prompt") == 0)
status = exec_command_prompt(scan_state, active_branch, cmd);
else if (strcmp(cmd, "pset") == 0)
@@ -598,6 +602,38 @@ exec_command_bind_named(PsqlScanState scan_state, bool active_branch,
return status;
}
+
+/*
+ * \bind_named -- set query parameters for an existing prepared statement
+ */
+static backslashResult
+exec_command_portal(PsqlScanState scan_state, bool active_branch,
+ const char *cmd)
+{
+ backslashResult status = PSQL_CMD_SKIP_LINE;
+
+ if (active_branch)
+ {
+ char *opt;
+
+ /* get the mandatory prepared statement name */
+ opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
+ if (!opt)
+ {
+ pg_log_error("\\%s: missing required argument", cmd);
+ status = PSQL_CMD_ERROR;
+ }
+ else
+ {
+ pset.portalName = opt;
+ }
+ }
+ else
+ ignore_slash_options(scan_state);
+
+ return status;
+}
+
/*
* \C -- override table title (formerly change HTML caption)
*/
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index a91acbf5acc..8d5d68b9501 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -1596,7 +1596,7 @@ ExecQueryAndProcessResults(const char *query,
break;
case PSQL_SEND_EXTENDED_QUERY_PREPARED:
Assert(pset.stmtName != NULL);
- success = PQsendQueryPrepared(pset.db, NULL, pset.stmtName,
+ success = PQsendQueryPrepared(pset.db, pset.portalName, pset.stmtName,
pset.bind_nparams,
(const char *const *) pset.bind_params,
NULL, NULL, 0);
@@ -2688,6 +2688,7 @@ clean_extended_state(void)
}
pset.stmtName = NULL;
+ pset.portalName = NULL;
pset.send_mode = PSQL_SEND_QUERY;
}
diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h
index fd82303f776..5e186874bde 100644
--- a/src/bin/psql/settings.h
+++ b/src/bin/psql/settings.h
@@ -123,6 +123,8 @@ typedef struct _psqlSettings
char **bind_params; /* parameters for extended query protocol call */
char *stmtName; /* prepared statement name used for extended
* query protocol commands */
+ char *portalName; /* destincation portal name used for extended
+ * query protocol commands */
int piped_commands; /* number of piped commands */
int piped_syncs; /* number of piped syncs */
int available_results; /* number of results available to get */
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index b1ff6f6cd94..3f8134f3590 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -1939,7 +1939,7 @@ psql_completion(const char *text, int start, int end)
"\\if", "\\include", "\\include_relative", "\\ir",
"\\list", "\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
"\\out",
- "\\parse", "\\password", "\\print", "\\prompt", "\\pset",
+ "\\parse", "\\password", "\\print", "\\portal", "\\prompt", "\\pset",
"\\qecho", "\\quit",
"\\reset", "\\restrict",
"\\s", "\\sendpipeline", "\\set", "\\setenv", "\\sf",
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index c8f3932edf0..a9eda0ce606 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -160,6 +160,37 @@ LINE 1: SELECT $1, $2
foo4 | bar4
(1 row)
+-- Since portals do not survive transaction
+-- bound, we have to make explicit BEGIN-COMMIT
+BEGIN;
+-- \portal (extended query protocol)
+\bind_named stmt2 'foo5' \portal prtl1 \g
+ ?column?
+----------
+ foo5
+(1 row)
+
+-- check we prepared in correct portal
+SELECT name FROM pg_cursors WHERE statement = 'SELECT $1 ';
+ name
+-------
+ prtl1
+(1 row)
+
+\bind_named stmt3 'foo6', 'boo6' \portal prtl2 \g
+ ?column? | ?column?
+----------+----------
+ foo6, | boo6
+(1 row)
+
+-- check we prepared in correct portal
+SELECT name FROM pg_cursors WHERE statement = 'SELECT $1, $2 ';
+ name
+-------
+ prtl2
+(1 row)
+
+COMMIT;
-- \close_prepared (extended query protocol)
\close_prepared
\close_prepared: missing required argument
diff --git a/src/test/regress/expected/psql_pipeline.out b/src/test/regress/expected/psql_pipeline.out
index a0816fb10b6..870fbe861ca 100644
--- a/src/test/regress/expected/psql_pipeline.out
+++ b/src/test/regress/expected/psql_pipeline.out
@@ -417,6 +417,27 @@ SELECT $1 \bind 3 \sendpipeline
(1 row)
\endpipeline
+-- Test named portals
+-- Since portals do not survive transaction
+-- bound, we have to make explicit BEGIN-COMMIT
+BEGIN;
+\startpipeline
+SELECT 10 + $1 \parse s1 \bind_named s1 1 \portal p1 \sendpipeline \syncpipeline
+\syncpipeline
+\endpipeline
+ ?column?
+----------
+ 11
+(1 row)
+
+--recheck that statement was prepared in right portal
+SELECT name FROM pg_cursors WHERE statement = 'SELECT 10 + $1 ';
+ name
+------
+ p1
+(1 row)
+
+COMMIT;
--
-- Pipeline errors
--
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index dcdbd4fc020..095123ad320 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -68,6 +68,20 @@ SELECT $1, $2 \parse stmt3
-- Multiple \g calls mean multiple executions
\bind_named stmt2 'foo3' \g \bind_named stmt3 'foo4' 'bar4' \g
+-- Since portals do not survive transaction
+-- bound, we have to make explicit BEGIN-COMMIT
+BEGIN;
+-- \portal (extended query protocol)
+\bind_named stmt2 'foo5' \portal prtl1 \g
+-- check we prepared in correct portal
+SELECT name FROM pg_cursors WHERE statement = 'SELECT $1 ';
+
+\bind_named stmt3 'foo6', 'boo6' \portal prtl2 \g
+-- check we prepared in correct portal
+SELECT name FROM pg_cursors WHERE statement = 'SELECT $1, $2 ';
+
+COMMIT;
+
-- \close_prepared (extended query protocol)
\close_prepared
\close_prepared ''
diff --git a/src/test/regress/sql/psql_pipeline.sql b/src/test/regress/sql/psql_pipeline.sql
index 6788dceee2e..f7f7f845439 100644
--- a/src/test/regress/sql/psql_pipeline.sql
+++ b/src/test/regress/sql/psql_pipeline.sql
@@ -200,6 +200,18 @@ SELECT $1 \bind 3 \sendpipeline
\getresults 0
\endpipeline
+-- Test named portals
+-- Since portals do not survive transaction
+-- bound, we have to make explicit BEGIN-COMMIT
+BEGIN;
+\startpipeline
+SELECT 10 + $1 \parse s1 \bind_named s1 1 \portal p1 \sendpipeline \syncpipeline
+\syncpipeline
+\endpipeline
+--recheck that statement was prepared in right portal
+SELECT name FROM pg_cursors WHERE statement = 'SELECT 10 + $1 ';
+COMMIT;
+
--
-- Pipeline errors
--
--
2.43.0