From 8f0eb0f8e321bd3af842e145fd0e91361980c8ab Mon Sep 17 00:00:00 2001
From: bithead2k <kirk.roybal@gmail.com>
Date: Fri, 3 Jul 2026 18:56:59 -0500
Subject: [PATCH] Add PROMPT_COMMAND and dynamic prompt support to psql

Introduce bash-like PROMPT_COMMAND handling that runs before each
interactive readline prompt, with a %D escape to embed the first line
of stdout in PROMPT1/PROMPT2.  Add SHELL_EXIT for last-command status
and PROMPT_SESSION_EXPORT (off by default) to optionally export PG*
and PSQL_* environment for external prompt renderers.

Includes reset_prompt_status_after_connect() after successful \connect,
prompt regeneration in input.c, and powerline-integration.md documenting
a companion patch for the powerline psql extension.

Backward compatible: no behavior change until PROMPT_COMMAND and related
variables are configured in .psqlrc.
---
 scripts/PR-description-postgres.md    |  57 +++++++
 src/bin/psql/command.c                |   2 +
 src/bin/psql/common.c                 |  25 ++-
 src/bin/psql/help.c                   |   7 +
 src/bin/psql/input.c                  |  19 ++-
 src/bin/psql/input.h                  |   5 +-
 src/bin/psql/mainloop.c               |   3 +-
 src/bin/psql/powerline-integration.md |  67 ++++++++
 src/bin/psql/prompt.c                 | 213 ++++++++++++++++++++++++--
 src/bin/psql/prompt.h                 |   2 +
 src/bin/psql/startup.c                |   1 +
 11 files changed, 384 insertions(+), 17 deletions(-)
 create mode 100644 scripts/PR-description-postgres.md
 create mode 100644 src/bin/psql/powerline-integration.md

