Skip to content

Fix special commands (\q, \d, exit, ...) being broken in explain mode - #1620

Open
DiegoDAF wants to merge 1 commit into
dbcli:mainfrom
DiegoDAF:upstream/explain-mode-special-commands
Open

Fix special commands (\q, \d, exit, ...) being broken in explain mode#1620
DiegoDAF wants to merge 1 commit into
dbcli:mainfrom
DiegoDAF:upstream/explain-mode-special-commands

Conversation

@DiegoDAF

@DiegoDAF DiegoDAF commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Description

With explain mode (F5) turned on, no special command works, and there is no way
out of the session short of killing it.

PGExecute.run() applies the explain prefix in an if/elif with the
pgspecial branch:

if explain_mode:
    sql = self.explain_prefix() + sql
elif pgspecial:
    ...detect and run special commands...

Because it is an elif, the pgspecial branch is never reached while
explain_mode is true, so every input is prefixed and shipped to the server as
SQL:

u: postgres db: postgres > \q
syntax error at or near "\"
LINE 1: EXPLAIN (ANALYZE, COSTS, VERBOSE, BUFFERS, FORMAT JSON) \q
                                                                ^
u: postgres db: postgres > exit
syntax error at or near "exit"
LINE 1: EXPLAIN (ANALYZE, COSTS, VERBOSE, BUFFERS, FORMAT JSON) exit
                                                                ^

\q, exit/quit, \d, \i, named queries and the rest all fail the same
way. F5 does not help either, since toggling it back is a key binding but the
session is already stuck for anything typed.

This has been there since the explain visualizer was added in #1279.

Change

Special commands are not SQL, so detect them first regardless of explain mode,
and apply the EXPLAIN prefix further down, to statements that fall through as
normal SQL.

Two side effects worth mentioning:

  • select ... \G now works in explain mode too. The \G stripping lives in
    the pgspecial branch, so previously the literal \G was sent to the server.
  • Anything else that lives in that branch (for example the protocol-error
    reconnect path) is reachable in explain mode again.

Validation

Four new tests in tests/test_pgexecute.py: a special command is dispatched
and never reaches execute_normal_sql, a describe command runs as special,
normal SQL is still prefixed with explain_prefix(), and \G is stripped.
Three of the four fail on current main.

Full suite green locally (2734 passed).

Checklist

  • I've added this contribution to the changelog.rst.

Not a feature from my list in discussion #1603: this is one of the upstream bugs I ran into while maintaining the fork, listed in the status section at the bottom of that discussion.

In PGExecute.run() the explain prefix was applied in an if/elif with the
pgspecial branch:

    if explain_mode:
        sql = self.explain_prefix() + sql
    elif pgspecial:
        ...detect and run special commands...

So with explain mode (F5) on, the pgspecial branch was never reached and every
input was prefixed with EXPLAIN (...) and sent to the server as SQL. \q, \d,
\i, named queries and the bare words exit/quit all came back with
`syntax error at or near "\"`, leaving no way to turn explain mode off or to
quit the session.

Special commands are not SQL, so they are now detected first, regardless of
explain mode, and the EXPLAIN prefix is applied further down to statements that
fall through as normal SQL. This also fixes `select ... \G` in explain mode:
the \G is stripped by the pgspecial branch, which previously never ran.

Adds four tests: a special command is dispatched (and never sent to the
server), a describe command runs as special, normal SQL is still prefixed, and
\G is stripped.
DiegoDAF added a commit to DiegoDAF/pgcli.daf that referenced this pull request Aug 18, 2026
@DiegoDAF

Copy link
Copy Markdown
Contributor Author

The red CI here is not this change, and it is a nice demonstration of why
#1619 exists.

On this same commit:

  • build (3.10): unit 2734 passed, behave 22 scenarios passed, 0 failed.
  • build (3.14): unit 2734 passed, behave 21 passed, 1 error ->
    tests/features/iocommands.feature:3 edit sql in file with external editor.

Same code, one Python version errors and another does not, and the errored
scenario is the external-editor one whose expect_exact timeout is 2 seconds.
Once 3.14 errored, fail-fast cancelled 3.10/3.11/3.12/3.13, which is why the
checks list looks like five failures instead of one.

#1619 raises those timeouts and should make this stop happening across the
board. If it lands first I will rebase this branch on top of it so the run is
clean.

(The codex-review failure appears on every PR in the repo at the moment,
including ones that have already been merged, so I assume it can be ignored.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant