Re: [PATCH] Add PROMPT_COMMAND and dynamic prompt support to psql

From: Kirk Roybal <kirk(dot)roybal(at)gmail(dot)com>
To: pgsql-hackers(at)postgresql(dot)org
Subject: Re: [PATCH] Add PROMPT_COMMAND and dynamic prompt support to psql
Date: 2026-09-12 02:50:46
Message-ID: CAE==Dwermr=gqWyfYmrKgp4pGnE+GDWK5TPYRn1uzjRC9yP2CQ@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi,

v3 follow-up: one-page merge rationale, explicit review ask, and a TAP test
(as offered in the original post). Patch attached.

========================================================================
Why merge this (one page)
========================================================================

Problem
-------
Interactive psql prompts are static between PROMPT1/PROMPT2 redefinitions.
Users who want connection context, transaction state, or last-command status
in the prompt today either fork psql or abuse %`shell` on every redraw.
There
is no bash-shaped hook to regenerate the prompt once per readline.

Bash-shaped solution
--------------------
Mirror bash, not invent a plugin API:

* PROMPT_COMMAND — if set, run a shell command before each interactive
prompt; capture the first line of stdout (existing prompt buffer limit).
* %D — PROMPT1/PROMPT2 escape for that captured line (renderer output).
* SHELL_EXIT — last *user* command status (SQL or \!); PROMPT_COMMAND does
not overwrite it, so themes can show the prior command’s outcome.
* Prompt refresh — run_prompt_command() immediately before readline() in
input.c (not rl_pre_input_hook, which left the prompt blank until a
key).
* \connect housekeeping — reset ROW_COUNT / SHELL_EXIT after successful
\connect; optional :txid refresh when that variable already exists.

Zero behavior change unless configured
--------------------------------------
Defaults are inert:

| Knob | Default | Effect when unset/off |
|-------------------------|---------|------------------------------------|
| PROMPT_COMMAND | unset | no shell, no popen |
| %D | unused | empty substitution if referenced |
| PROMPT_SESSION_EXPORT | off | no PG*/PSQL_* export into subprocess |
| SHELL_EXIT | "0" | new variable only; scripts ignore it |

Non-interactive use, existing scripts, and default interactive sessions are
unchanged. No .psqlrc change ⇒ no new behavior.

Security / gating story (PROMPT_SESSION_EXPORT + shell)
-------------------------------------------------------
PROMPT_COMMAND uses popen() — the same trust model as existing %`command`
prompt escapes and \!. It only runs when the user (or their .psqlrc)
explicitly sets PROMPT_COMMAND.

PROMPT_SESSION_EXPORT is a separate, default-off gate:

* Off (default): PROMPT_COMMAND subprocess inherits the normal process
environment only; no extra session dump.
* On: export current-session PG* / PSQL_* (database, user, host, port,
PSQL_TXN, PSQL_ROW_COUNT, PSQL_SHELL_EXIT, PSQL_SUPERUSER, optional
PSQL_TXID) so external renderers need not re-query.

Keeping export opt-in means “I only want %D from a fixed command” never
leaks connection metadata into the child env. Reviewers who want a
narrower first commit can take PROMPT_COMMAND + %D + SHELL_EXIT alone and
leave PROMPT_SESSION_EXPORT for a follow-up.

========================================================================
Looking for a reviewer to walk this path
========================================================================

Please walk the psql + readline path (not a general “please review”):

1. src/bin/psql/input.c
— run_prompt_command() immediately before readline() / gets_fromFile
2. src/bin/psql/prompt.c + prompt.h
— run_prompt_command(), export_prompt_environment(),
prompt_session_export_enabled(), %D in get_prompt(),
reset_prompt_status_after_connect()
3. src/bin/psql/mainloop.c
— gets_interactive() call sites (when prompts are drawn)
4. src/bin/psql/common.c
— SetLastExitVariable() / SHELL_EXIT; confirm PROMPT_COMMAND does not
call it
5. src/bin/psql/command.c
— reset_prompt_status_after_connect() after successful \connect
6. src/bin/psql/help.c + startup.c
— --help=variables / initial SHELL_EXIT
7. src/bin/psql/t/040_prompt_command.pl
— TAP coverage (below)

