Repository navigation
Releases
pgcli.daf 4.7.4
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,
Includepaths in~/.ssh/configwork, 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). -lshows each database's size; new\cdand\lscommands.- Passwords are kept out of the debug log,
--list-dsn, error lines and the process title. - SSH tunnel:
\cto 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 ALIASis now the connection (as inpgcli --dsn), and-vgoes to pg_dump; the tunnel's debug output moved to--tunnel-verbose.
Features
- Add
\cdand\lsfilesystem commands, with path completion for filesystem-aware commands including\e,\i,\log-file, and\o(upstream dbcli#1613). -l/--listshows 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 Accessfor 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. Includein~/.ssh/configkeeps a backslash that escapes nothing, as OpenSSH does:shlexdropped them all, so a Windows path such asC:\Users\me\.ssh\conf.d\*never matched.- The history and log paths migrated to the state directory are normalised (
...\state/historyon Windows). - Fix the development setup instructions to activate the
.venvdirectory created byuv 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\copytoo, 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-fproduced no file.\copynow 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\copywithout a semicolon still leaves the next line to run as its own statement. - The
floatformat 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,float8andnumericas float,int2toint8as 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,cidandmoney, 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-twith "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 newsql_ascii_fallback_encodingconfig option (latin-1by default). --auto-vertical-output,\x autoandauto_expandin the config work in-c/-fmode. 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 otherhostto 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\cand the reconnect opened tunnels too. The dead tunnel stays registered after a failed reconnect, so the next one tries the bastion again.-fand-cno longer drop a leadingLOAD,REASSIGN OWNEDorANALYSEstatement 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 withload '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
\nsin a-cstring or an-fscript saves the whole query. The rule that ends a backslash command at its newline exempts\nsat the prompt, but the-c/-fpath kept its own copy of the rule without the exemption, so\ns q select 1,\n2;savedselect 1,and ran2as 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 ...) crashedpgcli --dsnwith a traceback and made--list-dsngive up on every alias, andact = select pid, query from pg_stat_activityfailed on\n actwith "Bad arguments". --list-dsnhides the password of an alias in every spelling libpq accepts,sslpasswordincluded. It masked://user:pass@only; a key=value alias withpassword=or a URL with?password=was printed with the secret.- A custom
log_filename is honoured withlog_rotation_mode = none. Only its directory was used, solog_file = ~/x/mylog.txtwrote~/x/pgcli.log. Thepgclirccomment, which described a/var/log/pgcli/pgcli.logattempt, a daily rotation keeping 30 days andpgcli.YYYY-MM-DD.lognames that never existed, now describes the real behaviour. \v offprints "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, andpgcli_dump --tunnel-verboselogged the child command line with-d postgresql://user:pass@...intact. The process title shown bypsalso hides a password passed as?password=in a URL's query string. - A
.pg_service.confwhose leading comment has non-ASCII characters parses again. The comment skipper advanced the text position by character count, so# Configuraciónput 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 itCTEScan),Gather,Gather Merge,Memoize,Incremental Sort,WindowAgg,Unique,Materializeand 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 withKeyError: 'Rows Removed by Filter'. --log-fileand\log-filecreate 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 uncaughtre.errorat 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_isreadyreads the connection the way the dump wrappers do:--dsn ALIASis the server to check (it only picked the tunnel and then checkedlocalhost:5432), a-dconnection string counts for the tunnel decision, and-p abcis one line instead of a traceback.pgcli_dumpandpgcli_dumpallread getopt's attached short options and flag clusters (-hHOST,-p5433,-dDB,-ch HOST) for the tunnel decision; only the spaced forms were seen, sopgcli_dump -hdb.example.com mydbdialed 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 ata,b. - The EXPLAIN visualizer's exclusive time of a node above a
CTE Scanno longer subtracts the scan's time, as pev meant: the port compared the node type againstCTEScan, 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 thepgclilogger 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 theerror:part of the same line quoted the server's `LINE 1: alter role x password '...'...
pgcli.daf 4.7.3
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. -
-cand-fexit with psql's status codes. The status is that of the last-cor-fthat ran: a failed-cexits 1, and withon_error = STOP(psql'sON_ERROR_STOP) the first failure stops the rest and a failed script exits 3 instead of 1. WithRESUMEa failed last-cexits 1 where it used to exit 0. Measured against psql 18. Sent upstream as dbcli#1652. -
-ctogether with-fruns both. Only the-cran and the file was skipped without a word, a leftover of this fork's olderrun_clifrom before upstream made both run. -
-t/--tuples-onlyno longer hides SQL errors. The error of a failed statement travels where the status footer does, and-tdrops that footer, so a broken query printed nothing and only the exit code told. Errors now go to stderr under-t, as withpsql -t, so stdout still carries only rows.--no-statushid them the same way and now keeps them. Sent upstream as dbcli#1649. -
pgcli service=namehonors every setting in the service. Only host, port, user, dbname and password were passed on, so a service asking forsslmode=requireconnected without SSL wherepsql service=namerefuses. 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_dumpallread the password file the way pgcli and libpq do. Their own reader ignoredPGPASSFILE, kept the backslash of an escaped\:(handing pg_dump a wrong password) and took a partial glob such as*.example.comas a pattern, which libpq reads literally. They now use the same reader as pgcli; on a real 451-entry file nothing changed. -
%rand%pin aProxyCommandexpand 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'sUserandPort, and turned a literal%%into a broken token. Tokens inProxyJumpare 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 rawSecsh channel 0 open FAILEDand then libpq's "server closed the connection unexpectedly", so a typo in a DSN host looked like a server crash. -
A
-cstring or a script that starts with a blank line no longer reports1 line(s) of text before the statement removed: only lines that carried something are counted.
Features
-
The SSH tunnel also reads
/etc/ssh/ssh_configand what it includes (ssh_config.d/), after~/.ssh/config, the way ssh does: the first value found wins, so aProxyJump,UserorPortset 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 whensslmode=preferends up unencrypted against a remote server, also with-cand-f. libpq verifies against~/.postgresql/root.crtwhenever it exists, so aroot.crtwithout the server's CA silently turns everypreferconnection into a plaintext one. -
A
\serviceprompt token for connections made withservice=...orPGSERVICE, 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
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
-
\efand\evno longer crash pgcli inside an aborted transaction. The server's "current transaction is aborted" answer is raised by psycopg asInFailedSqlTransaction, which is not aProgrammingError, 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 forROLLBACK, as in psql. Upstream issue dbcli#1392. -
pgcli_dump/pgcli_dumpallapply 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 passwordpsqlwould have ignored. The permission check now lives in one place (pgpass.has_safe_permissions) and both use it; matching is untouched. -
pgcli_dump/pgcli_dumpallroute a URI connection string through the SSH tunnel. Given-d postgresql://[email protected]:5432/mydbthey read neither host nor port, so the tunnel opened tolocalhostand 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_dumpallread the user and database out of a connection string when looking up.pgpass. Only the bare-d mydbform was understood, so a tunnelled dump given a URI asked.pgpassabout the wrong user and about a database named after the whole URI. -
pgcli_dump/pgcli_dumpallno longer traceback on a non-numeric port in-p,--port=orPGPORT; they say which one is wrong. An emptyPGPORTfalls back to 5432. -
#is the bitwise XOR operator, not a comment marker.select 17 # 5returned 17 instead of 20 becausesqlparse.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
ProxyJumpfrom~/.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 withName or service not known. The tunnel now builds the exactssh -WProxyCommand 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 ... passwordincluded. 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_summaryon, 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,parallelorplan. 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 ownEXPLAIN (...)unconditionally, soexplain (analyze, buffers, wal, memory, serialize) select ...becameEXPLAIN (...) explain (...)and failed. The user's options are kept and only what the visualizer needs is added (ANALYZE,COSTS,FORMAT JSON). A plan requested astext,yamlorxmlis 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_summarysays. -
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_bindingsandprompt_utilswent from 48-66% to 100% coverage;completion_refresherandpgstylefrom 64% and 48% to 100%. Among the branches never exercised before: every function key except F5 and F9, the refresh restart handshake, and-yskipping the destructive-query confirmation in a script. - The pg_dump and pg_dumpall wrappers no longer carry a byte-identical copy of
parse_connection_argsandbuild_tunneled_args; 167 duplicated lines moved topgcli/dump_args.pyand one test module runs every case against both wrappers. tests/test_isready.pyno longer depends on the developer'sPGHOSTandPGPORT.- The fork's behave suite is green again after the
-c/-fexit-code change; the-tspecial-command scenario passed\\dtwith two backslashes and tested nothing. - The pgcli_dump/pgcli_dumpall behave steps capture output as bytes, since
pg_dump -F cwrites a binary dump. - CI:
codeql-actionv4 andactions/checkoutv5 (v2/v3 are deprecated), read-only token, Python 3.14 in the matrix, andapt-get updatebefore 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.clickis capped below 9, as upstream does. - Test hygiene:
@dbteston a test that errored without a server; aparametrizefed a list instead of anitertools.productiterator (pytest 9 rejects it);click.get_text_streamreplaced bysys.stdin(Click 9 removes it). - SSH tunnel follows
Includedirectives in~/.ssh/config, which paramiko's parser ignores. Seventeen configurations compared againstssh -G, all match. - Merged upstream 4.7.1. The only code upstream added that was not already here is one
@dbtestmarker.
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
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 stdinno longer kill the session. psycopg refuses those incursor.execute(), but only after the server has entered the COPY state, so the connection stayed busy and every later statement failed withanother command is already in progressuntil you reconnected. The COPY is now closed before the error surfaces, and the message points at\copy. -
.pg_service.confvalues are read literally, the way libpq does. They were parsed with ConfigObj, which strips an inline# ...as a comment, removes surrounding quotes and turnsa,binto a list, so a password likeabc#defsilently arrived asabc. Verified against a live server on ten cases; pgcli now matches libpq on all of them. It also warns when a value contains" #", becausepg_service.confhas no end-of-line comments (unlikepostgresql.conf) and libpq's own error forport=5432 # prodnever mentions comments. -
-f/-cno 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:\watchrepeats only its own statement, and a bare\watchpicks 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/-cexit non-zero whenon_erroris STOP, like psql withON_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/--listand--pingkeep a plain database name instead of replacing it withpostgres. psql connects to the named database and lists from there, so only the case where no database is given at all falls back topostgres.
Internal
- Recorded upstream 4.6.0 and everything through
924e7d4as merged. Every item was already present here; two of 4.6.0's items originated in this fork (\neand.pgpassover SSH tunnels). connect_timeoutis read withas_int()at load time, so a non-numeric value is reported instead of silently ignored, and the precedence rules moved into a unit-testedget_connect_timeout()helper.- Dropped a stale
xfailthat was hiding a passing test, and documented the one that is still needed. - Exposed
Changelog,RepositoryandIssuesproject 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
pgcli.daf 4.5.8 (tracks upstream 4.5.0). All changes since v4.5.7.
Features
- Connection timeout: new
--timeoutoption andconnect_timeoutconfig 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.confis 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/\neconvert-- xline 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: withday-of-week/day-of-monthrotation, a slot's file is truncated when it comes around again instead of appending forever.--on-error [STOP|RESUME]to override theon_errorconfig per invocation.
Bug fixes
- Security: unblocked sqlparse 0.6.x. The
<0.6cap 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
UPDATEdetection 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\Gfailed and you could not even quit. -l/--listand--pingno longer discard the connection string (URI orkey=value), which fell back to a local socket connection as the OS user.- CLI flags override a
key=valueconninfo, completing the 4.5.7 fix that covered--dsnaliases and URIs. -o/--outputwrites only result rows with--tuples-onlyor in-c/-fmode, so generated.sqlscripts come out clean. Interactive\okeeps 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 BYand after a column namedtype, 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
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 +autocommitconfig (defaultTrue), manual-transaction mode with anAutocommit: OFFtoolbar 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(defaultFalse). - psql-style paste (
paste_modeconfig + 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_EDITORfor\e/\ev/\ef/\ne, matching psql precedence (issue dbcli#1398). .pgpassfor 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\Gunder explain mode and keeps the restrict-mode guard authoritative. (Upstream bug.) - CLI flags override a
--dsn/URI, matching psql.-d/-U/-h/-pwere silently ignored for--dsn/URI connections; e.g.pgcli --dsn prod -d otherdbstill hit the alias's db. Only genuinely explicit flags override; the--portdefault and env vars don't clobber the DSN. - Security:
obfuscate_process_passwordleaked all-but-last password when the process title held multiple connection URLs; now masks every URL's password.
Internal
confirm_destructive_querytakes aforceparameter 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
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 innamedqueries.dsaves 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
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_resultsconfig option (defaultFalse). 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_passwordconfig option (defaultFalse). 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. WhenTruethe secret is saved automatically after a successful connect; whenFalsepgcli asks before saving. Requireskeyring = 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)
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_relationsandextend_columns, which raised aKeyError. - Fix row-limit detection for multiline
LIMITclauses (upstream dbcli#1594)._has_limitnow parses withsqlparseinstead of a substring match, soLIMITsplit across lines/tabs is detected andLIMITinside comments or string literals no longer triggers the row-limit prompt.
Maintenance
- Drop a now-unused
# type: ignore[operator]inpgcompleter.py(keepsmypy2.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
\qnow offers to exit even with an active transaction, instead of leaving the user stuck. - Let
sqlparseaccept arbitrarily-large queries by disablingMAX_GROUPING_DEPTHandMAX_GROUPING_TOKENS(upstream dbcli#1595). Fixes crashes when parsing very large SQL.
Internal
- Pass
PG*env variables to integration tests viatox.inipassenv(upstream dbcli#1596).
v4.4.6 (2026-05-11)
Upstream cherry-picks
- Respect DSN password over
PGPASSWORDenv var (upstream dbcli#1562). - Make auto-suggest optional via
auto_suggestconfig (upstream dbcli#1570). - Add
VERSIONto 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)
Features
- Streaming NOTICE output for long-running commands.
VACUUM VERBOSE,ANALYZE VERBOSE,REINDEXnow 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%, excludingmagic.pyandpyev.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