@carllee1983/dbcli 1.58.0 → 2.0.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/.cursor/rules/dbcli.mdc +12 -0
- package/.cursor/skills/dbcli/reference.md +43 -1
- package/.github/skills/dbcli/SKILL.md +12 -0
- package/.github/skills/dbcli/reference.md +43 -1
- package/CHANGELOG.md +31 -0
- package/assets/SKILL.md +12 -0
- package/assets/SKILL.zh-TW.md +11 -0
- package/assets/reference.md +43 -1
- package/dist/cli-runtime.mjs +590 -197
- package/dist/cli.mjs +1 -1
- package/dist/core.mjs +34 -0
- package/package.json +1 -1
- package/plugins/dbcli-agent/skills/dbcli/SKILL.md +12 -0
- package/plugins/dbcli-agent/skills/dbcli/reference.md +43 -1
- package/skills/dbcli/SKILL.md +12 -0
- package/skills/dbcli/reference.md +43 -1
package/dist/cli.mjs
CHANGED
package/dist/core.mjs
CHANGED
|
@@ -2191,6 +2191,23 @@ var ceremony_default = {
|
|
|
2191
2191
|
confirm_destructive_warning_ddl: "\u26A0\uFE0F Warning: this schema change is destructive and cannot be undone!",
|
|
2192
2192
|
confirm_ddl_prompt: "Are you sure you want to run this {operation}? This cannot be undone.",
|
|
2193
2193
|
confirm_prompt: "Proceed with this operation?",
|
|
2194
|
+
gate_summary: "This will {operation} rows in {table}.",
|
|
2195
|
+
gate_summary_untargeted: "This will run a {operation} statement.",
|
|
2196
|
+
gate_statement: "Statement:",
|
|
2197
|
+
gate_cancelled: "Cancelled. Nothing was run.",
|
|
2198
|
+
gate_full_table_warning: "\u26A0\uFE0F This affects EVERY row in {table} and cannot be undone.",
|
|
2199
|
+
gate_ddl_warning: "\u26A0\uFE0F This destroys {table} and cannot be undone.",
|
|
2200
|
+
gate_ddl_warning_untargeted: "\u26A0\uFE0F This is a destructive schema change and cannot be undone.",
|
|
2201
|
+
gate_unparseable_warning: "\u26A0\uFE0F dbcli could not parse this statement, so it is treated as affecting everything.",
|
|
2202
|
+
gate_multiple_statements_warning: "\u26A0\uFE0F This is more than one statement, and dbcli will not judge them one at a time.",
|
|
2203
|
+
gate_typed_prompt: "Type {phrase} to run it",
|
|
2204
|
+
gate_typed_mismatch: "That did not match {phrase}. Nothing was run.",
|
|
2205
|
+
gate_refused: "Refused: this statement is not limited to specific rows and nobody is here to confirm it (reason={reason}).",
|
|
2206
|
+
gate_remedy_no_where: "Add a WHERE clause \u2014 WHERE 1=1 if you really do mean every row \u2014 or a LIMIT, then run it again.",
|
|
2207
|
+
gate_remedy_ddl_destruction: "No flag skips this. Run it from an interactive terminal, or apply the schema change through a reviewed migration.",
|
|
2208
|
+
gate_remedy_unparseable: "dbcli could not parse this statement and will not guess. Simplify it, or run it from an interactive terminal.",
|
|
2209
|
+
gate_remedy_multiple_statements: "Run one statement per command.",
|
|
2210
|
+
gate_remedy_non_unique_where: "Match on a primary key or a uniquely indexed column, or run it from an interactive terminal.",
|
|
2194
2211
|
recovery_none: "This cannot be undone automatically \u2014 restore from a backup to get the previous values back.",
|
|
2195
2212
|
recovery_retry: "Re-run with --recovery to write a recovery plan, then read it with: dbcli recover",
|
|
2196
2213
|
blacklist_hint: "Run `dbcli blacklist list` to see what is protected"
|
|
@@ -2214,6 +2231,23 @@ var ceremony_default2 = {
|
|
|
2214
2231
|
confirm_destructive_warning_ddl: "\u26A0\uFE0F \u8B66\u544A\uFF1A\u9019\u500B schema \u8B8A\u66F4\u5177\u7834\u58DE\u6027\uFF0C\u57F7\u884C\u5F8C\u7121\u6CD5\u5FA9\u539F\uFF01",
|
|
2215
2232
|
confirm_ddl_prompt: "\u78BA\u5B9A\u8981\u57F7\u884C\u9019\u500B {operation} \u55CE\uFF1F\u6B64\u64CD\u4F5C\u7121\u6CD5\u5FA9\u539F\u3002",
|
|
2216
2233
|
confirm_prompt: "\u8981\u7E7C\u7E8C\u57F7\u884C\u9019\u500B\u64CD\u4F5C\u55CE\uFF1F",
|
|
2234
|
+
gate_summary: "\u9019\u53E5\u6703\u5C0D {table} \u57F7\u884C {operation}\u3002",
|
|
2235
|
+
gate_summary_untargeted: "\u9019\u53E5\u6703\u57F7\u884C {operation}\u3002",
|
|
2236
|
+
gate_statement: "\u8A9E\u53E5\uFF1A",
|
|
2237
|
+
gate_cancelled: "\u5DF2\u53D6\u6D88\uFF0C\u6C92\u6709\u57F7\u884C\u4EFB\u4F55\u8A9E\u53E5\u3002",
|
|
2238
|
+
gate_full_table_warning: "\u26A0\uFE0F \u9019\u53E5\u6703\u5F71\u97FF {table} \u7684\u6BCF\u4E00\u5217\uFF0C\u57F7\u884C\u5F8C\u7121\u6CD5\u5FA9\u539F\u3002",
|
|
2239
|
+
gate_ddl_warning: "\u26A0\uFE0F \u9019\u53E5\u6703\u6467\u6BC0 {table}\uFF0C\u57F7\u884C\u5F8C\u7121\u6CD5\u5FA9\u539F\u3002",
|
|
2240
|
+
gate_ddl_warning_untargeted: "\u26A0\uFE0F \u9019\u662F\u7834\u58DE\u6027\u7684 schema \u8B8A\u66F4\uFF0C\u57F7\u884C\u5F8C\u7121\u6CD5\u5FA9\u539F\u3002",
|
|
2241
|
+
gate_unparseable_warning: "\u26A0\uFE0F dbcli \u7121\u6CD5\u89E3\u6790\u9019\u53E5\uFF0C\u56E0\u6B64\u4E00\u5F8B\u8996\u70BA\u6703\u5F71\u97FF\u5168\u90E8\u8CC7\u6599\u3002",
|
|
2242
|
+
gate_multiple_statements_warning: "\u26A0\uFE0F \u9019\u88E1\u4E0D\u53EA\u4E00\u53E5\u8A9E\u53E5\uFF0Cdbcli \u4E0D\u6703\u9010\u53E5\u5224\u65B7\u3002",
|
|
2243
|
+
gate_typed_prompt: "\u8F38\u5165 {phrase} \u624D\u6703\u57F7\u884C",
|
|
2244
|
+
gate_typed_mismatch: "\u8F38\u5165\u7684\u5167\u5BB9\u8207 {phrase} \u4E0D\u7B26\uFF0C\u6C92\u6709\u57F7\u884C\u4EFB\u4F55\u8A9E\u53E5\u3002",
|
|
2245
|
+
gate_refused: "\u5DF2\u62D2\u7D55\uFF1A\u9019\u53E5\u6C92\u6709\u9650\u5B9A\u8981\u52D5\u54EA\u4E9B\u5217\uFF0C\u800C\u73FE\u5728\u6C92\u6709\u4EBA\u80FD\u78BA\u8A8D\uFF08reason={reason}\uFF09\u3002",
|
|
2246
|
+
gate_remedy_no_where: "\u88DC\u4E0A WHERE\uFF08\u771F\u7684\u8981\u5168\u8868\u5C31\u5BEB WHERE 1=1\uFF09\u6216 LIMIT\uFF0C\u7136\u5F8C\u91CD\u8DD1\u3002",
|
|
2247
|
+
gate_remedy_ddl_destruction: "\u6C92\u6709\u4EFB\u4F55\u65D7\u6A19\u53EF\u4EE5\u8DF3\u904E\u9019\u4E00\u95DC\u3002\u8ACB\u5728\u4E92\u52D5\u7D42\u7AEF\u4E0B\u57F7\u884C\uFF0C\u6216\u6539\u8D70\u7D93\u904E\u5BE9\u67E5\u7684 migration\u3002",
|
|
2248
|
+
gate_remedy_unparseable: "dbcli \u7121\u6CD5\u89E3\u6790\u9019\u53E5\uFF0C\u4E5F\u4E0D\u6703\u7528\u731C\u7684\u3002\u8ACB\u7C21\u5316\u8A9E\u53E5\uFF0C\u6216\u5728\u4E92\u52D5\u7D42\u7AEF\u4E0B\u57F7\u884C\u3002",
|
|
2249
|
+
gate_remedy_multiple_statements: "\u4E00\u6B21\u53EA\u57F7\u884C\u4E00\u53E5\u8A9E\u53E5\u3002",
|
|
2250
|
+
gate_remedy_non_unique_where: "\u6539\u7528\u4E3B\u9375\u6216\u552F\u4E00\u7D22\u5F15\u6B04\u4F4D\u7576\u689D\u4EF6\uFF0C\u6216\u5728\u4E92\u52D5\u7D42\u7AEF\u4E0B\u57F7\u884C\u3002",
|
|
2217
2251
|
recovery_none: "\u6B64\u64CD\u4F5C\u7121\u6CD5\u81EA\u52D5\u9084\u539F\uFF0C\u8981\u53D6\u56DE\u5148\u524D\u7684\u8CC7\u6599\u8ACB\u5F9E\u5099\u4EFD\u56DE\u5FA9\u3002",
|
|
2218
2252
|
recovery_retry: "\u52A0\u4E0A --recovery \u91CD\u8DD1\u53EF\u7522\u751F\u56DE\u5FA9\u8A08\u756B\uFF0C\u518D\u7528 dbcli recover \u8B80\u53D6",
|
|
2219
2253
|
blacklist_hint: "\u57F7\u884C `dbcli blacklist list` \u67E5\u770B\u53D7\u4FDD\u8B77\u7684\u9805\u76EE"
|
package/package.json
CHANGED
|
@@ -43,6 +43,18 @@ condition, first `query` / `export` the target rows' primary keys, then run one
|
|
|
43
43
|
`update` / `delete --where "id=<pk>"` per key — or escalate to a human. (MongoDB `--where`
|
|
44
44
|
takes a full JSON filter and is exempt.)
|
|
45
45
|
|
|
46
|
+
**Write gate (2.0.0) — the rule that will refuse you.** Every write is classified into two
|
|
47
|
+
tiers. Ordinary writes (`INSERT`, `UPDATE` / `DELETE` with a `WHERE`, `CREATE`, `ALTER`)
|
|
48
|
+
run unattended exactly as before; `--yes` skips the terminal prompt a human would see.
|
|
49
|
+
**Statements that are not limited to specific rows are refused outright when nobody can
|
|
50
|
+
answer a prompt** — `UPDATE` / `DELETE` with no `WHERE`, `DROP`, `TRUNCATE`, a statement
|
|
51
|
+
the SQL parser cannot read, several statements in one string, and `update` / `delete --where` matching on no primary key and
|
|
52
|
+
no unique index. The process exits `1` with `reason=no_where`, `reason=ddl_destruction`,
|
|
53
|
+
`reason=unparseable` or `reason=non_unique_where`, and **nothing reaches the database**.
|
|
54
|
+
**No flag bypasses this** — not `--yes`, not `--force`. To write every row on purpose, put
|
|
55
|
+
the intent in the SQL itself: add `WHERE 1=1` or a `LIMIT`. `DROP` / `TRUNCATE` have no
|
|
56
|
+
unattended route at all; escalate to a human.
|
|
57
|
+
|
|
46
58
|
> `report` and `guide` already embed an `inspect` snapshot — you do **not** need to run
|
|
47
59
|
> `dbcli inspect` first. Run `dbcli inspect --for-agent` manually only when you want the
|
|
48
60
|
> audit-recent context or to diagnose a connection problem.
|
|
@@ -334,9 +334,43 @@ dbcli query "SELECT day, dau FROM dau_daily" --ui # open in brows
|
|
|
334
334
|
dbcli query "SELECT * FROM orders" --format html > orders.html # pipe to stdout
|
|
335
335
|
```
|
|
336
336
|
|
|
337
|
-
**Options:** `--format <table|json|csv|html>`, `--ui` (open the dashboard in the system browser; implies `--format html`), `--limit <number>`, `--no-limit`, `--collection <name>` (MongoDB / Elasticsearch), `--index <name>` (Elasticsearch alias for `--collection`), `--fields <list>`, `--truncate <number>` / `--no-truncate`, `-f, --query-file <path>`, `--use <name[,name]>`, `--slow-ms <number>`, `--recovery`
|
|
337
|
+
**Options:** `--format <table|json|csv|html>`, `--ui` (open the dashboard in the system browser; implies `--format html`), `--limit <number>`, `--no-limit`, `--collection <name>` (MongoDB / Elasticsearch), `--index <name>` (Elasticsearch alias for `--collection`), `--fields <list>`, `--truncate <number>` / `--no-truncate`, `-f, --query-file <path>`, `--use <name[,name]>`, `--slow-ms <number>`, `--yes`, `--recovery`
|
|
338
338
|
**Permission:** query-only+ (Redis: per-command; Elasticsearch: per HTTP method/path)
|
|
339
339
|
|
|
340
|
+
#### Write confirmation gate (2.0.0)
|
|
341
|
+
|
|
342
|
+
A SQL write passes through a two-tier gate before the connection is opened. The gate is
|
|
343
|
+
separate from the permission axis: permission says what the connection may do, the gate
|
|
344
|
+
says whether this particular statement may run right now.
|
|
345
|
+
|
|
346
|
+
| Tier | Statements | Interactive terminal | Non-interactive (or `--format json`) |
|
|
347
|
+
| :--- | :--- | :--- | :--- |
|
|
348
|
+
| One | `INSERT`, `UPDATE` / `DELETE` **with** a `WHERE` or `LIMIT`, `CREATE`, `ALTER` | Summary + `y/N`; `--yes` skips it | Runs, exactly as before |
|
|
349
|
+
| Two | `UPDATE` / `DELETE` with **no** `WHERE`, `DROP`, `TRUNCATE`, unparseable statements, several statements in one string | Type the target table name; **no flag skips it** | **Refused**: exit `1`, nothing sent to the database |
|
|
350
|
+
|
|
351
|
+
A refusal message names a machine-readable reason — `reason=no_where`,
|
|
352
|
+
`reason=ddl_destruction`, `reason=unparseable`, `reason=multiple_statements` — so a caller can tell it apart from a
|
|
353
|
+
connection failure or a permission denial.
|
|
354
|
+
|
|
355
|
+
**Escape routes.** For a statement that accepts a `WHERE`, put the intent in the SQL:
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
dbcli query "UPDATE users SET banned = 1" # refused, reason=no_where
|
|
359
|
+
dbcli query "UPDATE users SET banned = 1 WHERE 1=1" # runs — intent is explicit
|
|
360
|
+
dbcli query "DELETE FROM sessions LIMIT 1000" # runs — damage is bounded
|
|
361
|
+
dbcli query "UPDATE users SET banned = 1 WHERE id = 3" --yes # tier one, question skipped
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
This is deliberately not a flag. `WHERE 1=1` appended to a statement that already has a
|
|
365
|
+
`WHERE` is a syntax error, so a blanket "always add it" habit breaks on the first ordinary
|
|
366
|
+
statement; a flag would be harmless everywhere and therefore added everywhere. For `DROP`
|
|
367
|
+
and `TRUNCATE` there is no clause to add — the connection's `permission` level decides
|
|
368
|
+
whether they are possible at all, and the typed confirmation must come from a person.
|
|
369
|
+
|
|
370
|
+
Every tier-two evaluation is written to the audit log with
|
|
371
|
+
`metadata.write_gate_outcome` (`allowed` / `declined` / `refused`) and
|
|
372
|
+
`metadata.write_gate_reason`.
|
|
373
|
+
|
|
340
374
|
> **Elasticsearch tiers by scope.** A read (`GET` / `HEAD`, plus `POST _search` and
|
|
341
375
|
> `POST _count`) is query-only. Indexing or updating a document is read-write.
|
|
342
376
|
> `DELETE /<index>/_doc/<id>` — one document — is data-admin. Everything that removes or
|
|
@@ -955,6 +989,14 @@ dbcli update users --where "id=1" --set '{"name":"Bob"}' --plan --format json
|
|
|
955
989
|
> target primary keys first, then issue one `update` / `delete --where "id=<pk>"` per key.
|
|
956
990
|
> (MongoDB `--where` accepts a full JSON filter and is exempt.)
|
|
957
991
|
|
|
992
|
+
> **Tier two for structured writes (2.0.0)** — a `--where` that matches on no primary key
|
|
993
|
+
> and no unique index selects an unknown number of rows, so it is treated the same as a
|
|
994
|
+
> raw statement with no `WHERE`: the target table name must be typed at an interactive
|
|
995
|
+
> terminal, and a non-interactive run is refused with exit `1` and
|
|
996
|
+
> `reason=non_unique_where`. `--force` and `--dry-run` do not affect this; `--force` skips
|
|
997
|
+
> the ordinary confirmation only. Select the primary keys first and write one row at a
|
|
998
|
+
> time, or run it where a person can confirm it.
|
|
999
|
+
|
|
958
1000
|
### delete
|
|
959
1001
|
|
|
960
1002
|
Delete data from a table.
|
package/skills/dbcli/SKILL.md
CHANGED
|
@@ -43,6 +43,18 @@ condition, first `query` / `export` the target rows' primary keys, then run one
|
|
|
43
43
|
`update` / `delete --where "id=<pk>"` per key — or escalate to a human. (MongoDB `--where`
|
|
44
44
|
takes a full JSON filter and is exempt.)
|
|
45
45
|
|
|
46
|
+
**Write gate (2.0.0) — the rule that will refuse you.** Every write is classified into two
|
|
47
|
+
tiers. Ordinary writes (`INSERT`, `UPDATE` / `DELETE` with a `WHERE`, `CREATE`, `ALTER`)
|
|
48
|
+
run unattended exactly as before; `--yes` skips the terminal prompt a human would see.
|
|
49
|
+
**Statements that are not limited to specific rows are refused outright when nobody can
|
|
50
|
+
answer a prompt** — `UPDATE` / `DELETE` with no `WHERE`, `DROP`, `TRUNCATE`, a statement
|
|
51
|
+
the SQL parser cannot read, several statements in one string, and `update` / `delete --where` matching on no primary key and
|
|
52
|
+
no unique index. The process exits `1` with `reason=no_where`, `reason=ddl_destruction`,
|
|
53
|
+
`reason=unparseable` or `reason=non_unique_where`, and **nothing reaches the database**.
|
|
54
|
+
**No flag bypasses this** — not `--yes`, not `--force`. To write every row on purpose, put
|
|
55
|
+
the intent in the SQL itself: add `WHERE 1=1` or a `LIMIT`. `DROP` / `TRUNCATE` have no
|
|
56
|
+
unattended route at all; escalate to a human.
|
|
57
|
+
|
|
46
58
|
> `report` and `guide` already embed an `inspect` snapshot — you do **not** need to run
|
|
47
59
|
> `dbcli inspect` first. Run `dbcli inspect --for-agent` manually only when you want the
|
|
48
60
|
> audit-recent context or to diagnose a connection problem.
|
|
@@ -334,9 +334,43 @@ dbcli query "SELECT day, dau FROM dau_daily" --ui # open in brows
|
|
|
334
334
|
dbcli query "SELECT * FROM orders" --format html > orders.html # pipe to stdout
|
|
335
335
|
```
|
|
336
336
|
|
|
337
|
-
**Options:** `--format <table|json|csv|html>`, `--ui` (open the dashboard in the system browser; implies `--format html`), `--limit <number>`, `--no-limit`, `--collection <name>` (MongoDB / Elasticsearch), `--index <name>` (Elasticsearch alias for `--collection`), `--fields <list>`, `--truncate <number>` / `--no-truncate`, `-f, --query-file <path>`, `--use <name[,name]>`, `--slow-ms <number>`, `--recovery`
|
|
337
|
+
**Options:** `--format <table|json|csv|html>`, `--ui` (open the dashboard in the system browser; implies `--format html`), `--limit <number>`, `--no-limit`, `--collection <name>` (MongoDB / Elasticsearch), `--index <name>` (Elasticsearch alias for `--collection`), `--fields <list>`, `--truncate <number>` / `--no-truncate`, `-f, --query-file <path>`, `--use <name[,name]>`, `--slow-ms <number>`, `--yes`, `--recovery`
|
|
338
338
|
**Permission:** query-only+ (Redis: per-command; Elasticsearch: per HTTP method/path)
|
|
339
339
|
|
|
340
|
+
#### Write confirmation gate (2.0.0)
|
|
341
|
+
|
|
342
|
+
A SQL write passes through a two-tier gate before the connection is opened. The gate is
|
|
343
|
+
separate from the permission axis: permission says what the connection may do, the gate
|
|
344
|
+
says whether this particular statement may run right now.
|
|
345
|
+
|
|
346
|
+
| Tier | Statements | Interactive terminal | Non-interactive (or `--format json`) |
|
|
347
|
+
| :--- | :--- | :--- | :--- |
|
|
348
|
+
| One | `INSERT`, `UPDATE` / `DELETE` **with** a `WHERE` or `LIMIT`, `CREATE`, `ALTER` | Summary + `y/N`; `--yes` skips it | Runs, exactly as before |
|
|
349
|
+
| Two | `UPDATE` / `DELETE` with **no** `WHERE`, `DROP`, `TRUNCATE`, unparseable statements, several statements in one string | Type the target table name; **no flag skips it** | **Refused**: exit `1`, nothing sent to the database |
|
|
350
|
+
|
|
351
|
+
A refusal message names a machine-readable reason — `reason=no_where`,
|
|
352
|
+
`reason=ddl_destruction`, `reason=unparseable`, `reason=multiple_statements` — so a caller can tell it apart from a
|
|
353
|
+
connection failure or a permission denial.
|
|
354
|
+
|
|
355
|
+
**Escape routes.** For a statement that accepts a `WHERE`, put the intent in the SQL:
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
dbcli query "UPDATE users SET banned = 1" # refused, reason=no_where
|
|
359
|
+
dbcli query "UPDATE users SET banned = 1 WHERE 1=1" # runs — intent is explicit
|
|
360
|
+
dbcli query "DELETE FROM sessions LIMIT 1000" # runs — damage is bounded
|
|
361
|
+
dbcli query "UPDATE users SET banned = 1 WHERE id = 3" --yes # tier one, question skipped
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
This is deliberately not a flag. `WHERE 1=1` appended to a statement that already has a
|
|
365
|
+
`WHERE` is a syntax error, so a blanket "always add it" habit breaks on the first ordinary
|
|
366
|
+
statement; a flag would be harmless everywhere and therefore added everywhere. For `DROP`
|
|
367
|
+
and `TRUNCATE` there is no clause to add — the connection's `permission` level decides
|
|
368
|
+
whether they are possible at all, and the typed confirmation must come from a person.
|
|
369
|
+
|
|
370
|
+
Every tier-two evaluation is written to the audit log with
|
|
371
|
+
`metadata.write_gate_outcome` (`allowed` / `declined` / `refused`) and
|
|
372
|
+
`metadata.write_gate_reason`.
|
|
373
|
+
|
|
340
374
|
> **Elasticsearch tiers by scope.** A read (`GET` / `HEAD`, plus `POST _search` and
|
|
341
375
|
> `POST _count`) is query-only. Indexing or updating a document is read-write.
|
|
342
376
|
> `DELETE /<index>/_doc/<id>` — one document — is data-admin. Everything that removes or
|
|
@@ -955,6 +989,14 @@ dbcli update users --where "id=1" --set '{"name":"Bob"}' --plan --format json
|
|
|
955
989
|
> target primary keys first, then issue one `update` / `delete --where "id=<pk>"` per key.
|
|
956
990
|
> (MongoDB `--where` accepts a full JSON filter and is exempt.)
|
|
957
991
|
|
|
992
|
+
> **Tier two for structured writes (2.0.0)** — a `--where` that matches on no primary key
|
|
993
|
+
> and no unique index selects an unknown number of rows, so it is treated the same as a
|
|
994
|
+
> raw statement with no `WHERE`: the target table name must be typed at an interactive
|
|
995
|
+
> terminal, and a non-interactive run is refused with exit `1` and
|
|
996
|
+
> `reason=non_unique_where`. `--force` and `--dry-run` do not affect this; `--force` skips
|
|
997
|
+
> the ordinary confirmation only. Select the primary keys first and write one row at a
|
|
998
|
+
> time, or run it where a person can confirm it.
|
|
999
|
+
|
|
958
1000
|
### delete
|
|
959
1001
|
|
|
960
1002
|
Delete data from a table.
|