Optional companion doc (not installed):
src/bin/psql/powerline-integration.md

========================================================================
Testing (v3: TAP added)
========================================================================

src/bin/psql/t/040_prompt_command.pl (registered in meson.build):

Non-interactive:
* SHELL_EXIT after successful SQL, failed SQL, \! true, \! false
* \connect clears ROW_COUNT and SHELL_EXIT
* --help=variables lists PROMPT_COMMAND, PROMPT_SESSION_EXPORT,
SHELL_EXIT

Interactive (IO::Pty + readline, same pattern as t/010 / t/030):
* PROMPT_COMMAND + %D in PROMPT1
* first line only of multi-line PROMPT_COMMAND stdout
* %D empty when PROMPT_COMMAND unset
* PROMPT_SESSION_EXPORT off → PSQL_TXN / PSQL_SUPERUSER not exported
* PROMPT_SESSION_EXPORT on → PSQL_TXN / PSQL_SUPERUSER exported
* PROMPT_COMMAND does not overwrite SHELL_EXIT after a failed SQL

Manual still useful for visual / powerline end-to-end; automation no longer
depends on it for the core knobs.

========================================================================
v3 patch notes
========================================================================

Relative to the original submission:

* Added src/bin/psql/t/040_prompt_command.pl
* Listed that test in src/bin/psql/meson.build
* No intentional functional change to the feature code

Happy to rename variables, drop PROMPT_SESSION_EXPORT from v1, or further
split if that helps commit.

Companion (not part of this patch):
https://github.com/powerline/powerline/pull/2287

Fork branch:
https://github.com/bithead2k/postgres/tree/psql-prompt-command

Comments welcome.

Regards,
Kirk Roybal

On Tue, Aug 4, 2026 at 10:49 AM Kirk Roybal <kirk(dot)roybal(at)gmail(dot)com> wrote:

