dbctl 0.7.3__tar.gz → 0.7.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dbctl-0.7.3 → dbctl-0.7.7}/CHANGELOG.md +125 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/PKG-INFO +84 -12
- {dbctl-0.7.3 → dbctl-0.7.7}/README.md +81 -11
- {dbctl-0.7.3 → dbctl-0.7.7}/dbctl/cli.py +297 -2
- {dbctl-0.7.3 → dbctl-0.7.7}/dbctl/db.py +12 -4
- {dbctl-0.7.3 → dbctl-0.7.7}/dbctl/runtime.py +61 -11
- dbctl-0.7.7/dbctl/ui/__init__.py +8 -0
- dbctl-0.7.7/dbctl/ui/app.py +234 -0
- dbctl-0.7.7/dbctl/ui/connection_tree.py +539 -0
- dbctl-0.7.7/dbctl/ui/editor_tab.py +164 -0
- dbctl-0.7.7/dbctl/ui/grouping.py +63 -0
- dbctl-0.7.7/dbctl/ui/operation_tab.py +171 -0
- dbctl-0.7.7/dbctl/ui/registry.py +39 -0
- dbctl-0.7.7/dbctl/ui/results.py +29 -0
- dbctl-0.7.7/dbctl/ui/schema.py +89 -0
- dbctl-0.7.7/dbctl/ui/screens.py +146 -0
- dbctl-0.7.7/dbctl/ui/session.py +137 -0
- dbctl-0.7.7/dbctl/ui/splitter.py +109 -0
- dbctl-0.7.7/dbctl/ui/sql_templates.py +55 -0
- dbctl-0.7.7/dbctl/ui/tabs.py +87 -0
- dbctl-0.7.7/docs/execute.md +149 -0
- dbctl-0.7.7/docs/logo_small.png +0 -0
- dbctl-0.7.7/docs/tui.md +184 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/pyproject.toml +14 -2
- dbctl-0.7.7/tests/test_execute.py +454 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/tests/test_regressions.py +3 -3
- dbctl-0.7.7/tests/ui/conftest.py +76 -0
- dbctl-0.7.7/tests/ui/test_app.py +43 -0
- dbctl-0.7.7/tests/ui/test_connection_tree.py +140 -0
- dbctl-0.7.7/tests/ui/test_connection_tree_grouping.py +200 -0
- dbctl-0.7.7/tests/ui/test_editor_tab.py +99 -0
- dbctl-0.7.7/tests/ui/test_grouping.py +112 -0
- dbctl-0.7.7/tests/ui/test_operation_launcher.py +119 -0
- dbctl-0.7.7/tests/ui/test_operation_tab.py +126 -0
- dbctl-0.7.7/tests/ui/test_resize.py +79 -0
- dbctl-0.7.7/tests/ui/test_schema.py +50 -0
- dbctl-0.7.7/tests/ui/test_screens.py +115 -0
- dbctl-0.7.7/tests/ui/test_session.py +74 -0
- dbctl-0.7.7/tests/ui/test_sql_templates.py +89 -0
- dbctl-0.7.7/tests/ui/test_status_bar_and_loading.py +135 -0
- dbctl-0.7.7/tests/ui/test_tab_resize.py +139 -0
- dbctl-0.7.7/uv.lock +1196 -0
- dbctl-0.7.3/docs/logo_small.png +0 -0
- dbctl-0.7.3/uv.lock +0 -810
- {dbctl-0.7.3 → dbctl-0.7.7}/.dbctl/connections.yaml +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/.dbctl/operations.yaml +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/.github/workflows/ci.yml +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/.github-local/ci.yml +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/.gitignore +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/Makefile +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/dbctl/__init__.py +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/dbctl/__main__.py +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/dbctl/audit.py +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/dbctl/config.py +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/dbctl/connections.py +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/dbctl/execute.py +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/dbctl/init.py +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/dbctl/multi.py +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/dbctl/operations.py +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/dbctl/refs.py +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/dbctl/reports.py +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/dbctl/tunnels/__init__.py +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/dbctl/tunnels/azure.py +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/dbctl/tunnels/base.py +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/dbctl/tunnels/direct.py +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/dbctl/tunnels/gcp.py +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/dbctl/tunnels/k8s.py +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/dbctl/tunnels/ssh.py +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/dbctl/tunnels/ssm.py +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/docker-compose.yml +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/docs/ACTION_OUTPUT.md +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/docs/DESIGN.md +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/docs/SESSION_STATE.md +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/docs/connections.md +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/docs/logo.png +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/docs/operations.md +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/docs/tutorial.md +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/seed/mssql.sql +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/seed/mysql.sql +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/seed/postgres.sql +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/tests/test_azure_tunnel.py +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/tests/test_bastion_tags.py +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/tests/test_connections_loader.py +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/tests/test_copy_features.py +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/tests/test_gcp_tunnel.py +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/tests/test_k8s_tunnel.py +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/tests/test_refs.py +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/tests/test_smoke.py +0 -0
- {dbctl-0.7.3 → dbctl-0.7.7}/tests/test_sso_cache.py +0 -0
|
@@ -5,6 +5,131 @@ All notable changes to this project will be documented here.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.7.7] — 2026-08-10
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **`ruff format` failed in CI** on the v0.7.6 commit — three files
|
|
13
|
+
(`dbctl/cli.py`, `dbctl/runtime.py`, `tests/test_execute.py`) had
|
|
14
|
+
manually-typed signatures that fit on one line per `ruff format`'s
|
|
15
|
+
rules but were wrapped in the source. Reformatted to the canonical
|
|
16
|
+
layout (`def opened_engine(ctx: click.Context, canonical: str, conn:
|
|
17
|
+
Connection) -> Iterator[OpenedStub]:` on one line; same for the
|
|
18
|
+
`execute_cmd` callback signature and the inline-URL error `print`
|
|
19
|
+
call). Re-running `uv run ruff format --check dbctl tests` is now
|
|
20
|
+
clean and `make format` is a no-op on top of this commit.
|
|
21
|
+
- **Typo in `docs/execute.md`** — `# dduckdb inline URL` comment →
|
|
22
|
+
`# duckdb inline URL`.
|
|
23
|
+
- **README project layout** now lists `execute.md` under `docs/` so the
|
|
24
|
+
new reference page is discoverable from the layout block.
|
|
25
|
+
|
|
26
|
+
No behaviour change; re-running `dbctl execute --help` produces the
|
|
27
|
+
same output as v0.7.6.
|
|
28
|
+
|
|
29
|
+
## [0.7.6] — 2026-08-10
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- **`dbctl execute`** — run ad-hoc SQL without declaring an operation in
|
|
34
|
+
`operations.yaml`. The new top-level verb takes a connection name *or* a
|
|
35
|
+
full SQLAlchemy URL, the SQL string as a single positional argument, and
|
|
36
|
+
the short option set the existing operation dispatch uses:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
dbctl execute -c pg -o json "SELECT * FROM users"
|
|
40
|
+
dbctl execute -c pg --show-sql --apply "DELETE FROM users WHERE name='bob'"
|
|
41
|
+
dbctl execute -c "postgresql+psycopg://u:p@host:5432/db" -o csv "SELECT 1"
|
|
42
|
+
dbctl execute -c pg -o yaml "SELECT * FROM users LIMIT 10"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Behaviour:
|
|
46
|
+
|
|
47
|
+
- **`-c` / `--connection`** accepts a registered connection name /
|
|
48
|
+
alias (resolved via `connections.yaml`) **or** a full SQLAlchemy URL
|
|
49
|
+
(detected by the presence of `://`). Inline URLs build a transient
|
|
50
|
+
`direct` Connection so the SSM/SSH password-source plumbing, native-lib
|
|
51
|
+
install hints, and `connect_args` (connect-timeout, Windows SSO) all
|
|
52
|
+
apply uniformly.
|
|
53
|
+
- **`-o` / `--output`** chooses `table` (default) / `json` / `csv` /
|
|
54
|
+
`yaml` for SELECT-shaped results, exactly like declared `fetch` ops.
|
|
55
|
+
- The SQL is auto-classified by its first verb: `SELECT` / `WITH` /
|
|
56
|
+
`SHOW` / `EXPLAIN` / `DESCRIBE` / `PRAGMA` / `VALUES` / `TABLE` runs
|
|
57
|
+
as a query (rendered via `--output`); anything else runs as DML
|
|
58
|
+
(INSERT / UPDATE / DELETE / CREATE / DROP / ALTER / ...) inside
|
|
59
|
+
`engine.begin()`.
|
|
60
|
+
- DML respects the connection's `safety.confirm` (dry-run-by-default
|
|
61
|
+
unless `--apply`), `safety.read_only` (DML blocked with exit 6), and
|
|
62
|
+
prompts before commit unless `--yes` / `-y` is passed — exactly the
|
|
63
|
+
same gate as a declared `mode: execute` operation. Inline URLs
|
|
64
|
+
default to `safety.confirm: true`.
|
|
65
|
+
- DDL that returns `rowcount = -1` (CREATE TABLE, DROP, ...) is rendered
|
|
66
|
+
as `OK in <ms>ms` instead of the misleading `OK -1 row(s) affected`.
|
|
67
|
+
Real INSERT/UPDATE/DELETE row counts are still reported.
|
|
68
|
+
- Every run is appended to `~/.dbctl/history.jsonl` as
|
|
69
|
+
`operation="execute"` with the SQL (truncated to 500 chars) in the
|
|
70
|
+
`params.sql` field, so it shows up under `dbctl history list`. The
|
|
71
|
+
audit `connection` field is the canonical connection name, or the
|
|
72
|
+
literal `<inline>` token for inline URL runs.
|
|
73
|
+
- `--show-sql` prints the resolved SQL before executing; `--` separates
|
|
74
|
+
options from positional SQL that begins with a dash (Click would
|
|
75
|
+
otherwise treat the leading dash as an unknown option).
|
|
76
|
+
- The new verb is registered as a top-level static command alongside
|
|
77
|
+
`connections` / `operations` / `doctor` / `init` / `history` /
|
|
78
|
+
`tunnel` / `ui`, so shell completion picks it up.
|
|
79
|
+
|
|
80
|
+
This closes the long-standing "no ad-hoc query command" gap without
|
|
81
|
+
weakening the declarative operation registry: the declared
|
|
82
|
+
`operations.yaml` model remains the recommended way to make "what can
|
|
83
|
+
be run against this DB" discoverable from a versioned file, while
|
|
84
|
+
`execute` covers the exploratory / break-glass path the TUI already
|
|
85
|
+
serves — both share the same tunnel / safety / audit plumbing so every
|
|
86
|
+
run is still logged and policy-checked.
|
|
87
|
+
|
|
88
|
+
## [0.7.4] — 2026-08-10
|
|
89
|
+
|
|
90
|
+
### Added
|
|
91
|
+
|
|
92
|
+
- **`dbctl ui`** — an interactive [Textual](https://textual.textualize.io/)
|
|
93
|
+
TUI on top of the same connections/operations/tunnels/audit-log stack as
|
|
94
|
+
the CLI (`textual` is a base dependency, no separate install step). See
|
|
95
|
+
`docs/tui.md` for the full reference.
|
|
96
|
+
- **Connection tree** doubling as a lazily-loaded schema browser: `m`
|
|
97
|
+
toggles a simple `connection → table → column` view and a fuller
|
|
98
|
+
`connection → schema → Tables/Views → table/view → Columns/Indexes`
|
|
99
|
+
view; `g` toggles a flat list and a grouping that treats `-` as a path
|
|
100
|
+
separator (`in-gateway-ifp-dev` nests under `in-gateway` → `ifp`), with
|
|
101
|
+
single-child folders compressed and support for a name that's both a
|
|
102
|
+
group and a real connection at once (`ifp` / `ifp-gateway`). `c`/`d`/`t`
|
|
103
|
+
connect/disconnect/test-tunnel; `a`/`e` open `connections.yaml` in
|
|
104
|
+
`$EDITOR`.
|
|
105
|
+
- **Tabbed workspace**: a SQL editor (syntax-highlighted) or an
|
|
106
|
+
operation-launcher (a form built from a declared operation's
|
|
107
|
+
parameters) per tab, each with an independently resizable results
|
|
108
|
+
table and a status line (row count / rows-affected + duration) below
|
|
109
|
+
it. `Ctrl+N` new tab, `Ctrl+O` searchable operation launcher (type to
|
|
110
|
+
filter), `Ctrl+W` close, `Ctrl+R` run. Activating a table/view in the
|
|
111
|
+
tree opens a SQL tab pre-filled with a dialect-correct preview query
|
|
112
|
+
(`LIMIT` / `TOP` / `FETCH FIRST … ROWS ONLY`, with per-dialect
|
|
113
|
+
identifier quoting for mixed-case table/schema names).
|
|
114
|
+
- Connect/disconnect/test-tunnel and every SQL/operation run happen on a
|
|
115
|
+
background thread with a loading indicator, so the UI stays responsive
|
|
116
|
+
instead of freezing while a tunnel spins up or a query runs.
|
|
117
|
+
- Every run still respects `safety.read_only` / `safety.confirm` /
|
|
118
|
+
`allowed_operations` and is appended to `~/.dbctl/history.jsonl`,
|
|
119
|
+
exactly like a CLI-driven run.
|
|
120
|
+
|
|
121
|
+
### Fixed
|
|
122
|
+
|
|
123
|
+
- **`uv.lock` could resolve against a contributor's local package-index
|
|
124
|
+
override instead of public PyPI** — a machine-level `uv` config pointing
|
|
125
|
+
at an internal package mirror silently became the index every regenerated
|
|
126
|
+
`uv.lock` entry was resolved and hashed against, which both leaked an
|
|
127
|
+
internal hostname into the lock file and broke `uv sync --frozen` for
|
|
128
|
+
anyone (CI included) without access to that mirror. Pinned
|
|
129
|
+
`[[tool.uv.index]]` in `pyproject.toml` with `default = true` so this
|
|
130
|
+
project always locks against `pypi.org`/`files.pythonhosted.org`
|
|
131
|
+
regardless of what index a contributor's environment defaults to.
|
|
132
|
+
|
|
8
133
|
## [0.7.3] — 2026-08-04
|
|
9
134
|
|
|
10
135
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dbctl
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.7
|
|
4
4
|
Summary: Generic CLI to monitor, control, and administer multiple databases via SSM, SSH, or direct connection.
|
|
5
5
|
Author: dbctl contributors
|
|
6
6
|
License: MIT
|
|
@@ -25,8 +25,10 @@ Requires-Dist: pyodbc>=5
|
|
|
25
25
|
Requires-Dist: pyyaml>=6
|
|
26
26
|
Requires-Dist: rich>=13
|
|
27
27
|
Requires-Dist: sqlalchemy>=2
|
|
28
|
+
Requires-Dist: textual>=0.60
|
|
28
29
|
Provides-Extra: dev
|
|
29
30
|
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
30
32
|
Requires-Dist: pytest-cov>=5; extra == 'dev'
|
|
31
33
|
Requires-Dist: pytest>=8; extra == 'dev'
|
|
32
34
|
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
@@ -71,11 +73,13 @@ leaving the terminal.
|
|
|
71
73
|
| DML is one `Ctrl-Enter` away | DML is **dry-run by default** until `--apply`; `--yes` skips the prompt |
|
|
72
74
|
| No cross-DB diff | `dbctl diff user-count prod-tenant1 int-tenant1` |
|
|
73
75
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
`dbctl tunnel
|
|
76
|
+
The declarative CLI above is still the primary interface: declaring
|
|
77
|
+
operations in YAML keeps "what can be run against this DB" discoverable from
|
|
78
|
+
a versioned file instead of buried in your shell history. For ad-hoc
|
|
79
|
+
exploration, `dbctl ui` (see below) gives you a schema browser and a SQL
|
|
80
|
+
editor without leaving `dbctl`'s tunnel/safety/audit model - or open the
|
|
81
|
+
tunnel with `dbctl tunnel open <conn>` and point your favourite client at
|
|
82
|
+
the local bind.
|
|
79
83
|
|
|
80
84
|
### How it reaches a database
|
|
81
85
|
|
|
@@ -118,9 +122,9 @@ the same params.
|
|
|
118
122
|
|
|
119
123
|
## Why "the operations are declarative"
|
|
120
124
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
125
|
+
The recommended path for permanent, repeatable work is to declare an
|
|
126
|
+
operation in `operations.yaml` first — its name, its parameters (with
|
|
127
|
+
types, descriptions, positional vs keyword), its SQL, and its mode
|
|
124
128
|
(`execute` / `fetch` / `fetch_one` / `script` / `upsert` for single-DB;
|
|
125
129
|
`diff` / `compare` / `copy` / `sync` / `validate` / `replay` for multi-DB).
|
|
126
130
|
The CLI then synthesises a Click subcommand per operation, so:
|
|
@@ -131,7 +135,18 @@ The CLI then synthesises a Click subcommand per operation, so:
|
|
|
131
135
|
- the audit log is queryable by operation name (`dbctl history list`).
|
|
132
136
|
|
|
133
137
|
This keeps "what can be run against this DB" discoverable from a versioned
|
|
134
|
-
config file, instead of buried in shell history.
|
|
138
|
+
config file, instead of buried in shell history. For exploratory / break-glass
|
|
139
|
+
SQL — the path the TUI serves — `dbctl execute` (added in v0.7.6) runs
|
|
140
|
+
ad-hoc SQL through the same tunnel / safety / audit plumbing without
|
|
141
|
+
requiring a declared operation:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
dbctl execute -c pg -o json "SELECT * FROM users"
|
|
145
|
+
dbctl execute -c "postgresql+psycopg://u:p@host:5432/db" -o csv "SELECT 1"
|
|
146
|
+
dbctl execute -c pg --show-sql --apply "DELETE FROM users WHERE name='bob'"
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
See [`docs/execute.md`](docs/execute.md) for the full reference.
|
|
135
150
|
|
|
136
151
|
## Install (uv)
|
|
137
152
|
|
|
@@ -184,6 +199,12 @@ dbctl pg add-user stephen 12 --show-sql # dry-run (prints SQL)
|
|
|
184
199
|
dbctl pg add-user stephen 12 --apply --yes # commit (no prompt)
|
|
185
200
|
dbctl pg increase-credits alice 10 --apply -y # +10% on alice's credits
|
|
186
201
|
|
|
202
|
+
# ad-hoc SQL without declaring an operation (since v0.7.6):
|
|
203
|
+
dbctl execute -c pg -o json "SELECT * FROM users"
|
|
204
|
+
dbctl execute -c "postgresql+psycopg://u:p@host:5432/db" -o csv "SELECT 1"
|
|
205
|
+
dbctl execute -c pg --show-sql --apply "DELETE FROM users WHERE name='bob'"
|
|
206
|
+
dbctl execute -c pg -o yaml -- "SELECT * FROM users WHERE name='-alice'" # SQL starting with a dash needs `--`
|
|
207
|
+
|
|
187
208
|
# multi-DB modes (operation-first, preferred since v0.6):
|
|
188
209
|
dbctl user-count pg my # multi-DB diff
|
|
189
210
|
dbctl compare-credits pg my Daily
|
|
@@ -352,6 +373,41 @@ reference.
|
|
|
352
373
|
Secret-typed **operation** parameters (`type: secret`) are redacted in the
|
|
353
374
|
audit log regardless of which DB password source the connection uses.
|
|
354
375
|
|
|
376
|
+
## Interactive UI (`dbctl ui`)
|
|
377
|
+
|
|
378
|
+
A [Textual](https://textual.textualize.io/) TUI for interactive work, on top
|
|
379
|
+
of the same connections/operations/tunnels/audit-log stack as the CLI - not
|
|
380
|
+
a separate code path with its own rules. `textual` is a base dependency, so
|
|
381
|
+
no separate install step.
|
|
382
|
+
|
|
383
|
+
```bash
|
|
384
|
+
dbctl ui
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
- **Left pane**: a connection tree that doubles as a lazily-loaded schema
|
|
388
|
+
browser (`m` toggles a simple `connection → table → column` view and a
|
|
389
|
+
fuller `connection → schema → Tables/Views → table/view →
|
|
390
|
+
Columns/Indexes` view). Expanding a connection connects it if needed;
|
|
391
|
+
activating (Enter) a table/view opens a SQL tab pre-filled with a
|
|
392
|
+
dialect-correct preview query using the real table name. `g` toggles a
|
|
393
|
+
grouped view that treats `-` as a path separator (`in-gateway-ifp-dev`
|
|
394
|
+
nests under `in-gateway` → `ifp`) - handy once a fleet has more than a
|
|
395
|
+
handful of connections.
|
|
396
|
+
- **Right pane**: a tabbed workspace of SQL editor / operation-launcher
|
|
397
|
+
tabs (`Ctrl+N` new tab, `Ctrl+O` searchable operation launcher, `Ctrl+W`
|
|
398
|
+
close, `Ctrl+R` run), each with an independently resizable results table
|
|
399
|
+
below it and a status line (row count / rows affected + duration) after
|
|
400
|
+
each run.
|
|
401
|
+
- Connecting, disconnecting, testing a tunnel, and every SQL/operation run
|
|
402
|
+
happen on a background thread with a loading indicator, so the UI stays
|
|
403
|
+
responsive instead of freezing while a tunnel spins up or a query runs.
|
|
404
|
+
- Every run still respects `safety.read_only` / `safety.confirm` /
|
|
405
|
+
`allowed_operations` and is appended to `~/.dbctl/history.jsonl`, exactly
|
|
406
|
+
like a CLI-driven run.
|
|
407
|
+
|
|
408
|
+
See [`docs/tui.md`](docs/tui.md) for the full keybinding reference and
|
|
409
|
+
per-dialect SQL details (row-limit clause, identifier quoting).
|
|
410
|
+
|
|
355
411
|
## Shell completion
|
|
356
412
|
|
|
357
413
|
```bash
|
|
@@ -375,6 +431,8 @@ dbctl/
|
|
|
375
431
|
├── docs/
|
|
376
432
|
│ ├── connections.md # connections.yaml reference
|
|
377
433
|
│ ├── operations.md # operations.yaml reference
|
|
434
|
+
│ ├── execute.md # dbctl execute (ad-hoc SQL) reference
|
|
435
|
+
│ ├── tui.md # dbctl ui reference (keybindings, dialect SQL)
|
|
378
436
|
│ └── DESIGN.md # architecture and design decisions
|
|
379
437
|
└── dbctl/
|
|
380
438
|
├── cli.py # dynamic groups + per-op Click commands
|
|
@@ -388,7 +446,21 @@ dbctl/
|
|
|
388
446
|
├── reports.py # rich tables / json / csv / yaml + diff + copy/sync/validate rendering
|
|
389
447
|
├── audit.py # history.jsonl
|
|
390
448
|
├── runtime.py # opened_conn() ctx-mgr (tunnel+engine+healthcheck)
|
|
391
|
-
|
|
449
|
+
├── init.py # dbctl init wizard
|
|
450
|
+
└── ui/ # Textual TUI (`dbctl ui`)
|
|
451
|
+
├── app.py # DbctlApp: connection tree + tabbed workspace
|
|
452
|
+
├── session.py # per-connection tunnel+engine kept open across tab-runs
|
|
453
|
+
├── connection_tree.py # lazy schema browser (simple/normal + flat/grouped views)
|
|
454
|
+
├── grouping.py # "-" as a path separator -> compressed connection-name trie
|
|
455
|
+
├── schema.py # sqlalchemy.inspect() wrapper for the tree
|
|
456
|
+
├── editor_tab.py # SQL editor tab
|
|
457
|
+
├── operation_tab.py # operation-launcher tab
|
|
458
|
+
├── sql_templates.py # dialect-aware default SELECT + identifier quoting
|
|
459
|
+
├── screens.py # confirm / new-tab / operation-launcher modal screens
|
|
460
|
+
├── splitter.py # draggable pane-resize bars
|
|
461
|
+
├── tabs.py # shared tab base: resize, toolbar, status bar, loading indicator
|
|
462
|
+
├── results.py # DataTable rendering helpers
|
|
463
|
+
└── registry.py # tolerant connections/operations loading
|
|
392
464
|
```
|
|
393
465
|
|
|
394
466
|
## Development
|
|
@@ -397,7 +469,7 @@ dbctl/
|
|
|
397
469
|
uv sync --extra dev
|
|
398
470
|
make help # list all Makefile targets
|
|
399
471
|
make check # lint + unit tests (the pre-commit gate)
|
|
400
|
-
make test # unit tests (~
|
|
472
|
+
make test # unit tests (~270 tests, sqlite-backed, no docker)
|
|
401
473
|
make typecheck # mypy strict (pre-existing debt; non-blocking)
|
|
402
474
|
make smoke # docker compose up + dbctl doctor against the fleet
|
|
403
475
|
make build # wheel + sdist via uv
|
|
@@ -37,11 +37,13 @@ leaving the terminal.
|
|
|
37
37
|
| DML is one `Ctrl-Enter` away | DML is **dry-run by default** until `--apply`; `--yes` skips the prompt |
|
|
38
38
|
| No cross-DB diff | `dbctl diff user-count prod-tenant1 int-tenant1` |
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
`dbctl tunnel
|
|
40
|
+
The declarative CLI above is still the primary interface: declaring
|
|
41
|
+
operations in YAML keeps "what can be run against this DB" discoverable from
|
|
42
|
+
a versioned file instead of buried in your shell history. For ad-hoc
|
|
43
|
+
exploration, `dbctl ui` (see below) gives you a schema browser and a SQL
|
|
44
|
+
editor without leaving `dbctl`'s tunnel/safety/audit model - or open the
|
|
45
|
+
tunnel with `dbctl tunnel open <conn>` and point your favourite client at
|
|
46
|
+
the local bind.
|
|
45
47
|
|
|
46
48
|
### How it reaches a database
|
|
47
49
|
|
|
@@ -84,9 +86,9 @@ the same params.
|
|
|
84
86
|
|
|
85
87
|
## Why "the operations are declarative"
|
|
86
88
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
89
|
+
The recommended path for permanent, repeatable work is to declare an
|
|
90
|
+
operation in `operations.yaml` first — its name, its parameters (with
|
|
91
|
+
types, descriptions, positional vs keyword), its SQL, and its mode
|
|
90
92
|
(`execute` / `fetch` / `fetch_one` / `script` / `upsert` for single-DB;
|
|
91
93
|
`diff` / `compare` / `copy` / `sync` / `validate` / `replay` for multi-DB).
|
|
92
94
|
The CLI then synthesises a Click subcommand per operation, so:
|
|
@@ -97,7 +99,18 @@ The CLI then synthesises a Click subcommand per operation, so:
|
|
|
97
99
|
- the audit log is queryable by operation name (`dbctl history list`).
|
|
98
100
|
|
|
99
101
|
This keeps "what can be run against this DB" discoverable from a versioned
|
|
100
|
-
config file, instead of buried in shell history.
|
|
102
|
+
config file, instead of buried in shell history. For exploratory / break-glass
|
|
103
|
+
SQL — the path the TUI serves — `dbctl execute` (added in v0.7.6) runs
|
|
104
|
+
ad-hoc SQL through the same tunnel / safety / audit plumbing without
|
|
105
|
+
requiring a declared operation:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
dbctl execute -c pg -o json "SELECT * FROM users"
|
|
109
|
+
dbctl execute -c "postgresql+psycopg://u:p@host:5432/db" -o csv "SELECT 1"
|
|
110
|
+
dbctl execute -c pg --show-sql --apply "DELETE FROM users WHERE name='bob'"
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
See [`docs/execute.md`](docs/execute.md) for the full reference.
|
|
101
114
|
|
|
102
115
|
## Install (uv)
|
|
103
116
|
|
|
@@ -150,6 +163,12 @@ dbctl pg add-user stephen 12 --show-sql # dry-run (prints SQL)
|
|
|
150
163
|
dbctl pg add-user stephen 12 --apply --yes # commit (no prompt)
|
|
151
164
|
dbctl pg increase-credits alice 10 --apply -y # +10% on alice's credits
|
|
152
165
|
|
|
166
|
+
# ad-hoc SQL without declaring an operation (since v0.7.6):
|
|
167
|
+
dbctl execute -c pg -o json "SELECT * FROM users"
|
|
168
|
+
dbctl execute -c "postgresql+psycopg://u:p@host:5432/db" -o csv "SELECT 1"
|
|
169
|
+
dbctl execute -c pg --show-sql --apply "DELETE FROM users WHERE name='bob'"
|
|
170
|
+
dbctl execute -c pg -o yaml -- "SELECT * FROM users WHERE name='-alice'" # SQL starting with a dash needs `--`
|
|
171
|
+
|
|
153
172
|
# multi-DB modes (operation-first, preferred since v0.6):
|
|
154
173
|
dbctl user-count pg my # multi-DB diff
|
|
155
174
|
dbctl compare-credits pg my Daily
|
|
@@ -318,6 +337,41 @@ reference.
|
|
|
318
337
|
Secret-typed **operation** parameters (`type: secret`) are redacted in the
|
|
319
338
|
audit log regardless of which DB password source the connection uses.
|
|
320
339
|
|
|
340
|
+
## Interactive UI (`dbctl ui`)
|
|
341
|
+
|
|
342
|
+
A [Textual](https://textual.textualize.io/) TUI for interactive work, on top
|
|
343
|
+
of the same connections/operations/tunnels/audit-log stack as the CLI - not
|
|
344
|
+
a separate code path with its own rules. `textual` is a base dependency, so
|
|
345
|
+
no separate install step.
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
dbctl ui
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
- **Left pane**: a connection tree that doubles as a lazily-loaded schema
|
|
352
|
+
browser (`m` toggles a simple `connection → table → column` view and a
|
|
353
|
+
fuller `connection → schema → Tables/Views → table/view →
|
|
354
|
+
Columns/Indexes` view). Expanding a connection connects it if needed;
|
|
355
|
+
activating (Enter) a table/view opens a SQL tab pre-filled with a
|
|
356
|
+
dialect-correct preview query using the real table name. `g` toggles a
|
|
357
|
+
grouped view that treats `-` as a path separator (`in-gateway-ifp-dev`
|
|
358
|
+
nests under `in-gateway` → `ifp`) - handy once a fleet has more than a
|
|
359
|
+
handful of connections.
|
|
360
|
+
- **Right pane**: a tabbed workspace of SQL editor / operation-launcher
|
|
361
|
+
tabs (`Ctrl+N` new tab, `Ctrl+O` searchable operation launcher, `Ctrl+W`
|
|
362
|
+
close, `Ctrl+R` run), each with an independently resizable results table
|
|
363
|
+
below it and a status line (row count / rows affected + duration) after
|
|
364
|
+
each run.
|
|
365
|
+
- Connecting, disconnecting, testing a tunnel, and every SQL/operation run
|
|
366
|
+
happen on a background thread with a loading indicator, so the UI stays
|
|
367
|
+
responsive instead of freezing while a tunnel spins up or a query runs.
|
|
368
|
+
- Every run still respects `safety.read_only` / `safety.confirm` /
|
|
369
|
+
`allowed_operations` and is appended to `~/.dbctl/history.jsonl`, exactly
|
|
370
|
+
like a CLI-driven run.
|
|
371
|
+
|
|
372
|
+
See [`docs/tui.md`](docs/tui.md) for the full keybinding reference and
|
|
373
|
+
per-dialect SQL details (row-limit clause, identifier quoting).
|
|
374
|
+
|
|
321
375
|
## Shell completion
|
|
322
376
|
|
|
323
377
|
```bash
|
|
@@ -341,6 +395,8 @@ dbctl/
|
|
|
341
395
|
├── docs/
|
|
342
396
|
│ ├── connections.md # connections.yaml reference
|
|
343
397
|
│ ├── operations.md # operations.yaml reference
|
|
398
|
+
│ ├── execute.md # dbctl execute (ad-hoc SQL) reference
|
|
399
|
+
│ ├── tui.md # dbctl ui reference (keybindings, dialect SQL)
|
|
344
400
|
│ └── DESIGN.md # architecture and design decisions
|
|
345
401
|
└── dbctl/
|
|
346
402
|
├── cli.py # dynamic groups + per-op Click commands
|
|
@@ -354,7 +410,21 @@ dbctl/
|
|
|
354
410
|
├── reports.py # rich tables / json / csv / yaml + diff + copy/sync/validate rendering
|
|
355
411
|
├── audit.py # history.jsonl
|
|
356
412
|
├── runtime.py # opened_conn() ctx-mgr (tunnel+engine+healthcheck)
|
|
357
|
-
|
|
413
|
+
├── init.py # dbctl init wizard
|
|
414
|
+
└── ui/ # Textual TUI (`dbctl ui`)
|
|
415
|
+
├── app.py # DbctlApp: connection tree + tabbed workspace
|
|
416
|
+
├── session.py # per-connection tunnel+engine kept open across tab-runs
|
|
417
|
+
├── connection_tree.py # lazy schema browser (simple/normal + flat/grouped views)
|
|
418
|
+
├── grouping.py # "-" as a path separator -> compressed connection-name trie
|
|
419
|
+
├── schema.py # sqlalchemy.inspect() wrapper for the tree
|
|
420
|
+
├── editor_tab.py # SQL editor tab
|
|
421
|
+
├── operation_tab.py # operation-launcher tab
|
|
422
|
+
├── sql_templates.py # dialect-aware default SELECT + identifier quoting
|
|
423
|
+
├── screens.py # confirm / new-tab / operation-launcher modal screens
|
|
424
|
+
├── splitter.py # draggable pane-resize bars
|
|
425
|
+
├── tabs.py # shared tab base: resize, toolbar, status bar, loading indicator
|
|
426
|
+
├── results.py # DataTable rendering helpers
|
|
427
|
+
└── registry.py # tolerant connections/operations loading
|
|
358
428
|
```
|
|
359
429
|
|
|
360
430
|
## Development
|
|
@@ -363,7 +433,7 @@ dbctl/
|
|
|
363
433
|
uv sync --extra dev
|
|
364
434
|
make help # list all Makefile targets
|
|
365
435
|
make check # lint + unit tests (the pre-commit gate)
|
|
366
|
-
make test # unit tests (~
|
|
436
|
+
make test # unit tests (~270 tests, sqlite-backed, no docker)
|
|
367
437
|
make typecheck # mypy strict (pre-existing debt; non-blocking)
|
|
368
438
|
make smoke # docker compose up + dbctl doctor against the fleet
|
|
369
439
|
make build # wheel + sdist via uv
|