@carllee1983/dbcli 1.6.0 → 1.8.0
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.
- package/CHANGELOG.md +30 -0
- package/README.md +58 -0
- package/README.zh-TW.md +54 -0
- package/assets/SKILL.md +79 -15
- package/assets/reference.md +327 -7
- package/assets/snippets/.keep +0 -0
- package/assets/snippets/README.md +18 -0
- package/assets/snippets/diag/cache-hit.mysql.sql +19 -0
- package/assets/snippets/diag/cache-hit.postgres.sql +18 -0
- package/assets/snippets/diag/connections.mysql.sql +16 -0
- package/assets/snippets/diag/connections.postgres.sql +16 -0
- package/assets/snippets/diag/db-size.mysql.sql +10 -0
- package/assets/snippets/diag/db-size.postgres.sql +9 -0
- package/assets/snippets/diag/index-usage.mysql.sql +15 -0
- package/assets/snippets/diag/index-usage.postgres.sql +14 -0
- package/assets/snippets/diag/locks.mysql.sql +14 -0
- package/assets/snippets/diag/locks.postgres.sql +15 -0
- package/assets/snippets/diag/long-running.mysql.sql +14 -0
- package/assets/snippets/diag/long-running.postgres.sql +19 -0
- package/assets/snippets/diag/missing-indexes.mysql.sql +13 -0
- package/assets/snippets/diag/missing-indexes.postgres.sql +15 -0
- package/assets/snippets/diag/table-sizes.mysql.sql +14 -0
- package/assets/snippets/diag/table-sizes.postgres.sql +13 -0
- package/dist/cli.mjs +14371 -1553
- package/package.json +3 -2
package/assets/reference.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Companion to [SKILL.md](SKILL.md). Exhaustive flags, copy-paste examples, `shell`, `completion`, `upgrade`, `migrate` DDL, and extended MongoDB examples.
|
|
4
4
|
|
|
5
|
+
For cross-engine support status, see `docs/feature-matrix.md` in the repository.
|
|
6
|
+
|
|
5
7
|
## Commands
|
|
6
8
|
|
|
7
9
|
### init
|
|
@@ -19,6 +21,14 @@ dbcli init --system mongodb --uri "mongodb://user:pass@host:27017/mydb?authSourc
|
|
|
19
21
|
dbcli init --system mongodb --host localhost --port 27017 --user admin --password secret --name mydb
|
|
20
22
|
dbcli init --system mongodb --host localhost --port 27017 --name mydb # No auth
|
|
21
23
|
|
|
24
|
+
# Redis (database = logical DB index)
|
|
25
|
+
dbcli init --system redis --host localhost --port 6379
|
|
26
|
+
dbcli init --system redis --host localhost --port 6379 --password secret --name 0
|
|
27
|
+
|
|
28
|
+
# Elasticsearch
|
|
29
|
+
dbcli init --system elasticsearch --host localhost --port 9200 --user elastic --password changeme
|
|
30
|
+
dbcli init --system elasticsearch --cloud-id "myCluster:dXMtZWFzdC0xLmF3..." --api-key "<base64>"
|
|
31
|
+
|
|
22
32
|
# Multi-connection (v2 format)
|
|
23
33
|
dbcli init --conn-name staging --env-file .env.staging # Named connection with custom env file
|
|
24
34
|
dbcli init --conn-name prod --env-file .env.production --use-env-refs --skip-test
|
|
@@ -30,6 +40,10 @@ dbcli init --rename staging:production # Rename a connection
|
|
|
30
40
|
|
|
31
41
|
**MongoDB-specific options:** `--uri <uri>` (full connection URI), `--auth-source <db>` (auth database, default: `admin` when user/password set)
|
|
32
42
|
|
|
43
|
+
**Elasticsearch-specific options:** `--cloud-id <id>` (Elastic Cloud), `--api-key <key>` (ApiKey auth). Other ES fields (`nodes[]`, `protocol`, `caPath`, `rejectUnauthorized`) can be edited directly in `.dbcli`.
|
|
44
|
+
|
|
45
|
+
**Redis note:** the `database` (or `--name`) field is the logical DB index (`"0"` … `"15"`), not a database name.
|
|
46
|
+
|
|
33
47
|
**Multi-connection:** Using `--conn-name` or `--env-file` creates a v2 config with named connections. Each connection can have its own env file and permission level. Existing v1 configs are automatically imported as the `default` connection when upgrading.
|
|
34
48
|
|
|
35
49
|
> **AI agent note on `--use-env-refs`:** If an existing `.dbcli` config contains `{"$env": "DB_HOST"}` style references, the connection values are read from environment variables at runtime. Do NOT re-run `init` to replace these references with actual values — the env-ref format is intentional for CI/CD and multi-environment setups.
|
|
@@ -55,16 +69,19 @@ dbcli list --use prod
|
|
|
55
69
|
|
|
56
70
|
### list
|
|
57
71
|
|
|
58
|
-
List all tables (SQL)
|
|
72
|
+
List all tables (SQL), collections (MongoDB), keys (Redis), or indices (Elasticsearch).
|
|
59
73
|
|
|
60
74
|
```bash
|
|
61
75
|
dbcli list
|
|
62
76
|
dbcli list --format json
|
|
77
|
+
dbcli list --include-system # Elasticsearch: include `.system` indices
|
|
63
78
|
```
|
|
64
79
|
|
|
65
80
|
**Permission:** query-only+
|
|
66
81
|
|
|
67
|
-
> **MongoDB:** Lists collections with estimated document count
|
|
82
|
+
> **MongoDB:** Lists collections with estimated document count.
|
|
83
|
+
> **Redis:** Returns up to 100 000 keys via `SCAN MATCH * COUNT 1000`. The header reads `Keys in db <n> (redis):` where `<n>` is the logical DB index.
|
|
84
|
+
> **Elasticsearch:** Returns indices with `documentCount` from `/_stats/docs`; aliases are tagged separately. System indices (names starting with `.`) are hidden unless `--include-system` is passed.
|
|
68
85
|
|
|
69
86
|
### schema
|
|
70
87
|
|
|
@@ -88,6 +105,9 @@ dbcli schema --use prod # Scan prod DB; saves to .dbcli/schemas/prod
|
|
|
88
105
|
|
|
89
106
|
**Schema storage (v1.4+):** Schema is persisted as layered files under `.dbcli/schemas/`. With v2 multi-connection config each connection gets its own subdirectory (`.dbcli/schemas/<connection>/`). Run `dbcli schema --use <connection>` once per connection before querying it — otherwise `schema <table>` may return data from the wrong connection's cache.
|
|
90
107
|
|
|
108
|
+
> **Redis:** `schema <key>` is required (no full scan). The output exposes `type`, `ttl`, `size`, and a small `sample` (e.g. first 5 hash keys). `--reset` / `--refresh` are rejected — Redis caches no schema.
|
|
109
|
+
> **Elasticsearch:** `schema [index]` flattens the `_mapping` properties (nested `a.b.c`) and emits each `.fields` multi-field as a separate column (e.g. `text` + `text.keyword`). Full scan iterates all non-system indices and stores per-connection caches alongside SQL engines.
|
|
110
|
+
|
|
91
111
|
### query
|
|
92
112
|
|
|
93
113
|
Execute SQL query (MySQL/PostgreSQL/MariaDB) or JSON filter/pipeline (MongoDB).
|
|
@@ -104,16 +124,158 @@ dbcli query '{"age": {"$gt": 18}}' --collection users --format json
|
|
|
104
124
|
|
|
105
125
|
# MongoDB: aggregation pipeline
|
|
106
126
|
dbcli query '[{"$match": {"status": "active"}}, {"$group": {"_id": "$role", "count": {"$sum": 1}}}]' --collection users
|
|
127
|
+
|
|
128
|
+
# Redis: any whitelisted command (permission-gated by command)
|
|
129
|
+
dbcli query "GET session:abc"
|
|
130
|
+
dbcli query "HGETALL user:42" --format json
|
|
131
|
+
dbcli query "SCAN 0 MATCH user:* COUNT 100"
|
|
132
|
+
dbcli query "SET feature:flag enabled" # requires read-write+
|
|
133
|
+
dbcli query "DEL stale:key" # requires data-admin+
|
|
134
|
+
|
|
135
|
+
# Elasticsearch: DSL body or Lucene q-string
|
|
136
|
+
dbcli query '{"query":{"match":{"status":"active"}}}' --collection orders
|
|
137
|
+
dbcli query 'status:active AND amount:>100' --index orders --limit 50
|
|
107
138
|
```
|
|
108
139
|
|
|
109
|
-
**Options:** `--format <table|json|csv>`, `--limit <number>`, `--no-limit`, `--collection <name>` (MongoDB
|
|
110
|
-
**Permission:** query-only+
|
|
140
|
+
**Options:** `--format <table|json|csv>`, `--limit <number>`, `--no-limit`, `--collection <name>` (MongoDB / Elasticsearch), `--index <name>` (Elasticsearch alias for `--collection`)
|
|
141
|
+
**Permission:** query-only+ (Redis: per-command; Elasticsearch: per HTTP method/path)
|
|
111
142
|
|
|
112
143
|
> **MongoDB notes:**
|
|
113
144
|
> - SQL syntax is rejected — use JSON object (filter) or JSON array (pipeline)
|
|
114
145
|
> - `--collection <name>` is required
|
|
115
146
|
> - Auto-limit does not apply; use `$limit` in your pipeline if needed
|
|
116
147
|
|
|
148
|
+
> **Redis notes:**
|
|
149
|
+
> - The first token must be an allow-listed command (`GET`/`SET`/`HGET`/`HSET`/`DEL`/...). Unknown commands are refused.
|
|
150
|
+
> - Permission tier is derived from the command (read → `query-only`, write → `read-write`, delete → `data-admin`, `KEYS`/`FLUSHDB`/`CONFIG`/... → `admin`).
|
|
151
|
+
> - Output is always shaped into rows: scalar replies become `{value: ...}`; arrays become indexed rows; `HGETALL` is folded into a single object.
|
|
152
|
+
|
|
153
|
+
> **Elasticsearch notes:**
|
|
154
|
+
> - `--collection` (or `--index`) is required.
|
|
155
|
+
> - A body that begins with `{` is sent as DSL via `POST /<index>/_search`; otherwise the value is URL-encoded into `?q=...` (Lucene query string) via `GET`.
|
|
156
|
+
> - Hits are flattened: each result row contains `_id` plus dotted-path fields from `_source`. Pass `--format json` to keep nested structures readable.
|
|
157
|
+
> - Query-only mode caps at 1000 hits; `--no-limit` is internally capped at 10 000 (use saved searches / `search_after` for deeper pagination).
|
|
158
|
+
|
|
159
|
+
### q
|
|
160
|
+
|
|
161
|
+
Run a saved query snippet by `@name`. Snippets are parameterised SELECT/WITH statements resolved from three layers, with **local > shared > builtin** precedence (a local file always shadows shared and builtin variants of the same key):
|
|
162
|
+
|
|
163
|
+
- `builtin` — bundled with dbcli (e.g. `@diag/*`); read-only at runtime.
|
|
164
|
+
- `.dbcli-shared/queries/` — committed, team-shared.
|
|
165
|
+
- `.dbcli/queries/` — gitignored, personal override.
|
|
166
|
+
|
|
167
|
+
Engine variants (`name.postgres.sql` / `name.mysql.sql`) at the same layer are merged; the variant matching the active connection's engine is selected at execution time.
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
dbcli q @dau # run with declared defaults
|
|
171
|
+
dbcli q @dau --param days=30 --format json # override a param
|
|
172
|
+
dbcli q @analytics/revenue --param-file params.json
|
|
173
|
+
dbcli q @dau --dry-run # show final SQL + bind values
|
|
174
|
+
dbcli q @dau --no-limit # disable size guard wrap
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Options:**
|
|
178
|
+
- `--format <table|json|csv>` — output format (default: `table`)
|
|
179
|
+
- `--param <key=value>` — pass a parameter (repeatable)
|
|
180
|
+
- `--param-file <path>` — JSON object whose keys are param names
|
|
181
|
+
- `--no-limit` — skip the `SELECT * FROM (…) AS _dbcli_guard LIMIT 1000` wrap
|
|
182
|
+
- `--dry-run` — print the bound SQL + values without executing
|
|
183
|
+
- `--use <name>` — pick a v2 named connection
|
|
184
|
+
|
|
185
|
+
**Permission:** query-only+
|
|
186
|
+
|
|
187
|
+
#### Snippet file format
|
|
188
|
+
|
|
189
|
+
Each `.sql` file is plain SQL with optional YAML frontmatter inside a leading `-- ---` block. Lines outside frontmatter form the SQL body.
|
|
190
|
+
|
|
191
|
+
```sql
|
|
192
|
+
-- ---
|
|
193
|
+
-- name: DAU
|
|
194
|
+
-- description: Daily Active Users
|
|
195
|
+
-- engine: postgres # or [postgres, mysql]
|
|
196
|
+
-- params:
|
|
197
|
+
-- days:
|
|
198
|
+
-- type: int # int | string | float | bool | date | datetime
|
|
199
|
+
-- default: 7
|
|
200
|
+
-- required: false
|
|
201
|
+
-- description: lookback window in days
|
|
202
|
+
-- enum: [7, 30, 90]
|
|
203
|
+
-- tags: [analytics]
|
|
204
|
+
-- ---
|
|
205
|
+
SELECT COUNT(DISTINCT user_id) AS dau
|
|
206
|
+
FROM events
|
|
207
|
+
WHERE created_at > NOW() - (:days || ' days')::interval;
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Param placeholders use `:name`. They are rewritten to `$1, $2, …` (Postgres) or `?, ?, …` (MySQL) at execution time and passed as bind values — string interpolation is never used.
|
|
211
|
+
|
|
212
|
+
#### Param type coercion
|
|
213
|
+
|
|
214
|
+
| Declared `type` | Accepts |
|
|
215
|
+
|-----------------|---------|
|
|
216
|
+
| `int` | integer literal |
|
|
217
|
+
| `float` | decimal literal |
|
|
218
|
+
| `bool` | `true` / `false` / `1` / `0` / `yes` / `no` |
|
|
219
|
+
| `string` | any value |
|
|
220
|
+
| `date` | `YYYY-MM-DD` |
|
|
221
|
+
| `datetime` | ISO 8601 |
|
|
222
|
+
|
|
223
|
+
`enum` (optional) restricts the accepted values; mismatch is a hard error. CLI `--param` overrides `--param-file`, which overrides the snippet's `default`.
|
|
224
|
+
|
|
225
|
+
#### Safety invariants
|
|
226
|
+
|
|
227
|
+
- Only `SELECT` / `WITH` (CTE) bodies are accepted; `INSERT/UPDATE/DELETE/DDL` are rejected by the parser.
|
|
228
|
+
- Multi-statement bodies (`SELECT 1; DROP TABLE x`) are rejected.
|
|
229
|
+
- Template syntax inside SQL (`${…}`, `{{…}}`) is rejected — use `:name` parameters.
|
|
230
|
+
- Files exceeding 64 KiB are rejected.
|
|
231
|
+
- `--no-limit` is honoured only at the outermost level; nested subqueries are still wrapped by the size guard.
|
|
232
|
+
|
|
233
|
+
### queries
|
|
234
|
+
|
|
235
|
+
Manage saved snippets — discover, inspect, scaffold, and edit local copies. Mutating
|
|
236
|
+
subcommands (`delete`, `rename`, `copy`, `import`) only operate on the local layer
|
|
237
|
+
(`.dbcli/queries/`); builtin and shared snippets are never modified in place.
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
# Discovery / inspection
|
|
241
|
+
dbcli queries list # all snippets (builtin + shared + local)
|
|
242
|
+
dbcli queries list --tag analytics --engine postgres --format json
|
|
243
|
+
dbcli queries list --source local # only personal overrides
|
|
244
|
+
dbcli queries show @dau # frontmatter + SQL
|
|
245
|
+
dbcli queries show @dau --format json # MCP-shaped contract
|
|
246
|
+
|
|
247
|
+
# Authoring
|
|
248
|
+
dbcli queries new @new/sample # scaffold under .dbcli-shared/queries/
|
|
249
|
+
dbcli queries new @scratch --local # personal copy under .dbcli/queries/
|
|
250
|
+
dbcli queries edit @dau # opens local first, falls back to shared
|
|
251
|
+
dbcli queries edit @dau --shared # always edit the shared file
|
|
252
|
+
dbcli queries check # parse all snippets; exit 1 on errors
|
|
253
|
+
dbcli queries check --strict # promote warnings (e.g. missing engine) to errors
|
|
254
|
+
|
|
255
|
+
# Local-layer file management
|
|
256
|
+
dbcli queries delete @scratch # remove local file(s); prompts unless --force
|
|
257
|
+
dbcli queries delete @scratch --force
|
|
258
|
+
dbcli queries rename @scratch @analytics/dau # rename within local layer; preserves engine suffix
|
|
259
|
+
dbcli queries copy @diag/connections @my/connections # fork builtin/shared into local for editing
|
|
260
|
+
dbcli queries import ./hotfix.sql # import an external .sql into .dbcli/queries/
|
|
261
|
+
dbcli queries import ./hotfix.sql --as @diag/custom # override the snippet key
|
|
262
|
+
dbcli queries export @dau --output dau.sql # write snippet body to a file (stdout if omitted)
|
|
263
|
+
dbcli queries export @diag/connections --engine postgres # pick a variant when multiple engines exist
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**`list` options:** `--format <table|json|csv>`, `--tag <tag>`, `--engine <postgres|mysql>`, `--source <local|shared>`
|
|
267
|
+
**`show` options:** `--format <table|json|csv>`
|
|
268
|
+
**`new` options:** `--local`, `--edit`
|
|
269
|
+
**`edit` options:** `--shared`
|
|
270
|
+
**`check` options:** `--strict`, `--format <table|json|csv>`
|
|
271
|
+
**`delete` options:** `--force` (skip the confirmation prompt). Refuses to run if `@name` has no local copy.
|
|
272
|
+
**`rename` options:** `--force`. Both names must start with `@`. Engine suffix (`.postgres.sql` / `.mysql.sql`) is preserved; frontmatter `name:` is rewritten to the new key.
|
|
273
|
+
**`copy` options:** *(none)*. Copies every variant (all engines) of the source into the local layer; fails if the destination already has a local copy.
|
|
274
|
+
**`import` options:** `--force` (overwrite existing local file), `--as <name>` (override snippet key; defaults to filename without `.postgres` / `.mysql` suffix). Source must be `.sql` and parse cleanly (frontmatter validated, non-SELECT bodies rejected).
|
|
275
|
+
**`export` options:** `--output <path>` (write to file; otherwise stdout), `--engine <postgres|mysql>` (required when the snippet has multiple engine variants).
|
|
276
|
+
|
|
277
|
+
`--format json` on `list` and `show` emits a stable, machine-readable shape — designed to back a future MCP server without further refactor.
|
|
278
|
+
|
|
117
279
|
### insert
|
|
118
280
|
|
|
119
281
|
Insert data into a table.
|
|
@@ -338,13 +500,25 @@ dbcli migrate drop-enum status --execute --force
|
|
|
338
500
|
|
|
339
501
|
## MongoDB Support
|
|
340
502
|
|
|
341
|
-
MongoDB connections use a JSON-based query model instead of SQL.
|
|
503
|
+
MongoDB connections use a JSON-based query model instead of SQL. Treat MongoDB support as a narrower document-database path, not as a full SQL feature equivalent.
|
|
342
504
|
|
|
343
505
|
Atlas-style `mongodb+srv://` URIs are supported. `list` and `query` run against the database configured for the connection, and `query` always requires `--collection <name>`.
|
|
344
506
|
|
|
345
|
-
**Supported commands:** `init`, `list`, `query`, `
|
|
507
|
+
**Supported commands:** `init`, `use`, `list`, `schema`, `query`, `insert`, `update`, `delete`, `status`, `shell`, `doctor`, `upgrade`, `completion`
|
|
508
|
+
|
|
509
|
+
**Limited support:**
|
|
510
|
+
|
|
511
|
+
- `schema` samples collection documents to infer field names/types. It does not provide relational constraints, primary keys, foreign keys, or reliable index metadata.
|
|
512
|
+
- `query` accepts only JSON object filters or aggregation pipeline arrays and always requires `--collection <name>`.
|
|
513
|
+
- `insert` inserts one JSON document into the named collection.
|
|
514
|
+
- `update` accepts a JSON filter in `--where` or simple `key=value` conditions. If `--set` does not use MongoDB update operators such as `$set`, dbcli wraps it in `$set`.
|
|
515
|
+
- `delete` deletes all documents matching the JSON/simple filter.
|
|
516
|
+
- MongoDB write paths do not currently provide the same SQL dry-run, relational schema validation, or column-level blacklist filtering guarantees as SQL writes.
|
|
517
|
+
- `shell` blocks raw SQL for MongoDB; use `query <json> --collection <name>` inside the shell.
|
|
346
518
|
|
|
347
|
-
**Not supported (exit with error):** `
|
|
519
|
+
**Not supported (exit with error):** `q` saved-query execution, `export`, `diff`, `migrate`
|
|
520
|
+
|
|
521
|
+
**Not a supported MongoDB target:** `check` is designed for relational health checks and emits SQL-style checks.
|
|
348
522
|
|
|
349
523
|
### MongoDB-specific workflow
|
|
350
524
|
|
|
@@ -359,6 +533,11 @@ dbcli list --format json
|
|
|
359
533
|
dbcli query '{}' --collection orders --format json # All documents
|
|
360
534
|
dbcli query '{"status": "paid"}' --collection orders # Filter
|
|
361
535
|
dbcli query '[{"$match": {"status":"paid"}}, {"$count":"total"}]' --collection orders # Pipeline
|
|
536
|
+
|
|
537
|
+
# 4. Document writes (permission-gated; no SQL dry-run semantics)
|
|
538
|
+
dbcli insert orders --data '{"status":"paid","total":42}'
|
|
539
|
+
dbcli update orders --where '{"status":"pending"}' --set '{"status":"paid"}'
|
|
540
|
+
dbcli delete orders --where '{"status":"cancelled"}' --force
|
|
362
541
|
```
|
|
363
542
|
|
|
364
543
|
### Query syntax
|
|
@@ -369,3 +548,144 @@ dbcli query '[{"$match": {"status":"paid"}}, {"$count":"total"}]' --collection o
|
|
|
369
548
|
| Field filter | `'{"field": "value"}'` |
|
|
370
549
|
| Comparison | `'{"age": {"$gt": 18}}'` |
|
|
371
550
|
| Aggregation | `'[{"$match": {...}}, {"$group": {...}}]'` |
|
|
551
|
+
|
|
552
|
+
## Redis Support
|
|
553
|
+
|
|
554
|
+
Redis connections speak Redis commands rather than SQL. The adapter uses the `ioredis` driver and exposes a narrow, permission-gated surface.
|
|
555
|
+
|
|
556
|
+
**Supported commands:** `init`, `use`, `list`, `schema`, `query`, `status`, `doctor`, `upgrade`, `completion`
|
|
557
|
+
|
|
558
|
+
**Not supported (exit with error or unsupported error):** `insert`, `update`, `delete`, `export`, `check`, `diff`, `migrate`, `q` (saved queries), `shell`. For writes, run the equivalent Redis command via `query` — the same permission gate applies.
|
|
559
|
+
|
|
560
|
+
### Connection and configuration
|
|
561
|
+
|
|
562
|
+
- Required fields: `system: redis`, `host`, `port`. `password` and `database` are optional.
|
|
563
|
+
- `database` is the **logical DB index** (`"0"` … `"15"`), kept as a string to play nicely with env-ref bindings. `list` and the connection metadata both label it as the active DB.
|
|
564
|
+
- `connection.timeout` (ms, default 5000) maps to ioredis's `connectTimeout`.
|
|
565
|
+
|
|
566
|
+
### Permission classification
|
|
567
|
+
|
|
568
|
+
Permission is derived from the command's first token (case-insensitive). Unknown commands are denied even at `admin` tier — they must be added to the allow-list.
|
|
569
|
+
|
|
570
|
+
| Tier | Commands |
|
|
571
|
+
|------|----------|
|
|
572
|
+
| `query-only` | `GET`, `MGET`, `STRLEN`, `EXISTS`, `TTL`, `PTTL`, `TYPE`, `SCAN`, `HGET`, `HGETALL`, `HKEYS`, `HVALS`, `HLEN`, `HEXISTS`, `HMGET`, `LRANGE`, `LLEN`, `LINDEX`, `SMEMBERS`, `SCARD`, `SISMEMBER`, `ZRANGE`, `ZREVRANGE`, `ZRANGEBYSCORE`, `ZCARD`, `ZSCORE`, `PING`, `ECHO` |
|
|
573
|
+
| `read-write` | `SET`, `SETEX`, `SETNX`, `PSETEX`, `MSET`, `MSETNX`, `APPEND`, `INCR`/`INCRBY`, `DECR`/`DECRBY`, `HSET`/`HSETNX`/`HMSET`/`HINCRBY`, `LPUSH`/`RPUSH`/`LPOP`/`RPOP`/`LSET`, `SADD`/`SREM`, `ZADD`/`ZREM`, `EXPIRE`/`EXPIREAT`/`PEXPIRE`/`PERSIST`, `RENAME` |
|
|
574
|
+
| `data-admin` | `DEL`, `UNLINK`, `HDEL` |
|
|
575
|
+
| `admin` | `FLUSHDB`, `FLUSHALL`, `CONFIG`, `INFO`, `CLIENT`, `DEBUG`, `SHUTDOWN`, `KEYS`, `MONITOR`, `SAVE`, `BGSAVE`, `BGREWRITEAOF`, `REPLICAOF`, `SLAVEOF`, `ACL` |
|
|
576
|
+
|
|
577
|
+
### Schema inspection
|
|
578
|
+
|
|
579
|
+
`schema <key>` returns one synthetic row per key with these columns:
|
|
580
|
+
|
|
581
|
+
| column | meaning |
|
|
582
|
+
|--------|---------|
|
|
583
|
+
| `type` | Redis type (`string` / `hash` / `list` / `set` / `zset` / `stream` / `none`) |
|
|
584
|
+
| `ttl` | `<n>s`, `no expiry`, or `missing` |
|
|
585
|
+
| `size` | `STRLEN` / `HLEN` / `LLEN` / `SCARD` / `ZCARD` / `XLEN` depending on type |
|
|
586
|
+
| `sample` | First 5 hash field names (hash only) |
|
|
587
|
+
|
|
588
|
+
`schema` (no key) and `--refresh` / `--reset` are rejected — there is no full-database schema cache for Redis.
|
|
589
|
+
|
|
590
|
+
### Recommended `query` patterns
|
|
591
|
+
|
|
592
|
+
```bash
|
|
593
|
+
# Read
|
|
594
|
+
dbcli query "GET feature:flag"
|
|
595
|
+
dbcli query "HGETALL user:42" --format json
|
|
596
|
+
dbcli query "LRANGE queue:jobs 0 9"
|
|
597
|
+
|
|
598
|
+
# Iterate keys (paginated; never use KEYS — admin-only)
|
|
599
|
+
dbcli query "SCAN 0 MATCH session:* COUNT 200"
|
|
600
|
+
|
|
601
|
+
# Write (requires read-write+)
|
|
602
|
+
dbcli query "SET counter 1"
|
|
603
|
+
dbcli query "EXPIRE session:abc 3600"
|
|
604
|
+
dbcli query "HSET user:42 name Alice"
|
|
605
|
+
|
|
606
|
+
# Delete (requires data-admin+)
|
|
607
|
+
dbcli query "DEL temp:lock"
|
|
608
|
+
dbcli query "HDEL user:42 lastLogin"
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
### Limitations
|
|
612
|
+
|
|
613
|
+
- No `--dry-run` for writes — Redis commands execute immediately. Pair writes with a confirming read (`GET`, `HGETALL`, `EXISTS`).
|
|
614
|
+
- No transaction wrapping (`MULTI`/`EXEC`). Submit one command at a time.
|
|
615
|
+
- `KEYS` requires `admin`. Prefer `SCAN` for routine work.
|
|
616
|
+
- Blacklist rules are not enforced for Redis (there is no concept of "column" / "table" the validator can map). Be careful with sensitive key prefixes.
|
|
617
|
+
|
|
618
|
+
## Elasticsearch Support
|
|
619
|
+
|
|
620
|
+
Elasticsearch connections speak the REST API. The adapter is fetch-based (no SDK) and supports HTTPS, custom CA, API key, basic auth, and Cloud ID.
|
|
621
|
+
|
|
622
|
+
**Supported commands:** `init`, `use`, `list`, `schema`, `query`, `status`, `doctor`, `upgrade`, `completion`
|
|
623
|
+
|
|
624
|
+
**Not supported (use external tooling):** `insert`, `update`, `delete`, `export`, `check`, `diff`, `migrate`, `q`, `shell`. The permission classifier already understands `_doc` / `_update` / `_bulk` so future write surfaces can be wired in without changing tiers.
|
|
625
|
+
|
|
626
|
+
### Connection and configuration
|
|
627
|
+
|
|
628
|
+
- Either `host` + `port` (default `https://localhost:9200`) or `nodes: [...]` (first node is used) or `cloudId`.
|
|
629
|
+
- Auth precedence: `apiKey` → `user`/`password` (HTTP Basic). Leave both unset for an open cluster.
|
|
630
|
+
- `protocol` defaults to `https`. For TLS quirks: `caPath` (path to a PEM bundle) and `rejectUnauthorized: false` (last resort).
|
|
631
|
+
- `connection.timeout` (ms, default 5000) is wired to `AbortController` on every request.
|
|
632
|
+
|
|
633
|
+
### Permission classification
|
|
634
|
+
|
|
635
|
+
Each REST request is mapped to a SQL-shaped tier based on method + path:
|
|
636
|
+
|
|
637
|
+
| ES surface | Mapped to | Permission |
|
|
638
|
+
|------------|-----------|------------|
|
|
639
|
+
| `GET _search` / `_count` / `_mapping` / `_settings` / `_alias` / `GET _doc` / `_source` | `SELECT` | `query-only` |
|
|
640
|
+
| `POST _update` / `POST _doc` | `UPDATE` | `read-write` |
|
|
641
|
+
| `PUT _doc` / `_create` | `INSERT` | `read-write` |
|
|
642
|
+
| `DELETE` (any) | `DELETE` | `data-admin` |
|
|
643
|
+
| `_bulk` | highest tier among the NDJSON actions (`delete` ⇒ `data-admin`) | derived |
|
|
644
|
+
| Anything else | `DROP` | `admin` (deny by default) |
|
|
645
|
+
|
|
646
|
+
### Schema inspection
|
|
647
|
+
|
|
648
|
+
`schema [index]` calls `GET /<index>/_mapping` and flattens nested properties into dotted-path columns. Multi-fields under `.fields` (e.g. `text` → `text.keyword`) are emitted as separate columns. All fields are reported as nullable. There is no PK / FK / index info.
|
|
649
|
+
|
|
650
|
+
`schema` (no argument) iterates all non-system indices through the standard full-scan code path and writes per-connection caches under `.dbcli/schemas/<connection>/`.
|
|
651
|
+
|
|
652
|
+
### Query semantics
|
|
653
|
+
|
|
654
|
+
- `--collection <index>` (or `--index <index>`) is required.
|
|
655
|
+
- Body that starts with `{` → sent as JSON DSL via `POST /<index>/_search`. Body otherwise → URL-encoded into `?q=...` (Lucene query string) on `GET`.
|
|
656
|
+
- Hits are flattened: each row carries `_id` plus dotted-path fields lifted from `_source`. Use `--format json` to inspect raw nested structure.
|
|
657
|
+
- Query-only mode caps `size` at 1000. `--no-limit` is internally capped at 10 000; for deeper pagination use the API directly with `search_after` or PIT.
|
|
658
|
+
|
|
659
|
+
### Recommended `query` patterns
|
|
660
|
+
|
|
661
|
+
```bash
|
|
662
|
+
# DSL match
|
|
663
|
+
dbcli query '{"query":{"match":{"status":"active"}}}' --collection orders --format json
|
|
664
|
+
|
|
665
|
+
# DSL with sort + size
|
|
666
|
+
dbcli query '{"query":{"range":{"created_at":{"gte":"2026-01-01"}}},"sort":[{"created_at":"desc"}],"size":50}' \
|
|
667
|
+
--collection orders
|
|
668
|
+
|
|
669
|
+
# Aggregation
|
|
670
|
+
dbcli query '{"size":0,"aggs":{"by_status":{"terms":{"field":"status.keyword"}}}}' \
|
|
671
|
+
--collection orders --format json
|
|
672
|
+
|
|
673
|
+
# Lucene query string
|
|
674
|
+
dbcli query 'status:active AND amount:>100' --index orders --limit 100
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
### Doctor and diagnostics
|
|
678
|
+
|
|
679
|
+
`dbcli doctor` runs a dedicated Elasticsearch path:
|
|
680
|
+
|
|
681
|
+
- Verifies REST connectivity to `GET /`.
|
|
682
|
+
- Reads `version.number` and runs the standard version freshness check.
|
|
683
|
+
- Walks every index via `listTables()` + `getTableSchema()` to feed the blacklist completeness check and the large-table heuristic (using `documentCount`).
|
|
684
|
+
- Standard schema-cache freshness using `schemaLastUpdated`.
|
|
685
|
+
|
|
686
|
+
### Limitations
|
|
687
|
+
|
|
688
|
+
- Writes (`insert`/`update`/`delete`/`export`) are not exposed yet — the adapter implements them, but the CLI currently only routes them for SQL and MongoDB.
|
|
689
|
+
- No `_search/scroll` or PIT pagination at the CLI layer; large pulls need a saved external script.
|
|
690
|
+
- `check`, `diff`, `migrate`, and `q` are SQL-only and exit with errors (or fall through to a generic "unsupported" path).
|
|
691
|
+
- Blacklist column rules are applied to flattened hit rows on `query`; table-level blacklist rejects an index up front.
|
|
File without changes
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Built-in snippets
|
|
2
|
+
|
|
3
|
+
Files under this directory are bundled with dbcli and resolved at runtime as
|
|
4
|
+
the `builtin` tier. Every file is a valid `.sql` snippet.
|
|
5
|
+
|
|
6
|
+
## Naming
|
|
7
|
+
|
|
8
|
+
- Single-engine variant: `<topic>.<engine>.sql` — loader derives key
|
|
9
|
+
`@<dir>/<topic>` and engine from the suffix.
|
|
10
|
+
- Cross-engine variant: `<topic>.sql` with explicit
|
|
11
|
+
`engine: [postgres, mysql]` in frontmatter.
|
|
12
|
+
|
|
13
|
+
## Override
|
|
14
|
+
|
|
15
|
+
Users can shadow any built-in snippet by placing a same-key file in
|
|
16
|
+
`.dbcli-shared/queries/` (team) or `.dbcli/queries/` (personal). Override is
|
|
17
|
+
per-engine: a local `connections.postgres.sql` only shadows the postgres
|
|
18
|
+
variant; the mysql variant is still served from builtin.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
-- ---
|
|
2
|
+
-- name: InnoDB buffer pool hit ratio (mysql)
|
|
3
|
+
-- description: Reads from disk vs. read requests from the buffer pool.
|
|
4
|
+
-- engine: mysql
|
|
5
|
+
-- ---
|
|
6
|
+
SELECT
|
|
7
|
+
(SELECT VARIABLE_VALUE FROM performance_schema.global_status
|
|
8
|
+
WHERE VARIABLE_NAME = 'Innodb_buffer_pool_reads') AS pool_reads,
|
|
9
|
+
(SELECT VARIABLE_VALUE FROM performance_schema.global_status
|
|
10
|
+
WHERE VARIABLE_NAME = 'Innodb_buffer_pool_read_requests') AS pool_read_requests,
|
|
11
|
+
ROUND(
|
|
12
|
+
1 -
|
|
13
|
+
(SELECT VARIABLE_VALUE FROM performance_schema.global_status
|
|
14
|
+
WHERE VARIABLE_NAME = 'Innodb_buffer_pool_reads')
|
|
15
|
+
/
|
|
16
|
+
NULLIF(
|
|
17
|
+
(SELECT VARIABLE_VALUE FROM performance_schema.global_status
|
|
18
|
+
WHERE VARIABLE_NAME = 'Innodb_buffer_pool_read_requests'), 0)
|
|
19
|
+
, 4) AS hit_ratio;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
-- ---
|
|
2
|
+
-- name: Cache hit ratio (postgres)
|
|
3
|
+
-- description: Heap and index buffer cache hit ratios across user tables.
|
|
4
|
+
-- engine: postgres
|
|
5
|
+
-- ---
|
|
6
|
+
SELECT SUM(heap_blks_read) AS heap_read,
|
|
7
|
+
SUM(heap_blks_hit) AS heap_hit,
|
|
8
|
+
ROUND(
|
|
9
|
+
SUM(heap_blks_hit)::numeric
|
|
10
|
+
/ NULLIF(SUM(heap_blks_hit) + SUM(heap_blks_read), 0)
|
|
11
|
+
, 4) AS heap_hit_ratio,
|
|
12
|
+
SUM(idx_blks_read) AS idx_read,
|
|
13
|
+
SUM(idx_blks_hit) AS idx_hit,
|
|
14
|
+
ROUND(
|
|
15
|
+
SUM(idx_blks_hit)::numeric
|
|
16
|
+
/ NULLIF(SUM(idx_blks_hit) + SUM(idx_blks_read), 0)
|
|
17
|
+
, 4) AS idx_hit_ratio
|
|
18
|
+
FROM pg_statio_user_tables;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
-- ---
|
|
2
|
+
-- name: Active connections (mysql)
|
|
3
|
+
-- description: Non-sleep processes ordered by elapsed time.
|
|
4
|
+
-- engine: mysql
|
|
5
|
+
-- ---
|
|
6
|
+
SELECT id,
|
|
7
|
+
user,
|
|
8
|
+
host,
|
|
9
|
+
db,
|
|
10
|
+
command,
|
|
11
|
+
time AS duration_seconds,
|
|
12
|
+
state,
|
|
13
|
+
info AS query
|
|
14
|
+
FROM information_schema.processlist
|
|
15
|
+
WHERE command <> 'Sleep'
|
|
16
|
+
ORDER BY time DESC;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
-- ---
|
|
2
|
+
-- name: Active connections (postgres)
|
|
3
|
+
-- description: Active sessions excluding idle, ordered by query start.
|
|
4
|
+
-- engine: postgres
|
|
5
|
+
-- ---
|
|
6
|
+
SELECT pid,
|
|
7
|
+
usename AS user,
|
|
8
|
+
application_name AS app,
|
|
9
|
+
client_addr AS client,
|
|
10
|
+
state,
|
|
11
|
+
NOW() - query_start AS duration,
|
|
12
|
+
query
|
|
13
|
+
FROM pg_stat_activity
|
|
14
|
+
WHERE state IS NOT NULL
|
|
15
|
+
AND state <> 'idle'
|
|
16
|
+
ORDER BY query_start;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
-- ---
|
|
2
|
+
-- name: Database size (mysql)
|
|
3
|
+
-- description: Total data + index size per schema in MB.
|
|
4
|
+
-- engine: mysql
|
|
5
|
+
-- ---
|
|
6
|
+
SELECT table_schema AS `database`,
|
|
7
|
+
ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS size_mb
|
|
8
|
+
FROM information_schema.tables
|
|
9
|
+
GROUP BY table_schema
|
|
10
|
+
ORDER BY size_mb DESC;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
-- ---
|
|
2
|
+
-- name: Database size (postgres)
|
|
3
|
+
-- description: Each database with pretty-printed total size.
|
|
4
|
+
-- engine: postgres
|
|
5
|
+
-- ---
|
|
6
|
+
SELECT datname AS database,
|
|
7
|
+
pg_size_pretty(pg_database_size(datname)) AS size
|
|
8
|
+
FROM pg_database
|
|
9
|
+
ORDER BY pg_database_size(datname) DESC;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
-- ---
|
|
2
|
+
-- name: Index usage (mysql)
|
|
3
|
+
-- description: Index I/O wait counts ordered by total uses.
|
|
4
|
+
-- engine: mysql
|
|
5
|
+
-- ---
|
|
6
|
+
SELECT object_schema AS `schema`,
|
|
7
|
+
object_name AS `table`,
|
|
8
|
+
index_name,
|
|
9
|
+
count_star AS uses,
|
|
10
|
+
count_read AS reads,
|
|
11
|
+
count_write AS writes
|
|
12
|
+
FROM performance_schema.table_io_waits_summary_by_index_usage
|
|
13
|
+
WHERE object_schema NOT IN ('mysql','performance_schema','sys')
|
|
14
|
+
AND index_name IS NOT NULL
|
|
15
|
+
ORDER BY count_star ASC;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
-- ---
|
|
2
|
+
-- name: Index usage (postgres)
|
|
3
|
+
-- description: Indexes ordered by scan count (low scans = candidates to drop).
|
|
4
|
+
-- engine: postgres
|
|
5
|
+
-- ---
|
|
6
|
+
SELECT schemaname AS schema,
|
|
7
|
+
relname AS table,
|
|
8
|
+
indexrelname AS index,
|
|
9
|
+
idx_scan AS scans,
|
|
10
|
+
idx_tup_read AS tuples_read,
|
|
11
|
+
idx_tup_fetch AS tuples_fetched,
|
|
12
|
+
pg_size_pretty(pg_relation_size(indexrelid)) AS index_size
|
|
13
|
+
FROM pg_stat_user_indexes
|
|
14
|
+
ORDER BY idx_scan ASC;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
-- ---
|
|
2
|
+
-- name: Lock waits (mysql)
|
|
3
|
+
-- description: InnoDB lock waits with waiting and blocking transactions.
|
|
4
|
+
-- engine: mysql
|
|
5
|
+
-- ---
|
|
6
|
+
SELECT waiting.trx_mysql_thread_id AS waiting_thread,
|
|
7
|
+
waiting.trx_query AS waiting_query,
|
|
8
|
+
blocking.trx_mysql_thread_id AS blocking_thread,
|
|
9
|
+
blocking.trx_query AS blocking_query
|
|
10
|
+
FROM performance_schema.data_lock_waits AS w
|
|
11
|
+
JOIN information_schema.innodb_trx AS waiting
|
|
12
|
+
ON w.requesting_engine_transaction_id = waiting.trx_id
|
|
13
|
+
JOIN information_schema.innodb_trx AS blocking
|
|
14
|
+
ON w.blocking_engine_transaction_id = blocking.trx_id;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
-- ---
|
|
2
|
+
-- name: Lock waits (postgres)
|
|
3
|
+
-- description: Sessions blocked by other sessions with both queries shown.
|
|
4
|
+
-- engine: postgres
|
|
5
|
+
-- ---
|
|
6
|
+
SELECT blocked.pid AS blocked_pid,
|
|
7
|
+
blocked.usename AS blocked_user,
|
|
8
|
+
blocked.query AS blocked_query,
|
|
9
|
+
blocking.pid AS blocking_pid,
|
|
10
|
+
blocking.usename AS blocking_user,
|
|
11
|
+
blocking.query AS blocking_query
|
|
12
|
+
FROM pg_stat_activity AS blocked
|
|
13
|
+
JOIN pg_stat_activity AS blocking
|
|
14
|
+
ON blocking.pid = ANY(pg_blocking_pids(blocked.pid))
|
|
15
|
+
WHERE blocked.pid <> blocking.pid;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
-- ---
|
|
2
|
+
-- name: Long-running queries (mysql)
|
|
3
|
+
-- description: Non-sleep processes whose elapsed time exceeds min_seconds.
|
|
4
|
+
-- engine: mysql
|
|
5
|
+
-- params:
|
|
6
|
+
-- min_seconds:
|
|
7
|
+
-- type: int
|
|
8
|
+
-- default: 30
|
|
9
|
+
-- ---
|
|
10
|
+
SELECT id, user, host, db, time AS duration_seconds, state, info AS query
|
|
11
|
+
FROM information_schema.processlist
|
|
12
|
+
WHERE command <> 'Sleep'
|
|
13
|
+
AND time > :min_seconds
|
|
14
|
+
ORDER BY time DESC;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
-- ---
|
|
2
|
+
-- name: Long-running queries (postgres)
|
|
3
|
+
-- description: Queries running longer than min_seconds.
|
|
4
|
+
-- engine: postgres
|
|
5
|
+
-- params:
|
|
6
|
+
-- min_seconds:
|
|
7
|
+
-- type: int
|
|
8
|
+
-- default: 30
|
|
9
|
+
-- ---
|
|
10
|
+
SELECT pid,
|
|
11
|
+
usename AS user,
|
|
12
|
+
NOW() - query_start AS duration,
|
|
13
|
+
state,
|
|
14
|
+
query
|
|
15
|
+
FROM pg_stat_activity
|
|
16
|
+
WHERE state IS NOT NULL
|
|
17
|
+
AND state <> 'idle'
|
|
18
|
+
AND NOW() - query_start > make_interval(secs => :min_seconds)
|
|
19
|
+
ORDER BY duration DESC;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
-- ---
|
|
2
|
+
-- name: Missing indexes (mysql)
|
|
3
|
+
-- description: Tables with significant full-scan I/O and no index used.
|
|
4
|
+
-- engine: mysql
|
|
5
|
+
-- ---
|
|
6
|
+
SELECT object_schema AS `schema`,
|
|
7
|
+
object_name AS `table`,
|
|
8
|
+
count_read AS full_scan_reads
|
|
9
|
+
FROM performance_schema.table_io_waits_summary_by_index_usage
|
|
10
|
+
WHERE index_name IS NULL
|
|
11
|
+
AND object_schema NOT IN ('mysql','performance_schema','sys')
|
|
12
|
+
AND count_read > 1000
|
|
13
|
+
ORDER BY count_read DESC;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
-- ---
|
|
2
|
+
-- name: Missing indexes (postgres)
|
|
3
|
+
-- description: User tables where seq scans dominate over index scans (>1k rows).
|
|
4
|
+
-- engine: postgres
|
|
5
|
+
-- ---
|
|
6
|
+
SELECT schemaname AS schema,
|
|
7
|
+
relname AS table,
|
|
8
|
+
seq_scan,
|
|
9
|
+
seq_tup_read,
|
|
10
|
+
idx_scan,
|
|
11
|
+
n_live_tup AS estimated_rows
|
|
12
|
+
FROM pg_stat_user_tables
|
|
13
|
+
WHERE seq_scan > COALESCE(idx_scan, 0)
|
|
14
|
+
AND n_live_tup > 1000
|
|
15
|
+
ORDER BY seq_tup_read DESC;
|