> Hi,
>
> v2 follow-up: one-page merge rationale, explicit review ask, and a TAP test
> (as offered in the original post). Patch attached.
>
> ========================================================================
> Why merge this (one page)
> ========================================================================
>
> Problem
> -------
> Interactive psql prompts are static between PROMPT1/PROMPT2 redefinitions.
> Users who want connection context, transaction state, or last-command
> status
> in the prompt today either fork psql or abuse %`shell` on every redraw.
> There
> is no bash-shaped hook to regenerate the prompt once per readline.
>
> Bash-shaped solution
> --------------------
> Mirror bash, not invent a plugin API:
>
> * PROMPT_COMMAND — if set, run a shell command before each interactive
> prompt; capture the first line of stdout (existing prompt buffer
> limit).
> * %D — PROMPT1/PROMPT2 escape for that captured line (renderer output).
> * SHELL_EXIT — last *user* command status (SQL or \!); PROMPT_COMMAND
> does
> not overwrite it, so themes can show the prior command’s outcome.
> * Prompt refresh — run_prompt_command() immediately before readline() in
> input.c (not rl_pre_input_hook, which left the prompt blank until a
> key).
> * \connect housekeeping — reset ROW_COUNT / SHELL_EXIT after successful
> \connect; optional :txid refresh when that variable already exists.
>
> Zero behavior change unless configured
> --------------------------------------
> Defaults are inert:
>
> | Knob | Default | Effect when unset/off |
>
> |-------------------------|---------|------------------------------------|
> | PROMPT_COMMAND | unset | no shell, no popen |
> | %D | unused | empty substitution if referenced |
> | PROMPT_SESSION_EXPORT | off | no PG*/PSQL_* export into subprocess |
> | SHELL_EXIT | "0" | new variable only; scripts ignore it |
>
> Non-interactive use, existing scripts, and default interactive sessions are
> unchanged. No .psqlrc change ⇒ no new behavior.
>
> Security / gating story (PROMPT_SESSION_EXPORT + shell)
> -------------------------------------------------------
> PROMPT_COMMAND uses popen() — the same trust model as existing %`command`
> prompt escapes and \!. It only runs when the user (or their .psqlrc)
> explicitly sets PROMPT_COMMAND.
>
> PROMPT_SESSION_EXPORT is a separate, default-off gate:
>
> * Off (default): PROMPT_COMMAND subprocess inherits the normal process
> environment only; no extra session dump.
> * On: export current-session PG* / PSQL_* (database, user, host, port,
> PSQL_TXN, PSQL_ROW_COUNT, PSQL_SHELL_EXIT, PSQL_SUPERUSER, optional
> PSQL_TXID) so external renderers need not re-query.
>
> Keeping export opt-in means “I only want %D from a fixed command” never
> leaks connection metadata into the child env. Reviewers who want a
> narrower first commit can take PROMPT_COMMAND + %D + SHELL_EXIT alone and
> leave PROMPT_SESSION_EXPORT for a follow-up.
>
> ========================================================================
> Looking for a reviewer to walk this path
> ========================================================================
>
> Please walk the psql + readline path (not a general “please review”):
>
> 1. src/bin/psql/input.c
> — run_prompt_command() immediately before readline() / gets_fromFile
> 2. src/bin/psql/prompt.c + prompt.h
> — run_prompt_command(), export_prompt_environment(),
> prompt_session_export_enabled(), %D in get_prompt(),
> reset_prompt_status_after_connect()
> 3. src/bin/psql/mainloop.c
> — gets_interactive() call sites (when prompts are drawn)
> 4. src/bin/psql/common.c
> — SetLastExitVariable() / SHELL_EXIT; confirm PROMPT_COMMAND does not
> call it
> 5. src/bin/psql/command.c
> — reset_prompt_status_after_connect() after successful \connect
> 6. src/bin/psql/help.c + startup.c
> — --help=variables / initial SHELL_EXIT
> 7. src/bin/psql/t/040_prompt_command.pl
> — TAP coverage (below)
>
> Optional companion doc (not installed):
> src/bin/psql/powerline-integration.md
>
> ========================================================================
> Testing (v2: TAP added)
> ========================================================================
>
> src/bin/psql/t/040_prompt_command.pl (registered in meson.build):
>
> Non-interactive:
> * SHELL_EXIT after successful SQL, failed SQL, \! true, \! false
> * \connect clears ROW_COUNT and SHELL_EXIT
> * --help=variables lists PROMPT_COMMAND, PROMPT_SESSION_EXPORT,
> SHELL_EXIT
>
> Interactive (IO::Pty + readline, same pattern as t/010 / t/030):
> * PROMPT_COMMAND + %D in PROMPT1
> * first line only of multi-line PROMPT_COMMAND stdout
> * %D empty when PROMPT_COMMAND unset
> * PROMPT_SESSION_EXPORT off → PSQL_TXN / PSQL_SUPERUSER not exported
> * PROMPT_SESSION_EXPORT on → PSQL_TXN / PSQL_SUPERUSER exported
> * PROMPT_COMMAND does not overwrite SHELL_EXIT after a failed SQL
>
> Manual still useful for visual / powerline end-to-end; automation no longer
> depends on it for the core knobs.
>
> ========================================================================
> v2 patch notes
> ========================================================================
>
> Relative to the original submission:
>
> * Added src/bin/psql/t/040_prompt_command.pl
> * Listed that test in src/bin/psql/meson.build
> * No intentional functional change to the feature code
>
> Happy to rename variables, drop PROMPT_SESSION_EXPORT from v1, or further
> split if that helps commit.
>
> Companion (not part of this patch):
> https://github.com/powerline/powerline/pull/2287
>
> Fork branch:
> https://github.com/bithead2k/postgres/tree/psql-prompt-command
>
> Comments welcome.
>
> Regards,
> Kirk Roybal
>
>

Attachment Content-Type Size
v3-0001-Add-PROMPT_COMMAND-and-dynamic-prompt-support-to-.patch text/x-patch 23.5 KB

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Chao Li 2026-09-12 02:57:01 Re: REPACK (CONCURRENTLY) decoding worker is canceled by lock_timeout
Previous Message Henson Choi 2026-09-12 02:49:48 Re: Row pattern recognition