dbctl 0.5.3__tar.gz → 0.6.2__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.5.3 → dbctl-0.6.2}/.dbctl/connections.yaml +38 -4
- dbctl-0.6.2/.dbctl/operations.yaml +179 -0
- {dbctl-0.5.3 → dbctl-0.6.2}/.github/workflows/ci.yml +5 -5
- dbctl-0.6.2/CHANGELOG.md +509 -0
- dbctl-0.6.2/Makefile +142 -0
- {dbctl-0.5.3 → dbctl-0.6.2}/PKG-INFO +58 -35
- {dbctl-0.5.3 → dbctl-0.6.2}/README.md +56 -32
- {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/cli.py +449 -47
- {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/config.py +171 -11
- {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/connections.py +26 -1
- {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/db.py +107 -20
- {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/init.py +69 -24
- dbctl-0.6.2/dbctl/multi.py +692 -0
- dbctl-0.6.2/dbctl/operations.py +131 -0
- {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/reports.py +94 -0
- {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/runtime.py +8 -1
- {dbctl-0.5.3 → dbctl-0.6.2}/docs/ACTION_OUTPUT.md +4 -2
- {dbctl-0.5.3 → dbctl-0.6.2}/docs/DESIGN.md +17 -17
- {dbctl-0.5.3 → dbctl-0.6.2}/docs/SESSION_STATE.md +6 -3
- {dbctl-0.5.3 → dbctl-0.6.2}/docs/connections.md +92 -15
- dbctl-0.6.2/docs/logo.png +0 -0
- dbctl-0.6.2/docs/logo_small.png +0 -0
- {dbctl-0.5.3 → dbctl-0.6.2}/docs/operations.md +228 -40
- {dbctl-0.5.3 → dbctl-0.6.2}/docs/tutorial.md +34 -31
- {dbctl-0.5.3 → dbctl-0.6.2}/pyproject.toml +14 -5
- {dbctl-0.5.3 → dbctl-0.6.2}/seed/mssql.sql +8 -8
- {dbctl-0.5.3 → dbctl-0.6.2}/seed/mysql.sql +9 -9
- {dbctl-0.5.3 → dbctl-0.6.2}/seed/postgres.sql +10 -10
- {dbctl-0.5.3 → dbctl-0.6.2}/tests/test_connections_loader.py +1 -1
- dbctl-0.6.2/tests/test_regressions.py +1148 -0
- {dbctl-0.5.3 → dbctl-0.6.2}/tests/test_smoke.py +19 -13
- {dbctl-0.5.3 → dbctl-0.6.2}/uv.lock +3 -168
- dbctl-0.5.3/.dbctl/operations.yaml +0 -110
- dbctl-0.5.3/CHANGELOG.md +0 -226
- dbctl-0.5.3/dbctl/multi.py +0 -53
- dbctl-0.5.3/dbctl/operations.py +0 -40
- dbctl-0.5.3/tests/test_regressions.py +0 -210
- {dbctl-0.5.3 → dbctl-0.6.2}/.github-local/ci.yml +0 -0
- {dbctl-0.5.3 → dbctl-0.6.2}/.gitignore +0 -0
- {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/__init__.py +0 -0
- {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/__main__.py +0 -0
- {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/audit.py +0 -0
- {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/execute.py +0 -0
- {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/tunnels/__init__.py +0 -0
- {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/tunnels/base.py +0 -0
- {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/tunnels/direct.py +0 -0
- {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/tunnels/k8s.py +0 -0
- {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/tunnels/ssh.py +0 -0
- {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/tunnels/ssm.py +0 -0
- {dbctl-0.5.3 → dbctl-0.6.2}/docker-compose.yml +0 -0
- {dbctl-0.5.3 → dbctl-0.6.2}/tests/test_bastion_tags.py +0 -0
- {dbctl-0.5.3 → dbctl-0.6.2}/tests/test_k8s_tunnel.py +0 -0
|
@@ -23,7 +23,7 @@ connections:
|
|
|
23
23
|
FROM pg_stat_user_tables
|
|
24
24
|
ORDER BY n_live_tup DESC LIMIT 10
|
|
25
25
|
- name: top_users
|
|
26
|
-
query: "SELECT name,
|
|
26
|
+
query: "SELECT name, credits_daily, is_active FROM users ORDER BY credits_daily DESC LIMIT 10"
|
|
27
27
|
safety:
|
|
28
28
|
confirm: true
|
|
29
29
|
read_only: false
|
|
@@ -41,7 +41,7 @@ connections:
|
|
|
41
41
|
healthcheck: { query: "SELECT 1" }
|
|
42
42
|
info:
|
|
43
43
|
- name: top_users
|
|
44
|
-
query: "SELECT name,
|
|
44
|
+
query: "SELECT name, credits_daily, is_active FROM users ORDER BY credits_daily DESC LIMIT 10"
|
|
45
45
|
safety:
|
|
46
46
|
confirm: true
|
|
47
47
|
read_only: false
|
|
@@ -62,7 +62,7 @@ connections:
|
|
|
62
62
|
healthcheck: { query: "SELECT 1" }
|
|
63
63
|
info:
|
|
64
64
|
- name: top_users
|
|
65
|
-
query: "SELECT TOP 10 name,
|
|
65
|
+
query: "SELECT TOP 10 name, credits_daily, is_active FROM dbo.users ORDER BY credits_daily DESC"
|
|
66
66
|
safety:
|
|
67
67
|
confirm: true
|
|
68
68
|
read_only: true
|
|
@@ -191,4 +191,38 @@ connections:
|
|
|
191
191
|
safety:
|
|
192
192
|
confirm: true
|
|
193
193
|
read_only: true
|
|
194
|
-
allowed_operations: []
|
|
194
|
+
allowed_operations: []
|
|
195
|
+
# --------------------------------------------------------------------- #
|
|
196
|
+
# Full SQLAlchemy URL + Windows SSO reference templates.
|
|
197
|
+
# --------------------------------------------------------------------- #
|
|
198
|
+
|
|
199
|
+
# Full SQLAlchemy connection string — use when you need ODBC-specific
|
|
200
|
+
# query params, Azure AD auth, or any scheme dbctl's individual fields
|
|
201
|
+
# don't model. When `url:` is set, the tunnel's local bind is NOT
|
|
202
|
+
# injected — the URL's own host:port wins.
|
|
203
|
+
azure-sql:
|
|
204
|
+
description: "REFERENCE: Azure SQL via full SQLAlchemy URL (edit before using)"
|
|
205
|
+
aliases: []
|
|
206
|
+
type: direct
|
|
207
|
+
url: "mssql+pyodbc://app_admin@myserver.database.windows.net:1433/app?driver=ODBC+Driver+18+for+SQL+Server&Encrypt=yes&Authentication=ActiveDirectoryPassword"
|
|
208
|
+
direct: { host: myserver.database.windows.net, port: 1433 }
|
|
209
|
+
healthcheck: { query: "SELECT 1", timeout_seconds: 10 }
|
|
210
|
+
safety:
|
|
211
|
+
confirm: true
|
|
212
|
+
read_only: true
|
|
213
|
+
|
|
214
|
+
# SQL Server with Windows Integrated Security (Trusted_Connection=yes).
|
|
215
|
+
# No username / password / password_env / prompt needed — the ODBC
|
|
216
|
+
# driver uses the current Windows user's credentials.
|
|
217
|
+
mssql-sso:
|
|
218
|
+
description: "REFERENCE: SQL Server with Windows SSO (edit before using)"
|
|
219
|
+
aliases: []
|
|
220
|
+
type: direct
|
|
221
|
+
driver: mssql+pyodbc
|
|
222
|
+
database: app
|
|
223
|
+
windows_sso: true
|
|
224
|
+
direct: { host: 10.0.0.5, port: 1433 }
|
|
225
|
+
healthcheck: { query: "SELECT 1", timeout_seconds: 5 }
|
|
226
|
+
safety:
|
|
227
|
+
confirm: true
|
|
228
|
+
read_only: true
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# Sample dbctl operations.
|
|
2
|
+
# Each operation is a parameterised SQL block declaring its inputs; the CLI
|
|
3
|
+
# builds one Click option per parameter (positional + keyword).
|
|
4
|
+
|
|
5
|
+
operations:
|
|
6
|
+
add-user:
|
|
7
|
+
description: "Create or update an application user (Daily credits by default)"
|
|
8
|
+
scope: single
|
|
9
|
+
mode: execute
|
|
10
|
+
confirm: true
|
|
11
|
+
parameters:
|
|
12
|
+
- { name: name, type: string, required: true, position: 1, description: "Unique user name" }
|
|
13
|
+
- { name: credits, type: integer, required: true, position: 2, description: "Daily credits (used as daily limit)" }
|
|
14
|
+
- { name: type, type: string, default: "Daily", position: 3, description: "Account type" }
|
|
15
|
+
sql: |
|
|
16
|
+
INSERT INTO users (name, credits_daily, credits_yearly, type)
|
|
17
|
+
VALUES ($name, $credits, $credits * 365, $type)
|
|
18
|
+
ON CONFLICT (name) DO UPDATE
|
|
19
|
+
SET credits_daily = EXCLUDED.credits_daily,
|
|
20
|
+
credits_yearly = EXCLUDED.credits_yearly,
|
|
21
|
+
type = EXCLUDED.type,
|
|
22
|
+
updated_at = NOW()
|
|
23
|
+
|
|
24
|
+
increase-credits:
|
|
25
|
+
description: "Increase a user's daily and yearly credits by a percentage"
|
|
26
|
+
scope: single
|
|
27
|
+
mode: execute
|
|
28
|
+
confirm: true
|
|
29
|
+
parameters:
|
|
30
|
+
- { name: name, type: string, required: true, position: 1, description: "User to bump" }
|
|
31
|
+
- { name: pct, type: float, required: true, position: 2, description: "Percentage increase (e.g. 10 = +10%)" }
|
|
32
|
+
sql: |
|
|
33
|
+
UPDATE users
|
|
34
|
+
SET credits_daily = (credits_daily * (1 + $pct / 100.0))::integer,
|
|
35
|
+
credits_yearly = (credits_yearly * (1 + $pct / 100.0))::integer,
|
|
36
|
+
updated_at = NOW()
|
|
37
|
+
WHERE name = $name
|
|
38
|
+
|
|
39
|
+
list-users:
|
|
40
|
+
description: "List users (top N)"
|
|
41
|
+
scope: single
|
|
42
|
+
mode: fetch
|
|
43
|
+
output: table
|
|
44
|
+
parameters:
|
|
45
|
+
- { name: limit, type: integer, default: 10, position: 1 }
|
|
46
|
+
sql: |
|
|
47
|
+
SELECT name, credits_daily, credits_yearly, type, is_active
|
|
48
|
+
FROM users
|
|
49
|
+
ORDER BY credits_daily DESC
|
|
50
|
+
LIMIT $limit
|
|
51
|
+
|
|
52
|
+
find-user:
|
|
53
|
+
description: "Find a user by name prefix"
|
|
54
|
+
scope: single
|
|
55
|
+
mode: fetch
|
|
56
|
+
parameters:
|
|
57
|
+
- { name: prefix, type: string, required: true, position: 1 }
|
|
58
|
+
sql: |
|
|
59
|
+
SELECT name, credits_daily, is_active FROM users WHERE name ILIKE $prefix || '%'
|
|
60
|
+
|
|
61
|
+
report-logs:
|
|
62
|
+
description: "Summarise logs by level between two dates"
|
|
63
|
+
scope: single
|
|
64
|
+
mode: fetch
|
|
65
|
+
output: table
|
|
66
|
+
parameters:
|
|
67
|
+
- { name: since, type: string, required: true, position: 1, description: "ISO date (e.g. 2025-01-01)" }
|
|
68
|
+
- { name: until, type: string, required: true, position: 2, description: "ISO date" }
|
|
69
|
+
sql: |
|
|
70
|
+
SELECT level, COUNT(*) AS events, MIN(created_at) AS first_seen, MAX(created_at) AS last_seen
|
|
71
|
+
FROM logs
|
|
72
|
+
WHERE created_at >= $since::timestamp
|
|
73
|
+
AND created_at < $until::timestamp
|
|
74
|
+
GROUP BY level
|
|
75
|
+
ORDER BY events DESC
|
|
76
|
+
|
|
77
|
+
# --------------------------------------------------------------------------- #
|
|
78
|
+
# multi-database operations - invoked as `dbctl diff A B --op <name>`
|
|
79
|
+
# --------------------------------------------------------------------------- #
|
|
80
|
+
user-count:
|
|
81
|
+
description: "Compare user counts between two databases"
|
|
82
|
+
scope: multi
|
|
83
|
+
mode: diff
|
|
84
|
+
roles: [src, trg]
|
|
85
|
+
queries:
|
|
86
|
+
src: "SELECT 'users' AS t, COUNT(*) AS n FROM users"
|
|
87
|
+
trg: "SELECT 'users' AS t, COUNT(*) AS n FROM users"
|
|
88
|
+
diff:
|
|
89
|
+
key: [t]
|
|
90
|
+
show: [n]
|
|
91
|
+
|
|
92
|
+
compare-credits:
|
|
93
|
+
description: "Side-by-side credits summary across two databases"
|
|
94
|
+
scope: multi
|
|
95
|
+
mode: diff
|
|
96
|
+
roles: [src, trg]
|
|
97
|
+
parameters:
|
|
98
|
+
- { name: period, type: string, default: "Daily", position: 1 }
|
|
99
|
+
queries:
|
|
100
|
+
src: |
|
|
101
|
+
SELECT period AS t, COUNT(*) AS n_rows, SUM(limit_value) AS total_limit
|
|
102
|
+
FROM credits JOIN users ON credits.user_id = users.id
|
|
103
|
+
WHERE period = $period GROUP BY period
|
|
104
|
+
trg: |
|
|
105
|
+
SELECT period AS t, COUNT(*) AS n_rows, SUM(limit_value) AS total_limit
|
|
106
|
+
FROM credits JOIN users ON credits.user_id = users.id
|
|
107
|
+
WHERE period = $period GROUP BY period
|
|
108
|
+
diff:
|
|
109
|
+
key: [t]
|
|
110
|
+
show: [n_rows, total_limit]
|
|
111
|
+
|
|
112
|
+
# compare row counts of every user table side-by-side.
|
|
113
|
+
# Uses the `table_counts` strategy so we don't have to spell out the
|
|
114
|
+
# SELECT 'users' AS t, COUNT(*) ... boilerplate per table.
|
|
115
|
+
# NOTE: list the tables explicitly — `["*"]` introspection is only
|
|
116
|
+
# supported by `copy`/`replay`, not by the `table_counts` diff strategy.
|
|
117
|
+
table-counts:
|
|
118
|
+
description: "Compare row counts of every declared table across two DBs"
|
|
119
|
+
scope: multi
|
|
120
|
+
mode: diff
|
|
121
|
+
roles: [src, trg]
|
|
122
|
+
diff:
|
|
123
|
+
strategy: table_counts
|
|
124
|
+
tables: [users, credits, usage, logs]
|
|
125
|
+
key: [t]
|
|
126
|
+
show: [n]
|
|
127
|
+
|
|
128
|
+
# bulk-copy the `users` table from src to trg in batches of 1000 rows.
|
|
129
|
+
# `--on-conflict truncate` is the safe refresh path; `--dry-run` simulates.
|
|
130
|
+
copy-users:
|
|
131
|
+
description: "Bulk-copy users table src → trg (batches of 1000)"
|
|
132
|
+
scope: multi
|
|
133
|
+
mode: copy
|
|
134
|
+
roles: [src, trg]
|
|
135
|
+
copy_spec:
|
|
136
|
+
batch_size: 1000
|
|
137
|
+
tables: [users] # omit `tables:` to introspect all src tables
|
|
138
|
+
on_conflict: error # error | skip | update | truncate
|
|
139
|
+
|
|
140
|
+
# converge the `users` table on trg to match src: insert missing rows,
|
|
141
|
+
# update differing rows, and (with --delete-extras) delete trg-only rows.
|
|
142
|
+
# Both queries MUST return the same column shape; `key` identifies rows.
|
|
143
|
+
sync-users:
|
|
144
|
+
description: "Converge trg users to match src (insert + update; --delete-extras to also remove)"
|
|
145
|
+
scope: multi
|
|
146
|
+
mode: sync
|
|
147
|
+
roles: [src, trg]
|
|
148
|
+
queries:
|
|
149
|
+
src: "SELECT id, name, credits_daily, credits_yearly, type, is_active FROM users"
|
|
150
|
+
trg: "SELECT id, name, credits_daily, credits_yearly, type, is_active FROM users"
|
|
151
|
+
sync_spec:
|
|
152
|
+
key: [id]
|
|
153
|
+
target_table: users
|
|
154
|
+
delete_extras: false # CLI `--delete-extras` overrides per run
|
|
155
|
+
|
|
156
|
+
# detect schema drift: compare columns + types for each table present in
|
|
157
|
+
# both schemas. Leave `tables:` null to introspect the intersection.
|
|
158
|
+
validate-schema:
|
|
159
|
+
description: "Detect column/type drift between src and trg schemas"
|
|
160
|
+
scope: multi
|
|
161
|
+
mode: validate
|
|
162
|
+
roles: [src, trg]
|
|
163
|
+
validate_spec:
|
|
164
|
+
tables: [users, credits, usage, logs]
|
|
165
|
+
# include: [] # restrict to these column names
|
|
166
|
+
# exclude: [created_at] # drop these column names from the diff
|
|
167
|
+
|
|
168
|
+
# copy with a per-row Python transform. `identity` is a no-op; to mutate
|
|
169
|
+
# rows set `transform:` to a `package.module:callable` import path that
|
|
170
|
+
# takes a row dict and returns a dict.
|
|
171
|
+
replay-users:
|
|
172
|
+
description: "Copy users src → trg with a per-row transform (identity = no-op)"
|
|
173
|
+
scope: multi
|
|
174
|
+
mode: replay
|
|
175
|
+
roles: [src, trg]
|
|
176
|
+
replay_spec:
|
|
177
|
+
tables: [users]
|
|
178
|
+
batch_size: 500
|
|
179
|
+
transform: identity
|
|
@@ -62,14 +62,14 @@ jobs:
|
|
|
62
62
|
strategy:
|
|
63
63
|
fail-fast: false
|
|
64
64
|
matrix:
|
|
65
|
-
# requires-python = ">=3.
|
|
66
|
-
python-version: ["3.
|
|
65
|
+
# requires-python = ">=3.12" in pyproject.toml
|
|
66
|
+
python-version: ["3.12", "3.13"]
|
|
67
67
|
os: [ubuntu-latest, macos-latest]
|
|
68
68
|
exclude:
|
|
69
|
-
# Trim the matrix:
|
|
70
|
-
#
|
|
69
|
+
# Trim the matrix: skip 3.13 on mac to keep minutes in check
|
|
70
|
+
# (Linux covers the linux/3.13 case).
|
|
71
71
|
- os: macos-latest
|
|
72
|
-
python-version: "3.
|
|
72
|
+
python-version: "3.12"
|
|
73
73
|
steps:
|
|
74
74
|
- uses: actions/checkout@v4
|
|
75
75
|
- name: Install uv
|