diff --git a/scripts/PR-description-postgres.md b/scripts/PR-description-postgres.md
new file mode 100644
index 0000000000..e3dcd65d21
--- /dev/null
+++ b/scripts/PR-description-postgres.md
@@ -0,0 +1,57 @@
+# PostgreSQL patch: dynamic psql prompts (PROMPT_COMMAND)
+
+## Summary
+
+Add bash-like `PROMPT_COMMAND` support to psql so interactive prompts can be
+regenerated before each `readline()` call, with optional session export for
+external prompt renderers.  A companion patch for the
+[powerline](https://github.com/powerline/powerline) project provides a
+reference implementation; see `src/bin/psql/powerline-integration.md`.
+
+## Backward compatibility
+
+- **Default behavior unchanged** for users who do not set new variables.
+- `PROMPT_COMMAND` and `%D` are inert until configured (same model as bash).
+- `PROMPT_SESSION_EXPORT` defaults to **off**; session environment is only
+  exported when explicitly enabled, so existing `PROMPT_COMMAND` uses are not
+  surprised by extra `setenv()` calls.
+- New psql variables (`SHELL_EXIT`, etc.) do not alter query execution.
+
+We are happy to adjust naming, defaults, or add further gating if reviewers
+prefer a different compatibility story.
+
+## Features
+
+1. **`PROMPT_COMMAND`** — run a shell command before each interactive prompt;
+   capture first line of stdout.
+2. **`%D` prompt escape** — insert `PROMPT_COMMAND` output into `PROMPT1`/`PROMPT2`.
+3. **`SHELL_EXIT`** — last command exit status (SQL failure → 1, `\!` → real
+   shell code); not updated by `PROMPT_COMMAND` itself.
+4. **`PROMPT_SESSION_EXPORT`** — when on, export `PG*` and `PSQL_*` env vars
+   for the `PROMPT_COMMAND` subprocess (connection info, txn state, row count,
+   superuser flag, optional `txid`).
+5. **Prompt refresh** — `run_prompt_command()` before each `readline()` (fixes
+   stale prompts without `rl_pre_input_hook` hacks).
+6. **Connect reset** — clear `ROW_COUNT`/`SHELL_EXIT` and refresh optional
+   `txid` after successful `\connect`.
+
+## Files touched
+
+- `src/bin/psql/prompt.c`, `prompt.h` — core prompt/PROMPT_COMMAND logic
+- `src/bin/psql/input.c`, `input.h` — regenerate prompt before readline
+- `src/bin/psql/common.c` — `SHELL_EXIT` tracking
+- `src/bin/psql/command.c` — connect hook
+- `src/bin/psql/mainloop.c`, `startup.c`, `help.c`
+- `src/bin/psql/powerline-integration.md` — integration notes (not installed)
+
+## Testing
+
+Manual: interactive psql with `PROMPT_COMMAND`, `%D`, `\c`, failed SQL,
+`\!`, and readline editing (e.g. `\c postgres` must retain spaces).
+
+Suggested follow-up: TAP test for `%D` and `SHELL_EXIT` in `src/bin/psql/t/`.
+
+## Companion patch
+
+powerline psql extension (separate PR): segments, theme, readline renderer.
+Cross-reference: `src/bin/psql/powerline-integration.md`.
\ No newline at end of file
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 7fdd4511a3..9a1be6e7cd 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_class_d.h"
 #include "command.h"
 #include "common.h"
+#include "prompt.h"
 #include "common/logging.h"
 #include "common/string.h"
 #include "copy.h"
@@ -4324,6 +4325,7 @@ do_connect(enum trivalue reuse_previous_specification,
 	pset.db = n_conn;
 	SyncVariables();
 	connection_warnings(false); /* Must be after SyncVariables */
+	reset_prompt_status_after_connect();
 
 	/* Tell the user about the new connection */
 	if (!pset.quiet)
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 7db8025d24..ac72fff4ed 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -463,6 +463,22 @@ AcceptResult(const PGresult *result, bool show_error)
 }
 
 
+/*
+ * Set SHELL_EXIT, the exit status of the last psql command (SQL or shell).
+ *
+ * SQL failures use exit code 1.  Shell commands use the real exit code
+ * (0-127, 128+signal, or -1).  PROMPT_COMMAND does not update this variable.
+ */
+static void
+SetLastExitVariable(int exit_code)
+{
+	char		buf[32];
+
+	snprintf(buf, sizeof(buf), "%d", exit_code);
+	SetVariable(pset.vars, "SHELL_EXIT", buf);
+}
+
+
 /*
  * Set special variables from a query result
  * - ERROR: true/false, whether an error occurred on this query
@@ -470,6 +486,7 @@ AcceptResult(const PGresult *result, bool show_error)
  * - ROW_COUNT: how many rows were returned or affected, or "0"
  * - LAST_ERROR_SQLSTATE: same for last error
  * - LAST_ERROR_MESSAGE: message of last error
+ * - SHELL_EXIT: 0 on success, 1 on failure
  *
  * Note: current policy is to apply this only to the results of queries
  * entered by the user, not queries generated by slash commands.
@@ -484,6 +501,7 @@ SetResultVariables(PGresult *result, bool success)
 		SetVariable(pset.vars, "ERROR", "false");
 		SetVariable(pset.vars, "SQLSTATE", "00000");
 		SetVariable(pset.vars, "ROW_COUNT", *ntuples ? ntuples : "0");
+		SetLastExitVariable(0);
 	}
 	else
 	{
@@ -502,6 +520,7 @@ SetResultVariables(PGresult *result, bool success)
 		SetVariable(pset.vars, "ROW_COUNT", "0");
 		SetVariable(pset.vars, "LAST_ERROR_SQLSTATE", code);
 		SetVariable(pset.vars, "LAST_ERROR_MESSAGE", mesg ? mesg : "");
+		SetLastExitVariable(1);
 	}
 }
 
@@ -510,6 +529,7 @@ SetResultVariables(PGresult *result, bool success)
  * Set special variables from a shell command result
  * - SHELL_ERROR: true/false, whether command returned exit code 0
  * - SHELL_EXIT_CODE: exit code according to shell conventions
+ * - SHELL_EXIT: same exit code as SHELL_EXIT_CODE
  *
  * The argument is a wait status as returned by wait(2) or waitpid(2),
  * which also applies to pclose(3) and system(3).
@@ -518,11 +538,13 @@ void
 SetShellResultVariables(int wait_result)
 {
 	char		buf[32];
+	int			exit_code = wait_result_to_exit_code(wait_result);
 
 	SetVariable(pset.vars, "SHELL_ERROR",
 				(wait_result == 0) ? "false" : "true");
-	snprintf(buf, sizeof(buf), "%d", wait_result_to_exit_code(wait_result));
+	snprintf(buf, sizeof(buf), "%d", exit_code);
 	SetVariable(pset.vars, "SHELL_EXIT_CODE", buf);
+	SetLastExitVariable(exit_code);
 }
 
 
@@ -2037,6 +2059,7 @@ ExecQueryAndProcessResults(const char *query,
 				SetVariable(pset.vars, "SQLSTATE", "00000");
 				snprintf(buf, sizeof(buf), INT64_FORMAT, total_tuples);
 				SetVariable(pset.vars, "ROW_COUNT", buf);
+				SetLastExitVariable(0);
 				/* Prevent SetResultVariables call below */
 				is_chunked_result = true;
 
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index bfd3fea177..895ec37e1e 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -433,6 +433,10 @@ helpVariables(unsigned short int pager)
 		  "    specifies the standard psql prompt\n");
 	HELP0("  PROMPT2\n"
 		  "    specifies the prompt used when a statement continues from a previous line\n");
