*** a/doc/src/sgml/ref/psql-ref.sgml
--- b/doc/src/sgml/ref/psql-ref.sgml
***************
*** 1617,1622 **** Tue Oct 26 21:40:57 CEST 1999
--- 1617,1654 ----
+ \gset prefix
+
+
+
+ Sends the current query input buffer to the server and stores the
+ query's output into corresponding variable. The preceding query must
+ return only one row. You can define prefix, that will be used as prefix
+ for target variables.
+ Example:
+
+ foo=> SELECT 'hello' AS var1, 'wonderful' AS var2, 'world!' AS var3 \gset result_
+ foo=> \echo :result_var1 :result_var3
+ hello world!
+
+
+
+ When this command fails, then related variables has undefined content.
+
+
+ Assign NULL to target variable is same as cleaning this
+ variable. Possible to use
+ coalesce as protection against to this behave.
+
+
+
+
+
+
\h or \help [ command ]
*** a/src/bin/psql/command.c
--- b/src/bin/psql/command.c
***************
*** 748,753 **** exec_command(const char *cmd,
--- 748,767 ----
status = PSQL_CMD_SEND;
}
+ /* \gset send a query and store result */
+ else if (strcmp(cmd, "gset") == 0)
+ {
+ char *prefix = psql_scan_slash_option(scan_state,
+ OT_NORMAL, NULL, false);
+
+ if (!prefix)
+ pset.gvprefix = pg_strdup("");
+ else
+ pset.gvprefix = prefix;
+
+ status = PSQL_CMD_SEND;
+ }
+
/* help */
else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
{
*** a/src/bin/psql/common.c
--- b/src/bin/psql/common.c
***************
*** 818,823 **** PrintQueryResults(PGresult *results)
--- 818,950 ----
/*
+ * StoreQueryTuple: assuming query result is OK, save first tuple
+ *
+ * Returns true if successful, false otherwise.
+ */
+ static bool
+ StoreQueryTuple(PGresult *result)
+ {
+ bool success = true;
+
+ if (PQntuples(result) < 1)
+ {
+ psql_error("no data found\n");
+ success = false;
+ }
+ else if (PQntuples(result) > 1)
+ {
+ psql_error("too many rows\n");
+ success = false;
+ }
+ else
+ {
+ int i;
+
+ for (i = 0; i < PQnfields(result); i++)
+ {
+ char *varname;
+ char *colname = PQfname(result, i);
+
+ if (strcmp(pset.gvprefix, "") != 0)
+ {
+ /* concate prefix and column name */
+ varname = pg_malloc(strlen(pset.gvprefix) + strlen(colname) + 1);
+ strcpy(varname, pset.gvprefix);
+ strcat(varname, colname);
+ }
+ else
+ varname = pg_strdup(colname);
+
+ if (!PQgetisnull(result, 0, i))
+ {
+ char *value = PQgetvalue(result, 0, i);
+
+ if (!SetVariable(pset.vars, varname, value))
+ {
+ psql_error("invalid variable name: \"%s\"\n",
+ varname);
+ success = false;
+ break;
+ }
+ }
+ else
+ {
+ /*
+ * we don't try to substitute NULL by some other value - current
+ * implementation of variables doesn't support NULL, so when some
+ * result is NULL, then just clean a target variable. We still
+ * request valid variable name.
+ */
+ if (!valid_variable_name(varname))
+ {
+ psql_error("invalid variable name: \"%s\"\n",
+ varname);
+ success = false;
+ break;
+ }
+
+ /* ignore result, variable should not exists */
+ DeleteVariable(pset.vars, varname);
+ }
+
+ free(varname);
+ }
+ }
+
+ return success;
+ }
+
+
+ /*
+ * StoreQueryResult: store first row of result to selected variables
+ *
+ * Note: Utility function for use by SendQuery() only.
+ *
+ * Returns true if the query executed successfully, false otherwise.
+ */
+ static bool
+ StoreQueryResult(PGresult *result)
+ {
+ bool success;
+
+ switch (PQresultStatus(result))
+ {
+ case PGRES_TUPLES_OK:
+ success = StoreQueryTuple(result);
+ break;
+
+ case PGRES_COMMAND_OK:
+ case PGRES_EMPTY_QUERY:
+ psql_error("no data found\n");
+ success = false;
+ break;
+
+ case PGRES_COPY_OUT:
+ case PGRES_COPY_IN:
+ psql_error("COPY is not supported by \\gset command\n");
+ success = false;
+ break;
+
+ case PGRES_BAD_RESPONSE:
+ case PGRES_NONFATAL_ERROR:
+ case PGRES_FATAL_ERROR:
+ success = false;
+ psql_error("bad response\n");
+ break;
+
+ default:
+ success = false;
+ psql_error("unexpected PQresultStatus: %d\n",
+ PQresultStatus(result));
+ break;
+ }
+
+ return success;
+ }
+
+
+ /*
* SendQuery: send the query string to the backend
* (and print out results)
*
***************
*** 943,949 **** SendQuery(const char *query)
/* but printing results isn't: */
if (OK && results)
! OK = PrintQueryResults(results);
}
else
{
--- 1070,1081 ----
/* but printing results isn't: */
if (OK && results)
! {
! if (pset.gvprefix)
! OK = StoreQueryResult(results);
! else
! OK = PrintQueryResults(results);
! }
}
else
{
***************
*** 1019,1024 **** SendQuery(const char *query)
--- 1151,1163 ----
PQclear(results);
+ /* clean gset prefix if was used */
+ if (pset.gvprefix)
+ {
+ free(pset.gvprefix);
+ pset.gvprefix = NULL;
+ }
+
/* Possible microtiming output */
if (pset.timing)
printf(_("Time: %.3f ms\n"), elapsed_msec);
***************
*** 1067,1072 **** ExecQueryUsingCursor(const char *query, double *elapsed_msec)
--- 1206,1212 ----
instr_time before,
after;
int flush_error;
+ bool first_iteration = true;
*elapsed_msec = 0;
***************
*** 1167,1172 **** ExecQueryUsingCursor(const char *query, double *elapsed_msec)
--- 1307,1339 ----
ntuples = PQntuples(results);
+ if (pset.gvprefix)
+ {
+ /* second iteration should to return zero rows */
+ if (first_iteration)
+ {
+ OK = StoreQueryResult(results);
+ PQclear(results);
+ first_iteration = false;
+ }
+ else if (ntuples > 0)
+ {
+ psql_error("too many rows\n");
+ OK = false;
+ }
+
+ if (!OK)
+ {
+ flush_error = fflush(pset.queryFout);
+ break;
+ }
+
+ if (ntuples < pset.fetch_count || cancel_pressed)
+ break;
+ else
+ continue;
+ }
+
if (ntuples < pset.fetch_count)
{
/* this is the last result set, so allow footer decoration */
***************
*** 1222,1227 **** ExecQueryUsingCursor(const char *query, double *elapsed_msec)
--- 1389,1399 ----
free(pset.gfname);
pset.gfname = NULL;
}
+ else if (pset.gvprefix)
+ {
+ free(pset.gvprefix);
+ pset.gvprefix = NULL;
+ }
else if (did_pager)
{
ClosePager(pset.queryFout);
*** a/src/bin/psql/help.c
--- b/src/bin/psql/help.c
***************
*** 165,177 **** slashUsage(unsigned short int pager)
currdb = PQdb(pset.db);
! output = PageOutput(94, pager);
/* if you add/remove a line here, change the row count above */
fprintf(output, _("General\n"));
fprintf(output, _(" \\copyright show PostgreSQL usage and distribution terms\n"));
fprintf(output, _(" \\g [FILE] or ; execute query (and send results to file or |pipe)\n"));
fprintf(output, _(" \\h [NAME] help on syntax of SQL commands, * for all commands\n"));
fprintf(output, _(" \\q quit psql\n"));
fprintf(output, "\n");
--- 165,178 ----
currdb = PQdb(pset.db);
! output = PageOutput(95, pager);
/* if you add/remove a line here, change the row count above */
fprintf(output, _("General\n"));
fprintf(output, _(" \\copyright show PostgreSQL usage and distribution terms\n"));
fprintf(output, _(" \\g [FILE] or ; execute query (and send results to file or |pipe)\n"));
+ fprintf(output, _(" \\gset prefix execute query and store result in internal variables\n"));
fprintf(output, _(" \\h [NAME] help on syntax of SQL commands, * for all commands\n"));
fprintf(output, _(" \\q quit psql\n"));
fprintf(output, "\n");
*** a/src/bin/psql/settings.h
--- b/src/bin/psql/settings.h
***************
*** 73,78 **** typedef struct _psqlSettings
--- 73,79 ----
printQueryOpt popt;
char *gfname; /* one-shot file output argument for \g */
+ char *gvprefix; /* one-shot prefix argument for \gset */
bool notty; /* stdin or stdout is not a tty (as determined
* on startup) */
*** a/src/bin/psql/tab-complete.c
--- b/src/bin/psql/tab-complete.c
***************
*** 856,862 **** psql_completion(char *text, int start, int end)
"\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL",
"\\dn", "\\do", "\\dp", "\\drds", "\\ds", "\\dS", "\\dt", "\\dT", "\\dv", "\\du",
"\\e", "\\echo", "\\ef", "\\encoding",
! "\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\ir", "\\l",
"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
"\\set", "\\sf", "\\t", "\\T",
--- 856,862 ----
"\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL",
"\\dn", "\\do", "\\dp", "\\drds", "\\ds", "\\dS", "\\dt", "\\dT", "\\dv", "\\du",
"\\e", "\\echo", "\\ef", "\\encoding",
! "\\f", "\\g", "\\gset", "\\h", "\\help", "\\H", "\\i", "\\ir", "\\l",
"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
"\\set", "\\sf", "\\t", "\\T",
*** a/src/bin/psql/variables.c
--- b/src/bin/psql/variables.c
***************
*** 18,24 ****
* underscore. Keep this in sync with the definition of variable_char in
* psqlscan.l.
*/
! static bool
valid_variable_name(const char *name)
{
const unsigned char *ptr = (const unsigned char *) name;
--- 18,24 ----
* underscore. Keep this in sync with the definition of variable_char in
* psqlscan.l.
*/
! bool
valid_variable_name(const char *name)
{
const unsigned char *ptr = (const unsigned char *) name;
*** a/src/bin/psql/variables.h
--- b/src/bin/psql/variables.h
***************
*** 48,53 **** int GetVariableNum(VariableSpace space,
--- 48,54 ----
void PrintVariables(VariableSpace space);
+ bool valid_variable_name(const char *name);
bool SetVariable(VariableSpace space, const char *name, const char *value);
bool SetVariableAssignHook(VariableSpace space, const char *name, VariableAssignHook hook);
bool SetVariableBool(VariableSpace space, const char *name);
*** /dev/null
--- b/src/test/regress/expected/psql_cmd.out
***************
*** 0 ****
--- 1,67 ----
+ -- \gset
+ select 10 as test01, 20 as test02, 'Hello' as test03 \gset pref01_
+ \echo :pref01_test01 :pref01_test02 :pref01_test03
+ 10 20 Hello
+ -- should fail bad name
+ select 10 as "bad name" \gset
+ invalid variable name: "bad name"
+ select NULL as "bad name" \gset
+ invalid variable name: "bad name"
+ -- more in one line
+ select 1 as x, 2 as y \gset pref01_ \\ \echo :pref01_x
+ 1
+ select 3 as x, 4 as y \gset pref01_ \echo :pref01_x \echo :pref01_y
+ 3
+ 4
+ select 5 as x, 6 as y \gset pref01_ \\ \g \echo :pref01_x :pref01_y
+ x | y
+ ---+---
+ 5 | 6
+ (1 row)
+
+ 5 6
+ select 7 as x, 8 as y \g \gset pref01_ \echo :pref01_x :pref01_y
+ x | y
+ ---+---
+ 7 | 8
+ (1 row)
+
+ 7 8
+ -- NULL handling
+ select 'Hello' as var1 \gset pref01_
+ \echo :pref01_var1
+ Hello
+ select NULL as var1 \gset pref01_
+ \echo :pref01_var1
+ :pref01_var1
+ --should file - no returned one tuple
+ select 10 as test01, 20 as test02, 'Hello' as test03 from generate_series(1,3) \gset pref01_
+ too many rows
+ select 10 as test01, 20 as test02, 'Hello' as test03 from generate_series(1,3) where false \gset pref01_
+ no data found
+ -- should to work with cursors
+ \set FETCH_COUNT 1
+ select 1 as x, 2 as y \gset pref01_ \\ \echo :pref01_x
+ 1
+ select 3 as x, 4 as y \gset pref01_ \echo :pref01_x \echo :pref01_y
+ 3
+ 4
+ select 5 as x, 6 as y \gset pref01_ \\ \g \echo :pref01_x :pref01_y
+ x | y
+ ---+---
+ 5 | 6
+ (1 row)
+
+ 5 6
+ select 7 as x, 8 as y \g \gset pref01_ \echo :pref01_x :pref01_y
+ x | y
+ ---+---
+ 7 | 8
+ (1 row)
+
+ 7 8
+ --should file - no returned one tuple
+ select 10 as test01, 20 as test02, 'Hello' as test03 from generate_series(1,3) \gset pref01_
+ too many rows
+ select 10 as test01, 20 as test02, 'Hello' as test03 from generate_series(1,3) where false \gset pref01_
+ no data found
*** a/src/test/regress/parallel_schedule
--- b/src/test/regress/parallel_schedule
***************
*** 21,26 **** test: strings
--- 21,31 ----
test: numerology
# ----------
+ # verify console features
+ # ----------
+ test: psql_cmd
+
+ # ----------
# The second group of parallel tests
# ----------
test: point lseg box path polygon circle date time timetz timestamp timestamptz interval abstime reltime tinterval inet macaddr tstypes comments
*** a/src/test/regress/serial_schedule
--- b/src/test/regress/serial_schedule
***************
*** 135,137 **** test: largeobject
--- 135,138 ----
test: with
test: xml
test: stats
+ test: psql_cmd
*** /dev/null
--- b/src/test/regress/sql/psql_cmd.sql
***************
*** 0 ****
--- 1,36 ----
+ -- \gset
+
+ select 10 as test01, 20 as test02, 'Hello' as test03 \gset pref01_
+
+ \echo :pref01_test01 :pref01_test02 :pref01_test03
+
+ -- should fail bad name
+ select 10 as "bad name" \gset
+ select NULL as "bad name" \gset
+
+ -- more in one line
+ select 1 as x, 2 as y \gset pref01_ \\ \echo :pref01_x
+ select 3 as x, 4 as y \gset pref01_ \echo :pref01_x \echo :pref01_y
+ select 5 as x, 6 as y \gset pref01_ \\ \g \echo :pref01_x :pref01_y
+ select 7 as x, 8 as y \g \gset pref01_ \echo :pref01_x :pref01_y
+
+ -- NULL handling
+ select 'Hello' as var1 \gset pref01_
+ \echo :pref01_var1
+ select NULL as var1 \gset pref01_
+ \echo :pref01_var1
+
+ --should file - no returned one tuple
+ select 10 as test01, 20 as test02, 'Hello' as test03 from generate_series(1,3) \gset pref01_
+ select 10 as test01, 20 as test02, 'Hello' as test03 from generate_series(1,3) where false \gset pref01_
+
+ -- should to work with cursors
+ \set FETCH_COUNT 1
+ select 1 as x, 2 as y \gset pref01_ \\ \echo :pref01_x
+ select 3 as x, 4 as y \gset pref01_ \echo :pref01_x \echo :pref01_y
+ select 5 as x, 6 as y \gset pref01_ \\ \g \echo :pref01_x :pref01_y
+ select 7 as x, 8 as y \g \gset pref01_ \echo :pref01_x :pref01_y
+
+ --should file - no returned one tuple
+ select 10 as test01, 20 as test02, 'Hello' as test03 from generate_series(1,3) \gset pref01_
+ select 10 as test01, 20 as test02, 'Hello' as test03 from generate_series(1,3) where false \gset pref01_