Skip to content

Releases

pgcli.daf 4.7.4

Choose a tag to compare

@DiegoDAF DiegoDAF released this 30 Oct 16:59

Everything since v4.7.3, the last published release. Still tracks upstream 4.7.1: upstream has published nothing since; its two new commits, \cd/\ls (dbcli#1613) and the uv venv docs fix (dbcli#1650), are merged here.

Highlights

  • Number columns are right-aligned, as in psql and pgAdmin.
  • Windows: the password file is read the way libpq does, Include paths in ~/.ssh/config work, and CI now runs the unit tests on Windows.
  • Text from SQL_ASCII databases is decoded (UTF-8, then the new sql_ascii_fallback_encoding).
  • -l shows each database's size; new \cd and \ls commands.
  • Passwords are kept out of the debug log, --list-dsn, error lines and the process title.
  • SSH tunnel: \c to another server goes through that server's own tunnel, and a reconnect revives a dead tunnel.
  • A multi-line \copy (query) to 'file' works again.

Behaviour changes

  • pgcli_dump/pgcli_dumpall: --dsn ALIAS is now the connection (as in pgcli --dsn), and -v goes to pg_dump; the tunnel's debug output moved to --tunnel-verbose.

Features

  • Add \cd and \ls filesystem commands, with path completion for filesystem-aware commands including \e, \i, \log-file, and \o (upstream dbcli#1613).
  • -l/--list shows each database's size next to its access privileges, as \l+ does: pg_size_pretty(pg_database_size()) for the databases the user may connect to, No Access for the others.

Bug fixes

  • On Windows the password file is read: by default at %APPDATA%\postgresql\pgpass.conf, as libpq does, and without the 0600 check, which libpq does not make there (Windows has no such mode bits). The SSH tunnel and the dump wrappers ignored every Windows user's file.
  • Include in ~/.ssh/config keeps a backslash that escapes nothing, as OpenSSH does: shlex dropped them all, so a Windows path such as C:\Users\me\.ssh\conf.d\* never matched.
  • The history and log paths migrated to the state directory are normalised (...\state/history on Windows).
  • Fix the development setup instructions to activate the .venv directory created by uv venv (upstream dbcli#1650).
  • A multi-line \copy (query) to 'file' works again. The 20-oct rule that ends a backslash command at its newline, psql's behaviour, was cutting \copy too, so the statement reached pgspecial as a bare \copy ( and failed with "Missing keyword in \copy command. Either TO or FROM is required"; an export script run with -f produced no file. \copy now ends at the first newline where its parentheses are closed (quotes, comments and dollar quotes skipped), so a multi-line query stays whole and a psql-style one-line \copy without a semicolon still leaves the next line to run as its own statement.
  • The float format of [data_formats] applies at last: the formatter was left to infer the column types from the values, and cli_helpers files float and Decimal alike under Decimal, which its number formatting does not treat as a number. The types now come from the cursor description (float4, float8 and numeric as float, int2 to int8 as int), in the table and in the auto-vertical fallback.
  • Number columns are right-aligned, as psql and pgAdmin show them: int2, int4, int8, float4, float8, numeric, oid, xid, xid8, cid and money, the types psql right-aligns. This replaces the decimal-point alignment, which once fed the real column types padded float and numeric values with spaces in csv and tsv output and crashed -t with "list index out of range".
  • Text from a SQL_ASCII connection no longer comes out as hex (0x63616fe9) or as [b'\xe9']: the server validates nothing there, so psycopg returns raw bytes, which pgcli now decodes as UTF-8 and, when that fails, with the new sql_ascii_fallback_encoding config option (latin-1 by default).
  • --auto-vertical-output, \x auto and auto_expand in the config work in -c/-f mode. The terminal width was read from the prompt, which those modes never build, so every statement failed with 'NoneType' object has no attribute 'output'. Without a prompt the terminal is asked directly, and only when the output is one: a pipe or a file gets the plain table, as with psql's \x auto.
  • \c db user otherhost to a server whose SSH tunnel cannot be opened keeps the current session, and so does the automatic reconnect when the bastion is down. The tunnel code exited the process on any failure, which was fine at startup and fatal once \c and the reconnect opened tunnels too. The dead tunnel stays registered after a failed reconnect, so the next one tries the bastion again.
  • -f and -c no longer drop a leading LOAD, REASSIGN OWNED or ANALYSE statement as "text before the statement". The detector that strips prose pasted from a chat missed those three keywords of the grammar, so a script that opened with load 'auto_explain'; lost that statement and exited 0.
  • Every fenced code block of a pasted markdown answer runs, not only the first one, and the report counts the blocks and mentions text outside them.
  • A multi-line \ns in a -c string or an -f script saves the whole query. The rule that ends a backslash command at its newline exempts \ns at the prompt, but the -c/-f path kept its own copy of the rule without the exemption, so \ns q select 1,\n2; saved select 1, and ran 2 as SQL. One shared function serves both paths now.
  • An unquoted value with commas in dsn.d, namedqueries.d, [alias_dsn] or [named queries] is taken as written. ConfigObj split it into a list, so a multi-host DSN (host=a,b port=5432 ...) crashed pgcli --dsn with a traceback and made --list-dsn give up on every alias, and act = select pid, query from pg_stat_activity failed on \n act with "Bad arguments".
  • --list-dsn hides the password of an alias in every spelling libpq accepts, sslpassword included. It masked ://user:pass@ only; a key=value alias with password= or a URL with ?password= was printed with the secret.
  • A custom log_file name is honoured with log_rotation_mode = none. Only its directory was used, so log_file = ~/x/mylog.txt wrote ~/x/pgcli.log. The pgclirc comment, which described a /var/log/pgcli/pgcli.log attempt, a daily rotation keeping 30 days and pgcli.YYYY-MM-DD.log names that never existed, now describes the real behaviour.
  • \v off prints "Verbose errors off." instead of "off.", and \c "my db" strips the quotes before asking for the database.
  • The debug log no longer records passwords: the "Launch Params" line masked nothing of a connection string, the raw statement was logged before the PASSWORD '...' masking ran, and pgcli_dump --tunnel-verbose logged the child command line with -d postgresql://user:pass@... intact. The process title shown by ps also hides a password passed as ?password= in a URL's query string.
  • A .pg_service.conf whose leading comment has non-ASCII characters parses again. The comment skipper advanced the text position by character count, so # Configuración put the parser down in the middle of the comment.
  • The auto-vertical fallback gets one column type per column: a numeric column added two, shifting the type of every column after it, so the configured integer format skipped the integers that followed a numeric.
  • The EXPLAIN visualizer describes CTE Scan (the table spelled it CTEScan), Gather, Gather Merge, Memoize, Incremental Sort, WindowAgg, Unique, Materialize and the other common nodes instead of printing "Not found", and a plan without actual rows (EXPLAIN (ANALYZE false, FORMAT JSON) with F5 on) no longer fails with KeyError: 'Rows Removed by Filter'.
  • --log-file and \log-file create their file readable by its owner only, like the history, the debug log and the config; an existing file the user owns is tightened the same way.
  • An invalid regular expression in [ssh tunnels] or [dsn ssh tunnels] is reported and skipped instead of being an uncaught re.error at every connection.
  • A failure inside the background completion refresh is one line on the terminal, with the traceback in the log, and the extra connection it opened is closed. Python used to dump the whole traceback over the prompt and leave that connection open until exit; the previous completions stay in use either way.
  • pgcli_isready reads the connection the way the dump wrappers do: --dsn ALIAS is the server to check (it only picked the tunnel and then checked localhost:5432), a -d connection string counts for the tunnel decision, and -p abc is one line instead of a traceback.
  • pgcli_dump and pgcli_dumpall read getopt's attached short options and flag clusters (-hHOST, -p5433, -dDB, -ch HOST) for the tunnel decision; only the spaced forms were seen, so pgcli_dump -hdb.example.com mydb dialed the host directly and skipped its tunnel.
  • A multi-host connection string (host=a,b, postgresql://a,b/db) is refused only when an SSH tunnel would have to rewrite it. The URI form was refused before the tunnel decision, so a dump that needed no tunnel died with a message about tunnels, while the key=value form was quietly rewritten to one host with the tunnel pointed at a,b.
  • The EXPLAIN visualizer's exclusive time of a node above a CTE Scan no longer subtracts the scan's time, as pev meant: the port compared the node type against CTEScan, so the exclusion never applied and the parent of a CTE scan showed less time than it spent.
  • The debug log keeps one handler per process. Every PGCli() added a handler to the pgcli logger and never removed the previous one, so a reconfiguration wrote each record twice, and the test suite, which builds hundreds of them, ran out of file descriptors under a 300-descriptor limit.
  • The log no longer gets a role password back through the server's error message. The sql: part of an ERROR line was masked, but the error: part of the same line quoted the server's `LINE 1: alter role x password '...'...
Read more

pgcli.daf 4.7.3

Choose a tag to compare

@DiegoDAF DiegoDAF released this 19 Oct 14:25

Everything since v4.7.2, the last published release. Still tracks upstream 4.7.1: upstream has published nothing since, and its only new commit, the \service prompt token (dbcli#1632), is merged here.

Bug fixes

  • A second Ctrl-C while a query is being cancelled no longer leaves the connection stuck. psycopg answers Ctrl-C by cancelling the query and then reading the server's reply; another Ctrl-C during that cleanup, easy over an SSH tunnel where it takes a round trip or two, cut it short. The cancel could be lost, the query kept running on the server and every later statement failed with another command is already in progress. After a cancelled query pgcli now checks the connection: if it is still busy it cancels again and reads the reply, and if the server does not answer within 5 seconds, or yet another Ctrl-C arrives, it cancels (ignoring Ctrl-C for the at most 2 seconds that takes) and closes the connection, so the next statement reconnects. Sent upstream as dbcli#1651; the psycopg side is psycopg/psycopg#1444.

  • -c and -f exit with psql's status codes. The status is that of the last -c or -f that ran: a failed -c exits 1, and with on_error = STOP (psql's ON_ERROR_STOP) the first failure stops the rest and a failed script exits 3 instead of 1. With RESUME a failed last -c exits 1 where it used to exit 0. Measured against psql 18. Sent upstream as dbcli#1652.

  • -c together with -f runs both. Only the -c ran and the file was skipped without a word, a leftover of this fork's older run_cli from before upstream made both run.

  • -t/--tuples-only no longer hides SQL errors. The error of a failed statement travels where the status footer does, and -t drops that footer, so a broken query printed nothing and only the exit code told. Errors now go to stderr under -t, as with psql -t, so stdout still carries only rows. --no-status hid them the same way and now keeps them. Sent upstream as dbcli#1649.

  • pgcli service=name honors every setting in the service. Only host, port, user, dbname and password were passed on, so a service asking for sslmode=require connected without SSL where psql service=name refuses. The rest of the keys (sslmode, sslrootcert, sslcert, sslkey, application_name, connect_timeout, ...) now reach libpq as written. Upstream issue dbcli#1474.

  • pgcli_dump / pgcli_dumpall read the password file the way pgcli and libpq do. Their own reader ignored PGPASSFILE, kept the backslash of an escaped \: (handing pg_dump a wrong password) and took a partial glob such as *.example.com as a pattern, which libpq reads literally. They now use the same reader as pgcli; on a real 451-entry file nothing changed.

  • %r and %p in a ProxyCommand expand to the user and port the tunnel connects with, the tunnel URL first, as ssh does with the command line. paramiko used the Host block's User and Port, and turned a literal %% into a broken token. Tokens in ProxyJump are left alone: OpenSSH rejects them outright.

  • A clear message when the SSH server cannot open the forwarded connection: SSH tunnel: bastion could not open a connection to db.internal:5432: Name or service not known, with a hint to check the host and port. It used to print paramiko's raw Secsh channel 0 open FAILED and then libpq's "server closed the connection unexpectedly", so a typo in a DSN host looked like a server crash.

  • A -c string or a script that starts with a blank line no longer reports 1 line(s) of text before the statement removed: only lines that carried something are counted.

Features

  • The SSH tunnel also reads /etc/ssh/ssh_config and what it includes (ssh_config.d/), after ~/.ssh/config, the way ssh does: the first value found wins, so a ProxyJump, User or Port set system-wide applies when the user's config does not set it. A system file paramiko cannot parse is skipped with a warning.

  • The startup banner shows whether the connection is encrypted, with the effective sslmode (SSL: on (sslmode=verify-ca)), and a warning appears when sslmode=prefer ends up unencrypted against a remote server, also with -c and -f. libpq verifies against ~/.postgresql/root.crt whenever it exists, so a root.crt without the server's CA silently turns every prefer connection into a plaintext one.

  • A \service prompt token for connections made with service=... or PGSERVICE, e.g. prompt = '\service> '. From upstream dbcli#1632.

Internal

  • cli_helpers >= 2.15.2. That release aligns expanded output by display width, so a header of wide characters lines up; a test had the old misalignment written down. Sent upstream as dbcli#1653.
  • Merged upstream through 3c3776cf (dbcli#1625 and dbcli#1632).

Install

uv tool install --force --reinstall "pgcli-4.7.3-py3-none-any.whl[sshtunnel,keyring]"

Both extras are needed: sshtunnel for the SSH tunnel, keyring for saving passwords.

pgcli.daf 4.7.2

Choose a tag to compare

@DiegoDAF DiegoDAF released this 07 Oct 19:47

Everything since v4.6.2, the last published release. Tracks upstream 4.7.1: that release line is where this fork's -c, -f, -y and -t reached released pgcli (4.7.0 shipped them, 4.7.1 fixed the version string), and everything else in 4.7.1 was already here.

Bug fixes

  • \ef and \ev no longer crash pgcli inside an aborted transaction. The server's "current transaction is aborted" answer is raised by psycopg as InFailedSqlTransaction, which is not a ProgrammingError, so it escaped the REPL with a traceback and the session was lost. The message is now printed in red like any other error and the transaction stays open for ROLLBACK, as in psql. Upstream issue dbcli#1392.

  • pgcli_dump / pgcli_dumpall apply libpq's rule to .pgpass. A file readable by group or others is refused with the same warning libpq prints. The wrappers carried a second reader that ignored the mode, so a backup could authenticate with a password psql would have ignored. The permission check now lives in one place (pgpass.has_safe_permissions) and both use it; matching is untouched.

  • pgcli_dump / pgcli_dumpall route a URI connection string through the SSH tunnel. Given -d postgresql://[email protected]:5432/mydb they read neither host nor port, so the tunnel opened to localhost and the URI, which libpq lets win over -h/-p, sent pg_dump straight to the real host. The URI is now parsed and rewritten onto the tunnel (userinfo, query and percent-encoding kept, IPv6 literals bracketed); hostaddr= in a keyword/value string is rewritten too. A string that cannot be rewritten honestly (multi-host URI, host= in the query) is reported instead of half-rewritten.

  • pgcli_dump / pgcli_dumpall read the user and database out of a connection string when looking up .pgpass. Only the bare -d mydb form was understood, so a tunnelled dump given a URI asked .pgpass about the wrong user and about a database named after the whole URI.

  • pgcli_dump / pgcli_dumpall no longer traceback on a non-numeric port in -p, --port= or PGPORT; they say which one is wrong. An empty PGPORT falls back to 5432.

  • # is the bitwise XOR operator, not a comment marker. select 17 # 5 returned 17 instead of 20 because sqlparse.format(strip_comments=True) follows MySQL and drops everything after a #. Trailing comments are now removed with PostgreSQL's own rules (--, nestable /* */, markers inside strings, quoted identifiers and dollar-quoted bodies left alone). An unterminated /* is kept so the server reports it. Reported upstream as dbcli#1646; the sqlparse limitation is andialbrecht/sqlparse#539.

  • SSH tunnel honors ProxyJump from ~/.ssh/config. paramiko returns the directive verbatim instead of turning it into a ProxyCommand, so a host reachable only through a jump host was dialed directly and failed with Name or service not known. The tunnel now builds the exact ssh -W ProxyCommand OpenSSH derives: user@host:port, multi-hop chains, IPv6 literals, trailing comments, none, and ssh's rule that the first ProxyCommand or ProxyJump directive read wins. The DNS error names the host and what was looked at.

  • History, log and config files are created readable by their owner only (0600), like psql's history and .pgpass: the history records every statement typed, alter role ... password included. An existing history or log with wider permissions is tightened on the next run; device nodes, FIFOs and files owned by someone else are left alone.

Features

  • SQL pasted out of a markdown document runs. A block copied from a chat or an LLM answer carries its code fence and sometimes the prose above it, so the server replied with a syntax error at or near three backticks. The fence is removed and the statement runs; text inside a fence wins, otherwise leading lines are dropped up to the first one that starts a statement. What was dropped is reported, so running something other than what was pasted is never silent.

  • EXPLAIN diagnostics. With explain_summary on, the plan is read for the problems its own numbers state: a hash or sort that spilled to disk, a lossy bitmap, a filter discarding far more rows than it keeps, an index-only scan still fetching from the heap, an inner side re-executed thousands of times, parallel workers asked for but not launched. Each one tags its node and is explained below it, grouped by what you would change: work_mem, index, vacuum, parallel or plan. Thresholds are deliberately conservative; every rule was checked against a live server.

  • Explain mode (F5) accepts a statement that already is an EXPLAIN. pgcli prepended its own EXPLAIN (...) unconditionally, so explain (analyze, buffers, wal, memory, serialize) select ... became EXPLAIN (...) explain (...) and failed. The user's options are kept and only what the visualizer needs is added (ANALYZE, COSTS, FORMAT JSON). A plan requested as text, yaml or xml is shown verbatim, and the visualizer no longer raises on a plan without costs or timings.

  • The EXPLAIN summary reports the statistics explain.depesz.com shows under Stats: total I/O with temporary traffic called out, time grouped by node type with counts, and time by table broken down by the scan that read it.

  • F5 cycles through three states: off, the plan, and the plan with the analysis summary. The toolbar says which one is active. Turning explain mode off restores whatever explain_summary says.

  • Completion restores the output column for functions whose arguments have no names, such as labels(variadic text[]) returns hstore: the function name is used as the column name, which is what pgcli already did for functions without output parameters.

Internal

  • Prompt-side modules pgbuffer, pgtoolbar, key_bindings and prompt_utils went from 48-66% to 100% coverage; completion_refresher and pgstyle from 64% and 48% to 100%. Among the branches never exercised before: every function key except F5 and F9, the refresh restart handshake, and -y skipping the destructive-query confirmation in a script.
  • The pg_dump and pg_dumpall wrappers no longer carry a byte-identical copy of parse_connection_args and build_tunneled_args; 167 duplicated lines moved to pgcli/dump_args.py and one test module runs every case against both wrappers.
  • tests/test_isready.py no longer depends on the developer's PGHOST and PGPORT.
  • The fork's behave suite is green again after the -c/-f exit-code change; the -t special-command scenario passed \\dt with two backslashes and tested nothing.
  • The pgcli_dump/pgcli_dumpall behave steps capture output as bytes, since pg_dump -F c writes a binary dump.
  • CI: codeql-action v4 and actions/checkout v5 (v2/v3 are deprecated), read-only token, Python 3.14 in the matrix, and apt-get update before installing pgbouncer (a stale runner image made the install 404 and cancelled every job on 2026-10-07).
  • Require sqlparse >= 0.6.0: every 0.5.x release carries the DoS and escaping advisories fixed in 0.6.0. click is capped below 9, as upstream does.
  • Test hygiene: @dbtest on a test that errored without a server; a parametrize fed a list instead of an itertools.product iterator (pytest 9 rejects it); click.get_text_stream replaced by sys.stdin (Click 9 removes it).
  • SSH tunnel follows Include directives in ~/.ssh/config, which paramiko's parser ignores. Seventeen configurations compared against ssh -G, all match.
  • Merged upstream 4.7.1. The only code upstream added that was not already here is one @dbtest marker.

Upstream

Open pull requests from this fork: dbcli#1628 (SQL_ASCII decoding), dbcli#1637 (--no-timings/--no-status), dbcli#1640 (sqlparse >= 0.6.0), dbcli#1645 (0600 state files), dbcli#1647 (# is XOR).

Install

uv tool install --force --reinstall "pgcli-4.7.2-py3-none-any.whl[sshtunnel,keyring]"

Both extras are required: without keyring, saving database and SSH secrets does not work. --reinstall matters too: --force alone keeps dependencies that still satisfy the old range.

Tested on Python 3.12 with PostgreSQL 18: 3600 tests green, 8 skipped, 1 xfailed.

pgcli.daf 4.6.2

Choose a tag to compare

@DiegoDAF DiegoDAF released this 16 Sep 13:47

Everything since v4.5.8, the last published release. This includes the 4.6.1 section, which was built locally on 2026-08-27 but never tagged, so its changes ship here for the first time.

Bug fixes

  • copy ... to stdout / copy ... from stdin no longer kill the session. psycopg refuses those in cursor.execute(), but only after the server has entered the COPY state, so the connection stayed busy and every later statement failed with another command is already in progress until you reconnected. The COPY is now closed before the error surfaces, and the message points at \copy.

  • .pg_service.conf values are read literally, the way libpq does. They were parsed with ConfigObj, which strips an inline # ... as a comment, removes surrounding quotes and turns a,b into a list, so a password like abc#def silently arrived as abc. Verified against a live server on ten cases; pgcli now matches libpq on all of them. It also warns when a value contains " #", because pg_service.conf has no end-of-line comments (unlike postgresql.conf) and libpq's own error for port=5432 # prod never mentions comments.

  • -f/-c no longer repeat the whole block when it ends in \watch. The watch regex captures everything before it, so a multi-statement file re-ran every statement. Files and commands now run one statement at a time, like psql: \watch repeats only its own statement, and a bare \watch picks up the one before it. A backslash command also spans only its own line, so a metacommand followed by SQL no longer swallows the SQL. A failed statement now makes -f/-c exit non-zero when on_error is STOP, like psql with ON_ERROR_STOP.

  • Fixed a prompt crash, garbled timezone output and a completion-refresh crash under SQL_ASCII, where psycopg returns text columns as raw bytes. The socket directory, the timezone and the function metadata rows are now decoded defensively.

  • -l/--list and --ping keep a plain database name instead of replacing it with postgres. psql connects to the named database and lists from there, so only the case where no database is given at all falls back to postgres.

Internal

  • Recorded upstream 4.6.0 and everything through 924e7d4 as merged. Every item was already present here; two of 4.6.0's items originated in this fork (\ne and .pgpass over SSH tunnels).
  • connect_timeout is read with as_int() at load time, so a non-numeric value is reported instead of silently ignored, and the precedence rules moved into a unit-tested get_connect_timeout() helper.
  • Dropped a stale xfail that was hiding a passing test, and documented the one that is still needed.
  • Exposed Changelog, Repository and Issues project URLs in the package metadata.

Upstream

Eleven of this fork's pull requests were merged into dbcli/pgcli during this cycle: -c, -f, -y, -t, .pgpass over SSH tunnels, trailing comments, \ne, the behave de-flake, the explain-mode fix, -l keeping the connection string, and --timeout.

Install

uv tool install --force --reinstall "pgcli-4.6.2-py3-none-any.whl[sshtunnel,keyring]"

Both extras are required: without keyring, saving database and SSH secrets does not work.

Tested on Python 3.12 with PostgreSQL 18: 3207 tests green.

pgcli.daf 4.5.8

Choose a tag to compare

@DiegoDAF DiegoDAF released this 19 Aug 16:30

pgcli.daf 4.5.8 (tracks upstream 4.5.0). All changes since v4.5.7.

Features

  • Connection timeout: new --timeout option and connect_timeout config value (default 30s). pgcli used to inherit libpq's default of 0, so an unreachable host hung until the OS gave up on the TCP connection. Precedence: --timeout > connection string > $PGCONNECT_TIMEOUT > config.
  • Server-versioned named queries (namedqueries.d, psqlrc style): activity-17.conf is only offered when the connected server is 17+, best-fit among variants, suffix-less file as fallback. Queries the server cannot support disappear from \n. The version comes from the connection's startup packet, no extra round-trip.
  • Multiline named queries are fully supported (ConfigObj triple quotes), and \ns/\ne convert -- x line comments to /* x */ at save time, so a query survives being flattened to one line.
  • log_truncate_on_rotation (default off), like the PostgreSQL setting: with day-of-week/day-of-month rotation, a slot's file is truncated when it comes around again instead of appending forever.
  • --on-error [STOP|RESUME] to override the on_error config per invocation.

Bug fixes

  • Security: unblocked sqlparse 0.6.x. The <0.6 cap prevented installing the release that fixes CVE-2026-59893 (quadratic regex on dollar-quoted literals), CVE-2026-71491, CVE-2026-54284 and CVE-2026-59894. Now >=0.5.0,<0.7, verified against 0.6.0.
  • Unconditional UPDATE detection now uses the parse tree instead of splitting on whitespace: update t set a = 'x where y' no longer skips the destructive-statement warning (upstream dbcli#1614, cherry-picked).
  • Explain mode (F5) no longer breaks special commands. Everything typed was prefixed with EXPLAIN (...), so \q, \d, exit, named queries and \G failed and you could not even quit.
  • -l/--list and --ping no longer discard the connection string (URI or key=value), which fell back to a local socket connection as the OS user.
  • CLI flags override a key=value conninfo, completing the 4.5.7 fix that covered --dsn aliases and URIs.
  • -o/--output writes only result rows with --tuples-only or in -c/-f mode, so generated .sql scripts come out clean. Interactive \o keeps the transcript.
  • Unrecognized backslash commands fail client-side with a clear message instead of being sent to the server as SQL.
  • Cursor shape restored for Emacs mode, columns suggested after GROUP BY and after a column named type, and bytes identifiers decoded in completion metadata (upstream dbcli#1599, dbcli#1610, dbcli#1611, dbcli#1612, cherry-picked).

Internal

  • behave editor scenarios de-flaked (pexpect timeouts raised to 10s).

Install: uv tool install --force "pgcli-4.5.8-py3-none-any.whl[sshtunnel,keyring]"

Upstream PRs open from this work: dbcli#1619, dbcli#1620, dbcli#1621, dbcli#1622 (plus dbcli#1542, dbcli#1543, dbcli#1544, dbcli#1545). See discussion #1603.

pgcli.daf 4.5.7

Choose a tag to compare

@DiegoDAF DiegoDAF released this 21 Jul 14:08

pgcli.daf 4.5.7 (tracks upstream 4.5.0). All changes since v4.5.2.

Features

  • Query-tool bundle (pgAdmin-inspired): \autocommit [on|off] special + autocommit config (default True), manual-transaction mode with an Autocommit: OFF toolbar flag; \hist [N] lists the session's SQL with elapsed time + OK/ERR; F9 runs only the selected text (run-selection). Ctrl-C already cancels server-side (psycopg3), so no extra wiring.
  • EXPLAIN analysis summary (F5): after the plan tree, a compact summary of slowest nodes by exclusive time, time by relation, and worst planner estimate misses. Config explain_summary (default False).
  • psql-style paste (paste_mode config + F6): each statement echoed with its result interleaved. Off by default.
  • Log/history under $XDG_STATE_HOME (~/.local/state/pgcli/) with automatic migration from ~/.config (issue dbcli#1497).
  • Honor $PSQL_EDITOR for \e/\ev/\ef/\ne, matching psql precedence (issue dbcli#1398).
  • .pgpass for SSH tunnels: resolve the password using the ORIGINAL host:port before the tunnel port rewrite (libpq missed explicit-port entries).

Bug fixes

  • Explain mode (F5) no longer breaks special/meta commands. Previously every input got the EXPLAIN (...) prefix — including \q, \d, exit, named queries, \G — so you couldn't even quit while F5 was on (worse after an idle-timeout reconnect). Special commands are now detected first; the prefix hits only real SQL. Also repairs \G under explain mode and keeps the restrict-mode guard authoritative. (Upstream bug.)
  • CLI flags override a --dsn/URI, matching psql. -d/-U/-h/-p were silently ignored for --dsn/URI connections; e.g. pgcli --dsn prod -d otherdb still hit the alias's db. Only genuinely explicit flags override; the --port default and env vars don't clobber the DSN.
  • Security: obfuscate_process_password leaked all-but-last password when the process title held multiple connection URLs; now masks every URL's password.

Internal

  • confirm_destructive_query takes a force parameter and owns the whole decision (mirrors upstream PR dbcli#1544 feedback).
  • Security-focused test suite (tests/test_sec_*.py).
  • SPDX license = "BSD-3-Clause" (PEP 639); fixed a broken build (removed setuptools-scm).

Install: uv tool install --force "pgcli-4.5.7-py3-none-any.whl[sshtunnel,keyring]"

v4.5.2

Choose a tag to compare

@DiegoDAF DiegoDAF released this 26 Jun 18:37

Features

  • \ne <name> edits a named query in the external editor. Loads the named query's SQL into $EDITOR; on save it is persisted back to the main config's [named queries] section. If the name does not exist the editor opens empty and saving creates it. Editing a query that lives in namedqueries.d saves an overriding copy to the main config. Addresses the editor part of upstream issue #1430.

Install

uv tool install --force "dist/pgcli-4.5.2-py3-none-any.whl[sshtunnel,keyring]"

Full Changelog: v4.5.1...v4.5.2

v4.5.1

Choose a tag to compare

@DiegoDAF DiegoDAF released this 25 Jun 19:24

Version realigned to track upstream: this fork now carries everything from upstream 4.5.0 (all our cherry-picks landed there) plus the features below. Previous fork release was 4.4.8.

Features

  • stream_results config option (default False). When enabled, the result of each statement is printed as soon as it finishes instead of being buffered and printed all together at the end. Pasting several statements (select a; select b; select c;) now shows each result live as its statement completes, similar to psql/pgAdmin. Note: in stream mode the pager is not used to wrap the combined output.
  • ssh_tunnel_save_password config option (default False). When the SSH agent cannot authenticate the tunnel (a passphrase-protected key with no agent, or password auth), pgcli now prompts for the key passphrase / SSH password and can store it in the OS keyring (reusing the existing keyring integration) so it is reused on the next connect, similar to pgAdmin. When True the secret is saved automatically after a successful connect; when False pgcli asks before saving. Requires keyring = True.

Install

uv tool install --force "dist/pgcli-4.5.1-py3-none-any.whl[sshtunnel,keyring]"

Both extras are required: sshtunnel (SSH tunnel support) and keyring (secret storage for DB and SSH passwords).

Full Changelog: v4.4.8...v4.5.1

pgcli.daf v4.4.8 (upstream: 4.4.0)

Choose a tag to compare

@DiegoDAF DiegoDAF released this 01 Jun 18:30

pgcli.daf v4.4.8 (upstream: 4.4.0)

Full changelog since the last published release (v4.4.5). Covers v4.4.6, v4.4.7 and v4.4.8.

v4.4.8 (2026-06-01)

Upstream cherry-picks

  • Fix completion crash when tables are created during refresh (upstream dbcli#1591). Guards against a race in the completion refresh thread when a relation is created between extend_relations and extend_columns, which raised a KeyError.
  • Fix row-limit detection for multiline LIMIT clauses (upstream dbcli#1594). _has_limit now parses with sqlparse instead of a substring match, so LIMIT split across lines/tabs is detected and LIMIT inside comments or string literals no longer triggers the row-limit prompt.

Maintenance

  • Drop a now-unused # type: ignore[operator] in pgcompleter.py (keeps mypy 2.x green).
  • Dependency refresh to latest: paramiko 4.0 to 5.0, cli_helpers 2.7 to 2.15, psycopg 3.3.4, click 8.4.1, Pygments 2.20, sqlparse 0.5.5, cryptography 48.0. paramiko 5.0 SSH tunnels validated.

v4.4.7 (2026-05-18)

Upstream cherry-picks

  • Allow force-quit when a transaction is running (upstream dbcli#1573). Pressing Ctrl-D or running \q now offers to exit even with an active transaction, instead of leaving the user stuck.
  • Let sqlparse accept arbitrarily-large queries by disabling MAX_GROUPING_DEPTH and MAX_GROUPING_TOKENS (upstream dbcli#1595). Fixes crashes when parsing very large SQL.

Internal

  • Pass PG* env variables to integration tests via tox.ini passenv (upstream dbcli#1596).

v4.4.6 (2026-05-11)

Upstream cherry-picks

  • Respect DSN password over PGPASSWORD env var (upstream dbcli#1562).
  • Make auto-suggest optional via auto_suggest config (upstream dbcli#1570).
  • Add VERSION to built-in function completion (upstream dbcli#1574).
  • Hide timezone notice when local and server timezones are the same (upstream dbcli#1580).
  • Add support for Python 3.14 in classifiers (upstream dbcli#1583).
  • Fix integration with IPython magic (upstream dbcli#1585).

Internal

  • Remove outdated Vagrantfile (upstream dbcli#1581).
  • Bump Dockerfile to Python 3.14 (upstream dbcli#1584).

Install: uv tool install --force "pgcli-4.4.8-py3-none-any.whl[sshtunnel]"

pgcli.daf v4.4.5 (upstream: 4.4.0)

Choose a tag to compare

@DiegoDAF DiegoDAF released this 30 Apr 18:36

Features

  • Streaming NOTICE output for long-running commands. VACUUM VERBOSE, ANALYZE VERBOSE, REINDEX now display server messages line-by-line as they arrive instead of waiting until the command finishes. Works in non-interactive mode too (-c, -f).
  • Test coverage reporting with pytest-cov. Reports generated in CI and uploaded as artifacts (baseline: 68%, excluding magic.py and pyev.py).

Maintenance

  • Remove PyPI publish workflow (this fork is not published to PyPI).
  • Translate TODO to English, mark vi cursor shape item as discarded.
  • Fix ruff format in test_pgcompleter.py.

Install

uv tool install --force "https://github.com/DiegoDAF/pgcli.daf/releases/download/v4.4.5/pgcli-4.4.5-py3-none-any.whl[sshtunnel]"

Full changelog: v4.4.4...v4.4.5