+	HELP0("  PROMPT_COMMAND\n"
+		  "    shell command executed before each prompt; stdout available as %D\n");
+	HELP0("  PROMPT_SESSION_EXPORT\n"
+		  "    if on, export PG* and PSQL_* environment for PROMPT_COMMAND (off by default)\n");
 	HELP0("  PROMPT3\n"
 		  "    specifies the prompt used during COPY ... FROM STDIN\n");
 	HELP0("  QUIET\n"
@@ -444,6 +448,9 @@ helpVariables(unsigned short int pager)
 		  "    server's version (in short string or numeric format)\n");
 	HELP0("  SHELL_ERROR\n"
 		  "    \"true\" if the last shell command failed, \"false\" if it succeeded\n");
+	HELP0("  SHELL_EXIT\n"
+		  "    exit status of the last SQL or shell command (0 on success, 1 on SQL error,\n"
+		  "    or the shell exit code for \\!, \\copy, etc.)\n");
 	HELP0("  SHELL_EXIT_CODE\n"
 		  "    exit status of the last shell command\n");
 	HELP0("  SHOW_ALL_RESULTS\n"
diff --git a/src/bin/psql/input.c b/src/bin/psql/input.c
index 6d37359fc9..bb34196e36 100644
--- a/src/bin/psql/input.c
+++ b/src/bin/psql/input.c
@@ -16,6 +16,7 @@
 #include "common.h"
 #include "common/logging.h"
 #include "input.h"
+#include "prompt.h"
 #include "settings.h"
 #include "tab-complete.h"
 
@@ -55,7 +56,7 @@ static void finishInput(void);
  *
  * Gets a line of interactive input, using readline if desired.
  *
- * prompt: the prompt string to be used
+ * status, cstack: prompt state passed to get_prompt()
  * query_buf: buffer containing lines already read in the current command
  * (query_buf is not modified here, but may be consulted for tab completion)
  *
@@ -64,12 +65,14 @@ static void finishInput(void);
  * Caller *must* have set up sigint_interrupt_jmp before calling.
  */
 char *
-gets_interactive(const char *prompt, PQExpBuffer query_buf)
+gets_interactive(promptStatus_t status, ConditionalStack cstack,
+				 PQExpBuffer query_buf)
 {
 #ifdef USE_READLINE
 	if (useReadline)
 	{
 		char	   *result;
+		const char *prompt;
 
 		/*
 		 * Some versions of readline don't notice SIGWINCH signals that arrive
@@ -82,6 +85,15 @@ gets_interactive(const char *prompt, PQExpBuffer query_buf)
 		rl_reset_screen_size();
 #endif
 
+		/*
+		 * Regenerate the prompt immediately before readline(), the same way
+		 * bash runs PROMPT_COMMAND and re-evaluates PS1.  Using
+		 * rl_pre_input_hook with readline("") leaves the prompt invisible
+		 * until the user presses a key.
+		 */
+		run_prompt_command();
+		prompt = get_prompt(status, cstack);
+
 		/* Make current query_buf available to tab completion callback */
 		tab_completion_query_buf = query_buf;
 
@@ -100,7 +112,8 @@ gets_interactive(const char *prompt, PQExpBuffer query_buf)
 	}
 #endif
 
-	fputs(prompt, stdout);
+	run_prompt_command();
+	fputs(get_prompt(status, cstack), stdout);
 	fflush(stdout);
 	return gets_fromFile(stdin);
 }
diff --git a/src/bin/psql/input.h b/src/bin/psql/input.h
index 2a47166347..14b73c12f5 100644
--- a/src/bin/psql/input.h
+++ b/src/bin/psql/input.h
@@ -44,10 +44,13 @@
 #endif							/* HAVE_READLINE_READLINE_H, etc */
 #endif							/* HAVE_LIBREADLINE */
 
+#include "fe_utils/conditional.h"
+#include "fe_utils/psqlscan.h"
 #include "pqexpbuffer.h"
 
 
-extern char *gets_interactive(const char *prompt, PQExpBuffer query_buf);
+extern char *gets_interactive(promptStatus_t status, ConditionalStack cstack,
+							  PQExpBuffer query_buf);
 extern char *gets_fromFile(FILE *source);
 
 extern void initializeInput(int flags);
diff --git a/src/bin/psql/mainloop.c b/src/bin/psql/mainloop.c
index 1f409a573b..90dc453628 100644
--- a/src/bin/psql/mainloop.c
+++ b/src/bin/psql/mainloop.c
@@ -163,8 +163,7 @@ MainLoop(FILE *source)
 				need_redisplay = false;
 			}
 			/* Now we can fetch a line */
