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.
Files changed (52) hide show
  1. {dbctl-0.5.3 → dbctl-0.6.2}/.dbctl/connections.yaml +38 -4
  2. dbctl-0.6.2/.dbctl/operations.yaml +179 -0
  3. {dbctl-0.5.3 → dbctl-0.6.2}/.github/workflows/ci.yml +5 -5
  4. dbctl-0.6.2/CHANGELOG.md +509 -0
  5. dbctl-0.6.2/Makefile +142 -0
  6. {dbctl-0.5.3 → dbctl-0.6.2}/PKG-INFO +58 -35
  7. {dbctl-0.5.3 → dbctl-0.6.2}/README.md +56 -32
  8. {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/cli.py +449 -47
  9. {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/config.py +171 -11
  10. {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/connections.py +26 -1
  11. {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/db.py +107 -20
  12. {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/init.py +69 -24
  13. dbctl-0.6.2/dbctl/multi.py +692 -0
  14. dbctl-0.6.2/dbctl/operations.py +131 -0
  15. {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/reports.py +94 -0
  16. {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/runtime.py +8 -1
  17. {dbctl-0.5.3 → dbctl-0.6.2}/docs/ACTION_OUTPUT.md +4 -2
  18. {dbctl-0.5.3 → dbctl-0.6.2}/docs/DESIGN.md +17 -17
  19. {dbctl-0.5.3 → dbctl-0.6.2}/docs/SESSION_STATE.md +6 -3
  20. {dbctl-0.5.3 → dbctl-0.6.2}/docs/connections.md +92 -15
  21. dbctl-0.6.2/docs/logo.png +0 -0
  22. dbctl-0.6.2/docs/logo_small.png +0 -0
  23. {dbctl-0.5.3 → dbctl-0.6.2}/docs/operations.md +228 -40
  24. {dbctl-0.5.3 → dbctl-0.6.2}/docs/tutorial.md +34 -31
  25. {dbctl-0.5.3 → dbctl-0.6.2}/pyproject.toml +14 -5
  26. {dbctl-0.5.3 → dbctl-0.6.2}/seed/mssql.sql +8 -8
  27. {dbctl-0.5.3 → dbctl-0.6.2}/seed/mysql.sql +9 -9
  28. {dbctl-0.5.3 → dbctl-0.6.2}/seed/postgres.sql +10 -10
  29. {dbctl-0.5.3 → dbctl-0.6.2}/tests/test_connections_loader.py +1 -1
  30. dbctl-0.6.2/tests/test_regressions.py +1148 -0
  31. {dbctl-0.5.3 → dbctl-0.6.2}/tests/test_smoke.py +19 -13
  32. {dbctl-0.5.3 → dbctl-0.6.2}/uv.lock +3 -168
  33. dbctl-0.5.3/.dbctl/operations.yaml +0 -110
  34. dbctl-0.5.3/CHANGELOG.md +0 -226
  35. dbctl-0.5.3/dbctl/multi.py +0 -53
  36. dbctl-0.5.3/dbctl/operations.py +0 -40
  37. dbctl-0.5.3/tests/test_regressions.py +0 -210
  38. {dbctl-0.5.3 → dbctl-0.6.2}/.github-local/ci.yml +0 -0
  39. {dbctl-0.5.3 → dbctl-0.6.2}/.gitignore +0 -0
  40. {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/__init__.py +0 -0
  41. {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/__main__.py +0 -0
  42. {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/audit.py +0 -0
  43. {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/execute.py +0 -0
  44. {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/tunnels/__init__.py +0 -0
  45. {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/tunnels/base.py +0 -0
  46. {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/tunnels/direct.py +0 -0
  47. {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/tunnels/k8s.py +0 -0
  48. {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/tunnels/ssh.py +0 -0
  49. {dbctl-0.5.3 → dbctl-0.6.2}/dbctl/tunnels/ssm.py +0 -0
  50. {dbctl-0.5.3 → dbctl-0.6.2}/docker-compose.yml +0 -0
  51. {dbctl-0.5.3 → dbctl-0.6.2}/tests/test_bastion_tags.py +0 -0
  52. {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, quota_daily, is_active FROM users ORDER BY quota_daily DESC LIMIT 10"
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, quota_daily, is_active FROM users ORDER BY quota_daily DESC LIMIT 10"
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, quota_daily, is_active FROM dbo.users ORDER BY quota_daily DESC"
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.11" in pyproject.toml
66
- python-version: ["3.11", "3.12", "3.13"]
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: macos + 3.11 + 3.13 stay, but skip 3.13 on
70
- # mac to keep minutes in check (Linux covers the linux/3.13 case).
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.11"
72
+ python-version: "3.12"
73
73
  steps:
74
74
  - uses: actions/checkout@v4
75
75
  - name: Install uv