-			line = gets_interactive(get_prompt(prompt_status, cond_stack),
-									query_buf);
+			line = gets_interactive(prompt_status, cond_stack, query_buf);
 		}
 		else
 		{
diff --git a/src/bin/psql/powerline-integration.md b/src/bin/psql/powerline-integration.md
new file mode 100644
index 0000000000..b318d946f0
--- /dev/null
+++ b/src/bin/psql/powerline-integration.md
@@ -0,0 +1,67 @@
+# psql prompt extensions and Powerline integration
+
+This document describes optional prompt features added for dynamic prompt
+generation (similar in spirit to bash `PROMPT_COMMAND`) and their companion
+implementation in the [powerline](https://github.com/powerline/powerline) project.
+
+## Backward compatibility
+
+All behavior is **opt-in**.  With no `.psqlrc` changes, psql behaves as before
+except for the addition of new psql variables (`SHELL_EXIT`, `PROMPT_COMMAND`,
+`PROMPT_SESSION_EXPORT`, and the `%D` prompt escape) that have no effect until
+configured.
+
+| Switch | Default | Purpose |
+|--------|---------|---------|
+| `PROMPT_COMMAND` | unset | Run a shell command before each interactive prompt; first line of stdout is inserted via `%D` |
+| `PROMPT_SESSION_EXPORT` | off | When on, export `PG*` / `PSQL_*` environment for `PROMPT_COMMAND` subprocesses |
+| `%D` in `PROMPT1`/`PROMPT2` | unused | Substitute `PROMPT_COMMAND` output (only when `PROMPT_COMMAND` is set) |
+
+`PROMPT_SESSION_EXPORT` exists so maintainers can keep session export separate
+from `PROMPT_COMMAND` itself: users who only need `%D` without polluting the
+subprocess environment leave it off.
+
+## PostgreSQL side (this tree)
+
+- `PROMPT_COMMAND` — shell command before each prompt (like bash)
+- `%D` — insert first line of `PROMPT_COMMAND` stdout into `PROMPT1`/`PROMPT2`
+- `SHELL_EXIT` — exit status of the last SQL or shell command (for prompt themes)
+- `PROMPT_SESSION_EXPORT` — gate for exporting session state to the subprocess
+- Exported when `PROMPT_SESSION_EXPORT` is on: `PGDATABASE`, `PGUSER`, `PGHOST`,
+  `PGPORT`, `PSQL_SHELL_EXIT`, `PSQL_TXN`, `PSQL_ROW_COUNT`, `PSQL_TXID` (if
+  the `txid` variable is set), `PSQL_SUPERUSER`
+
+## Powerline side (companion patch)
+
+A matching **psql extension** is prepared for submission to powerline:
+
+- Repository: https://github.com/powerline/powerline
+- Extension: `psql` (`powerline-render psql left`)
+- Segments: user (with PostgreSQL superuser highlighting), database, host,
+  port, transaction, row count, txid, last exit status
+- Renderer: readline non-printing markers (`\x01`/`\x02`)
+
+See powerline’s `docs/source/usage/psql-postgres-integration.rst` (added in the
+companion patch) for `.psqlrc` configuration.
+
+## Example `.psqlrc` (requires both patches)
+
+```sql
+\set PROMPT_SESSION_EXPORT on
+\set PROMPT_COMMAND 'powerline-render psql left --last-exit-code ${PSQL_SHELL_EXIT:-0} -w ${COLUMNS:-120} 2>/dev/null'
+\set PROMPT1 '%D %x%# '
+\set PROMPT2 '%w%R%x%# '
+```
+
+Optional: snapshot transaction id at connect for the txid segment:
+
+```sql
+SELECT txid_current() AS txid \gset
+```
+
+## Deployment note
+
+Neither patch depends on the other at build time.  Full functionality appears
+when both are deployed and the user enables the variables above.  Either
+project can merge independently; cross-references in code and docs point to
+the companion implementation.
\ No newline at end of file
diff --git a/src/bin/psql/prompt.c b/src/bin/psql/prompt.c
index 4906f320e7..2d7762050e 100644
--- a/src/bin/psql/prompt.c
+++ b/src/bin/psql/prompt.c
@@ -13,11 +13,184 @@
 #endif
 
 #include "common.h"
+#include "common/logging.h"
 #include "common/string.h"
 #include "input.h"
 #include "libpq/pqcomm.h"
 #include "prompt.h"
 #include "settings.h"
+#include "variables.h"
+
+#define MAX_PROMPT_SIZE 8192
+#define MAX_PROMPT_SUBST_SIZE 8192
+
+/* stdout captured from the most recent PROMPT_COMMAND execution */
+static char prompt_command_result[MAX_PROMPT_SUBST_SIZE + 1];
+
+/*
+ * Export psql state for PROMPT_COMMAND subprocesses (powerline, etc.).
+ *
+ * Enabled only when the PROMPT_SESSION_EXPORT psql variable is on (see
+ * src/bin/psql/powerline-integration.md).  A companion implementation
+ * lives in the powerline project (psql extension).
+ *
+ * Connection settings use the standard libpq PG* names, set from the
+ * current session (PQhost, PQdb, etc.) rather than stale startup env.
+ * There is no PG* variable for last-command exit status; use
+ * PSQL_SHELL_EXIT (from :SHELL_EXIT) for that.  The psql variable ``txid`` is
+ * exported as PSQL_TXID when set (e.g. via ``\\gset`` at connect).
+ * PSQL_SUPERUSER is set to ``1`` or ``0`` from the connection's superuser
+ * status (same test used for the ``%#`` prompt escape).
+ */
+static bool
+prompt_session_export_enabled(void)
+{
+	const char *val = GetVariable(pset.vars, "PROMPT_SESSION_EXPORT");
+	bool		on = false;
+
+	if (val != NULL && ParseVariableBool(val, "PROMPT_SESSION_EXPORT", &on))
+		return on;
+
+	return false;
+}
+
+static void
+export_prompt_environment(void)
+{
+	const char *val;
+
+	val = GetVariable(pset.vars, "SHELL_EXIT");
+	if (val)
+		setenv("PSQL_SHELL_EXIT", val, 1);
+
+	if (pset.db)
+	{
+		setenv("PGDATABASE", PQdb(pset.db), 1);
+		setenv("PGUSER", session_username(), 1);
+
+		val = PQhost(pset.db);
+		if (val && val[0] != '\0')
+			setenv("PGHOST", val, 1);
+		else
+			unsetenv("PGHOST");
+
+		val = PQport(pset.db);
+		if (val && val[0] != '\0')
+			setenv("PGPORT", val, 1);
+		else
+			unsetenv("PGPORT");
+
+		switch (PQtransactionStatus(pset.db))
+		{
+			case PQTRANS_IDLE:
+				setenv("PSQL_TXN", "idle", 1);
+				break;
+			case PQTRANS_ACTIVE:
+			case PQTRANS_INTRANS:
+				setenv("PSQL_TXN", "active", 1);
+				break;
+			case PQTRANS_INERROR:
+				setenv("PSQL_TXN", "error", 1);
+				break;
+			default:
+				setenv("PSQL_TXN", "unknown", 1);
+				break;
+		}
+
+		setenv("PSQL_SUPERUSER", is_superuser() ? "1" : "0", 1);
+	}
+	else
+	{
+		unsetenv("PGDATABASE");
+		unsetenv("PGUSER");
+		unsetenv("PGHOST");
+		unsetenv("PGPORT");
+		unsetenv("PSQL_TXN");
+		unsetenv("PSQL_SUPERUSER");
+	}
+
+	val = GetVariable(pset.vars, "ROW_COUNT");
+	if (val)
+		setenv("PSQL_ROW_COUNT", val, 1);
+	else
+		unsetenv("PSQL_ROW_COUNT");
+
+	val = GetVariable(pset.vars, "txid");
+	if (val && val[0] != '\0')
+		setenv("PSQL_TXID", val, 1);
+	else
+		unsetenv("PSQL_TXID");
+}
+
+/*
+ * Run PROMPT_COMMAND, if set, before generating a prompt.
+ *
+ * Like bash's PROMPT_COMMAND, this executes a shell command before each
+ * prompt is displayed.  The first line of stdout is captured and can be
+ * inserted into PROMPT1/PROMPT2 via the %D escape.  (Unlike bash, psql
+ * cannot run the command in-process, so "export" in PROMPT_COMMAND will
+ * not affect later %`command` substitutions.)
+ */
+void
+run_prompt_command(void)
+{
+	const char *cmd = GetVariable(pset.vars, "PROMPT_COMMAND");
+	FILE	   *fd;
+	size_t		len = 0;
+	int			c;
+
+	prompt_command_result[0] = '\0';
+
+	if (cmd == NULL || cmd[0] == '\0')
+		return;
+
+	if (prompt_session_export_enabled())
+		export_prompt_environment();
+
+	fflush(NULL);
+	fd = popen(cmd, "r");
+	if (fd == NULL)
+		return;
+
+	while (len < MAX_PROMPT_SUBST_SIZE && (c = fgetc(fd)) != EOF)
+	{
+		if (c == '\n' || c == '\r')
+			break;
+		prompt_command_result[len++] = (char) c;
+	}
+	prompt_command_result[len] = '\0';
+
+	/*
+	 * Do not update SHELL_EXIT or SHELL_EXIT_CODE here: PROMPT_COMMAND is not
+	 * a user command, and overwriting would hide the status of the last SQL
+	 * or shell command (e.g. for powerline --last-exit-code).
+	 */
+	(void) pclose(fd);
+}
+
+/*
+ * Reset prompt-related psql variables after a successful \connect.
+ *
+ * Clears stale row-count and exit-status from the previous session, and
+ * refreshes :varname:`txid` when that variable is already defined (e.g. via
+ * ``\\gset`` in .psqlrc for powerline).
+ */
+void
+reset_prompt_status_after_connect(void)
+{
+	PGresult   *res;
+
+	SetVariable(pset.vars, "ROW_COUNT", "0");
+	SetVariable(pset.vars, "SHELL_EXIT", "0");
+
+	if (!pset.db || GetVariable(pset.vars, "txid") == NULL)
+		return;
+
+	res = PQexec(pset.db, "SELECT txid_current()::text");
+	if (PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) > 0)
+		SetVariable(pset.vars, "txid", PQgetvalue(res, 0, 0));
+	PQclear(res);
+}
 
 /*--------------------------
  * get_prompt
@@ -59,8 +232,16 @@
  *					 newline stripped.
  * %:name:		   - The value of the psql variable 'name'
  * (those will not be rescanned for more escape sequences!)
+ * %D			   - stdout from PROMPT_COMMAND (first line, no trailing newline)
  *
  * %[ ... %]	   - tell readline that the contained text is invisible
+ *					 (use around ANSI color sequences from prompt utilities)
+ *
+ * PROMPT_COMMAND   - if set, a shell command run before each prompt (see
+ *					 run_prompt_command()).
+ *
+ * PROMPT_SESSION_EXPORT - if on, export connection/session environment for
+ *					 PROMPT_COMMAND (see powerline-integration.md).
  *
  * If the application-wide prompts become NULL somehow, the returned string
  * will be empty (not NULL!).
@@ -70,14 +251,21 @@
 char *
 get_prompt(promptStatus_t status, ConditionalStack cstack)
 {
-#define MAX_PROMPT_SIZE 256
-	static char destination[MAX_PROMPT_SIZE + 1];
-	char		buf[MAX_PROMPT_SIZE + 1];
+	static PQExpBuffer destination = NULL;
+	char		buf[MAX_PROMPT_SUBST_SIZE + 1];
 	bool		esc = false;
 	const char *p;
 	const char *prompt_string = "? ";
 	static size_t last_prompt1_width = 0;
 
+	if (destination == NULL)
+		destination = createPQExpBuffer();
+	else
+		resetPQExpBuffer(destination);
+
+	if (PQExpBufferBroken(destination))
+		pg_fatal("out of memory");
+
 	switch (status)
 	{
 		case PROMPT_READY:
@@ -98,10 +286,8 @@ get_prompt(promptStatus_t status, ConditionalStack cstack)
 			break;
 	}
 
-	destination[0] = '\0';
-
 	for (p = prompt_string;
-		 *p && strlen(destination) < sizeof(destination) - 1;
+		 *p && destination->len < MAX_PROMPT_SIZE;
 		 p++)
 	{
 		memset(buf, 0, sizeof(buf));
@@ -307,6 +493,10 @@ get_prompt(promptStatus_t status, ConditionalStack cstack)
 					/* not here yet */
 					break;
 
+				case 'D':
+					strlcpy(buf, prompt_command_result, sizeof(buf));
+					break;
+
 				case '#':
 					if (is_superuser())
 						buf[0] = '#';
@@ -384,14 +574,17 @@ get_prompt(promptStatus_t status, ConditionalStack cstack)
 		}
 
 		if (!esc)
-			strlcat(destination, buf, sizeof(destination));
+			appendPQExpBufferStr(destination, buf);
 	}
 
+	if (PQExpBufferBroken(destination))
+		pg_fatal("out of memory");
+
 	/* Compute the visible width of PROMPT1, for PROMPT2's %w */
 	if (prompt_string == pset.prompt1)
 	{
-		char	   *p = destination;
-		char	   *end = p + strlen(p);
+		char	   *p = destination->data;
+		char	   *end = p + destination->len;
 		bool		visible = true;
 
 		last_prompt1_width = 0;
@@ -433,5 +626,5 @@ get_prompt(promptStatus_t status, ConditionalStack cstack)
 		}
 	}
 
-	return destination;
+	return destination->data;
 }
diff --git a/src/bin/psql/prompt.h b/src/bin/psql/prompt.h
index 3227cb6eeb..0c25a72038 100644
--- a/src/bin/psql/prompt.h
+++ b/src/bin/psql/prompt.h
@@ -13,5 +13,7 @@
 #include "fe_utils/psqlscan.h"
 
 char	   *get_prompt(promptStatus_t status, ConditionalStack cstack);
+void		run_prompt_command(void);
+void		reset_prompt_status_after_connect(void);
 
 #endif							/* PROMPT_H */
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index eafc7c9734..7412e47ca1 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -198,6 +198,7 @@ main(int argc, char *argv[])
 	/* Initialize variables for last error */
 	SetVariable(pset.vars, "LAST_ERROR_MESSAGE", "");
 	SetVariable(pset.vars, "LAST_ERROR_SQLSTATE", "00000");
+	SetVariable(pset.vars, "SHELL_EXIT", "0");
 
 	/* Default values for variables (that don't match the result of \unset) */
 	SetVariableBool(pset.vars, "AUTOCOMMIT");
-- 
2.43.0

