@carllee1983/dbcli 1.57.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 +19 -2
- package/.cursor/skills/dbcli/reference.md +55 -4
- package/.github/skills/dbcli/SKILL.md +19 -2
- package/.github/skills/dbcli/reference.md +55 -4
- package/CHANGELOG.md +71 -0
- package/assets/SKILL.md +19 -2
- package/assets/SKILL.zh-TW.md +15 -1
- package/assets/reference.md +55 -4
- package/dist/cli-runtime.mjs +1323 -688
- package/dist/cli.mjs +4 -2
- package/dist/core.d.ts +79 -24
- package/dist/core.mjs +349 -343
- package/package.json +4 -2
- package/plugins/dbcli-agent/skills/dbcli/SKILL.md +19 -2
- package/plugins/dbcli-agent/skills/dbcli/reference.md +55 -4
- package/skills/dbcli/SKILL.md +19 -2
- package/skills/dbcli/reference.md +55 -4
package/dist/cli-runtime.mjs
CHANGED
|
@@ -51,7 +51,7 @@ var package_default;
|
|
|
51
51
|
var init_package = __esm(() => {
|
|
52
52
|
package_default = {
|
|
53
53
|
name: "@carllee1983/dbcli",
|
|
54
|
-
version: "
|
|
54
|
+
version: "2.0.0",
|
|
55
55
|
description: "Database CLI for AI agents",
|
|
56
56
|
type: "module",
|
|
57
57
|
publishConfig: {
|
|
@@ -125,12 +125,14 @@ var init_package = __esm(() => {
|
|
|
125
125
|
"test:unit": "bun test tests/unit tests/core",
|
|
126
126
|
"test:integration": "bun test tests/integration",
|
|
127
127
|
"test:gherkin": "bun test tests/gherkin",
|
|
128
|
-
"test:docker": "docker compose -f docker-compose.test.yml up -d --wait && bun test tests/integration
|
|
128
|
+
"test:docker": "docker compose -f docker-compose.test.yml up -d --wait && bun test tests/integration; docker compose -f docker-compose.test.yml down",
|
|
129
|
+
"services:check": "bun run scripts/check-test-services.ts",
|
|
129
130
|
"docs:check": "bun run scripts/check-user-docs.ts",
|
|
130
131
|
"contract:check": "bun run scripts/check-cli-contract.ts",
|
|
131
132
|
"skill:check": "bun run scripts/check-skill-parity.ts",
|
|
132
133
|
"platform:check": "bun run scripts/check-platform-parity.ts",
|
|
133
134
|
"agent-core:check": "bun run scripts/check-agent-core-purity.ts",
|
|
135
|
+
"core-stdout:check": "bun run scripts/check-core-no-stdout.ts",
|
|
134
136
|
typecheck: "tsc --noEmit --pretty false",
|
|
135
137
|
"test:perf": "bun test ./tests/perf/*.bench.ts",
|
|
136
138
|
lint: "eslint src tests scripts --ext .ts --max-warnings=0",
|
|
@@ -8943,6 +8945,12 @@ var init_messages = __esm(() => {
|
|
|
8943
8945
|
invalid_config: "Invalid configuration: {field}",
|
|
8944
8946
|
connection_failed: "Failed to connect to database: {message}",
|
|
8945
8947
|
permission_denied: "Permission denied (required: {required})",
|
|
8948
|
+
permission_requires_level: "{type} operation requires {minimum} permission or higher (current level: {permission})",
|
|
8949
|
+
elasticsearch_requires_level: "Elasticsearch {type} operation requires {minimum} permission or higher (current level: {permission})",
|
|
8950
|
+
escalated_write_requires_admin: "This statement opens as a read but contains an executable {keyword}. A write hidden inside a read statement requires admin permission (current level: {permission}).",
|
|
8951
|
+
multiple_statements_refused: "SQL containing multiple statements is refused below admin permission, because only the first statement determines the permission check. Run each statement separately.",
|
|
8952
|
+
unknown_statement_query_only: "Unrecognised SQL statement (current level: query-only). Security policy requires read-write+ for unknown statements. If this is a legitimate read-only statement, please open an issue at https://github.com/CarlLee1983/dbcli/issues.",
|
|
8953
|
+
permission_denied_reason: "Permission denied: {reason}",
|
|
8946
8954
|
table_not_found: "Table not found: {table}",
|
|
8947
8955
|
unsupported_lang: "Language '{lang}' not supported, using English",
|
|
8948
8956
|
invalid_json: "Invalid JSON: {message}",
|
|
@@ -8964,7 +8972,8 @@ Provide options: --env-host, --env-port, --env-user, --env-password, --env-datab
|
|
|
8964
8972
|
Or use interactive mode: run "dbcli init --use-env-refs" without --no-interactive`,
|
|
8965
8973
|
env_var_not_defined: `Cannot test connection: environment variable {envKey} is not defined.
|
|
8966
8974
|
Set {envKey} in .env or environment variables.
|
|
8967
|
-
Hint: run 'export {envKey}=<value>' and retry
|
|
8975
|
+
Hint: run 'export {envKey}=<value>' and retry`,
|
|
8976
|
+
invalid_output_format: "--format {format} is not supported here \u2014 use text or json"
|
|
8968
8977
|
},
|
|
8969
8978
|
success: {
|
|
8970
8979
|
inserted: "Successfully inserted {count} row(s)",
|
|
@@ -8975,19 +8984,22 @@ Hint: run 'export {envKey}=<value>' and retry`
|
|
|
8975
8984
|
insert: {
|
|
8976
8985
|
description: "Insert data into a table",
|
|
8977
8986
|
prompt_data: "Enter JSON data to insert: ",
|
|
8978
|
-
confirm: "Insert {count} row(s) into {table}? (y/n): "
|
|
8987
|
+
confirm: "Insert {count} row(s) into {table}? (y/n): ",
|
|
8988
|
+
elasticsearch_unsupported: "Elasticsearch does not support the insert command; for now, use an external tool (e.g. curl or Kibana DevTools) to write documents"
|
|
8979
8989
|
},
|
|
8980
8990
|
update: {
|
|
8981
8991
|
description: "Update data in a table",
|
|
8982
8992
|
prompt_where: "Enter WHERE clause (e.g., id=1): ",
|
|
8983
8993
|
prompt_set: 'Enter JSON updates (e.g., {"status": "active"}): ',
|
|
8984
|
-
confirm: "Update rows in {table}? (y/n): "
|
|
8994
|
+
confirm: "Update rows in {table}? (y/n): ",
|
|
8995
|
+
elasticsearch_unsupported: "Elasticsearch does not support the update command; for now, use an external tool (e.g. curl or Kibana DevTools) to update documents"
|
|
8985
8996
|
},
|
|
8986
8997
|
delete: {
|
|
8987
8998
|
description: "Delete data from a table",
|
|
8988
8999
|
prompt_where: "Enter WHERE clause (e.g., id=1): ",
|
|
8989
9000
|
confirm: "Delete rows from {table}? (y/n): ",
|
|
8990
|
-
admin_only: "Delete command requires data-admin or admin permission"
|
|
9001
|
+
admin_only: "Delete command requires data-admin or admin permission",
|
|
9002
|
+
elasticsearch_unsupported: "Elasticsearch does not support the delete command; for now, use an external tool (e.g. curl or Kibana DevTools) to delete documents"
|
|
8991
9003
|
},
|
|
8992
9004
|
export: {
|
|
8993
9005
|
description: "Export query results",
|
|
@@ -9241,6 +9253,12 @@ var init_messages2 = __esm(() => {
|
|
|
9241
9253
|
invalid_config: "\u914D\u7F6E\u7121\u6548\uFF1A{field}",
|
|
9242
9254
|
connection_failed: "\u7121\u6CD5\u9023\u63A5\u5230\u8CC7\u6599\u5EAB\uFF1A{message}",
|
|
9243
9255
|
permission_denied: "\u6B0A\u9650\u88AB\u62D2\uFF08\u9700\u8981\uFF1A{required}\uFF09",
|
|
9256
|
+
permission_requires_level: "{type} \u64CD\u4F5C\u9700\u8981 {minimum} \u4EE5\u4E0A\u7684\u6B0A\u9650\uFF08\u76EE\u524D\u5C64\u7D1A\uFF1A{permission}\uFF09",
|
|
9257
|
+
elasticsearch_requires_level: "Elasticsearch {type} \u64CD\u4F5C\u9700\u8981 {minimum} \u4EE5\u4E0A\u7684\u6B0A\u9650\uFF08\u76EE\u524D\u5C64\u7D1A\uFF1A{permission}\uFF09",
|
|
9258
|
+
escalated_write_requires_admin: "\u6B64\u8A9E\u53E5\u8868\u9762\u4E0A\u662F\u8B80\u53D6\uFF0C\u4F46\u5BE6\u969B\u5305\u542B\u53EF\u57F7\u884C\u7684 {keyword}\u3002\u8B80\u53D6\u8A9E\u53E5\u4E2D\u96B1\u85CF\u5BEB\u5165\u64CD\u4F5C\u9700\u8981 admin \u6B0A\u9650\uFF08\u76EE\u524D\u5C64\u7D1A\uFF1A{permission}\uFF09\u3002",
|
|
9259
|
+
multiple_statements_refused: "\u4F4E\u65BC admin \u6B0A\u9650\u6642\u62D2\u7D55\u57F7\u884C\u5305\u542B\u591A\u500B\u8A9E\u53E5\u7684 SQL\uFF0C\u56E0\u70BA\u6B0A\u9650\u6AA2\u67E5\u53EA\u6703\u4F9D\u64DA\u7B2C\u4E00\u500B\u8A9E\u53E5\u5224\u65B7\u3002\u8ACB\u5C07\u5404\u8A9E\u53E5\u5206\u958B\u57F7\u884C\u3002",
|
|
9260
|
+
unknown_statement_query_only: "\u7121\u6CD5\u8FA8\u8B58\u7684 SQL \u8A9E\u53E5\uFF08\u76EE\u524D\u5C64\u7D1A\uFF1Aquery-only\uFF09\u3002\u5B89\u5168\u653F\u7B56\u8981\u6C42\u672A\u77E5\u8A9E\u53E5\u81F3\u5C11\u9700\u8981 read-write \u4EE5\u4E0A\u6B0A\u9650\u3002\u82E5\u9019\u5176\u5BE6\u662F\u5408\u6CD5\u7684\u552F\u8B80\u8A9E\u53E5\uFF0C\u8ACB\u81F3 https://github.com/CarlLee1983/dbcli/issues \u63D0\u51FA\u8B70\u984C\u3002",
|
|
9261
|
+
permission_denied_reason: "\u6B0A\u9650\u88AB\u62D2\uFF1A{reason}",
|
|
9244
9262
|
table_not_found: "\u627E\u4E0D\u5230\u8868\u683C\uFF1A{table}",
|
|
9245
9263
|
unsupported_lang: "\u4E0D\u652F\u63F4\u7684\u8A9E\u8A00 '{lang}'\uFF0C\u4F7F\u7528\u82F1\u6587",
|
|
9246
9264
|
invalid_json: "\u7121\u6548\u7684 JSON\uFF1A{message}",
|
|
@@ -9262,7 +9280,8 @@ var init_messages2 = __esm(() => {
|
|
|
9262
9280
|
\u6216\u4F7F\u7528\u4E92\u52D5\u6A21\u5F0F\uFF1A\u57F7\u884C "dbcli init --use-env-refs"\uFF08\u4E0D\u52A0 --no-interactive\uFF09`,
|
|
9263
9281
|
env_var_not_defined: `\u7121\u6CD5\u6E2C\u8A66\u9023\u7DDA\uFF1A\u74B0\u5883\u8B8A\u6578 {envKey} \u672A\u5B9A\u7FA9\u3002
|
|
9264
9282
|
\u8ACB\u5728 .env \u6216\u74B0\u5883\u8B8A\u6578\u4E2D\u8A2D\u5B9A {envKey}\u3002
|
|
9265
|
-
\u63D0\u793A\uFF1A\u57F7\u884C 'export {envKey}=<value>' \u5F8C\u91CD\u8A66
|
|
9283
|
+
\u63D0\u793A\uFF1A\u57F7\u884C 'export {envKey}=<value>' \u5F8C\u91CD\u8A66`,
|
|
9284
|
+
invalid_output_format: "\u7121\u6CD5\u4F7F\u7528 --format {format}\uFF0C\u8ACB\u4F7F\u7528 text \u6216 json"
|
|
9266
9285
|
},
|
|
9267
9286
|
success: {
|
|
9268
9287
|
inserted: "\u6210\u529F\u63D2\u5165 {count} \u5217",
|
|
@@ -9273,19 +9292,22 @@ var init_messages2 = __esm(() => {
|
|
|
9273
9292
|
insert: {
|
|
9274
9293
|
description: "\u63D2\u5165\u8CC7\u6599\u5230\u8868\u683C",
|
|
9275
9294
|
prompt_data: "\u8F38\u5165\u8981\u63D2\u5165\u7684 JSON \u8CC7\u6599\uFF1A",
|
|
9276
|
-
confirm: "\u5C07 {count} \u5217\u63D2\u5165\u5230 {table}\uFF1F (y/n)\uFF1A"
|
|
9295
|
+
confirm: "\u5C07 {count} \u5217\u63D2\u5165\u5230 {table}\uFF1F (y/n)\uFF1A",
|
|
9296
|
+
elasticsearch_unsupported: "Elasticsearch \u4E0D\u652F\u63F4 insert \u6307\u4EE4\uFF1B\u76EE\u524D\u8ACB\u4F7F\u7528\u5916\u90E8\u5DE5\u5177\uFF08\u5982 curl \u6216 Kibana DevTools\uFF09\u9032\u884C\u6587\u4EF6\u5BEB\u5165"
|
|
9277
9297
|
},
|
|
9278
9298
|
update: {
|
|
9279
9299
|
description: "\u66F4\u65B0\u8868\u683C\u4E2D\u7684\u8CC7\u6599",
|
|
9280
9300
|
prompt_where: "\u8F38\u5165 WHERE \u5B50\u53E5 (\u4F8B\u5982\uFF1Aid=1)\uFF1A",
|
|
9281
9301
|
prompt_set: '\u8F38\u5165 JSON \u66F4\u65B0 (\u4F8B\u5982\uFF1A{"status": "active"})\uFF1A',
|
|
9282
|
-
confirm: "\u66F4\u65B0 {table} \u4E2D\u7684\u5217\uFF1F (y/n)\uFF1A"
|
|
9302
|
+
confirm: "\u66F4\u65B0 {table} \u4E2D\u7684\u5217\uFF1F (y/n)\uFF1A",
|
|
9303
|
+
elasticsearch_unsupported: "Elasticsearch \u4E0D\u652F\u63F4 update \u6307\u4EE4\uFF1B\u76EE\u524D\u8ACB\u4F7F\u7528\u5916\u90E8\u5DE5\u5177\uFF08\u5982 curl \u6216 Kibana DevTools\uFF09\u9032\u884C\u6587\u4EF6\u66F4\u65B0"
|
|
9283
9304
|
},
|
|
9284
9305
|
delete: {
|
|
9285
9306
|
description: "\u522A\u9664\u8868\u683C\u4E2D\u7684\u8CC7\u6599",
|
|
9286
9307
|
prompt_where: "\u8F38\u5165 WHERE \u5B50\u53E5 (\u4F8B\u5982\uFF1Aid=1)\uFF1A",
|
|
9287
9308
|
confirm: "\u522A\u9664 {table} \u4E2D\u7684\u5217\uFF1F (y/n)\uFF1A",
|
|
9288
|
-
admin_only: "\u522A\u9664\u547D\u4EE4\u9700\u8981 data-admin \u6216 admin \u6B0A\u9650"
|
|
9309
|
+
admin_only: "\u522A\u9664\u547D\u4EE4\u9700\u8981 data-admin \u6216 admin \u6B0A\u9650",
|
|
9310
|
+
elasticsearch_unsupported: "Elasticsearch \u4E0D\u652F\u63F4 delete \u6307\u4EE4\uFF1B\u76EE\u524D\u8ACB\u4F7F\u7528\u5916\u90E8\u5DE5\u5177\uFF08\u5982 curl \u6216 Kibana DevTools\uFF09\u9032\u884C\u6587\u4EF6\u522A\u9664"
|
|
9289
9311
|
},
|
|
9290
9312
|
export: {
|
|
9291
9313
|
description: "\u532F\u51FA\u67E5\u8A62\u7D50\u679C",
|
|
@@ -9430,6 +9452,94 @@ var init_shell2 = __esm(() => {
|
|
|
9430
9452
|
};
|
|
9431
9453
|
});
|
|
9432
9454
|
|
|
9455
|
+
// resources/lang/en/ceremony.json
|
|
9456
|
+
var ceremony_default;
|
|
9457
|
+
var init_ceremony = __esm(() => {
|
|
9458
|
+
ceremony_default = {
|
|
9459
|
+
updated: "Updated {count} row(s) in {table}",
|
|
9460
|
+
deleted: "Deleted {count} row(s) from {table}",
|
|
9461
|
+
inserted: "Inserted {count} row(s) into {table}",
|
|
9462
|
+
matched_nothing: "No rows matched in {table} \u2014 nothing changed",
|
|
9463
|
+
cancelled: "Cancelled. {table} was not changed",
|
|
9464
|
+
dry_run: "Preview only. {table} was not changed",
|
|
9465
|
+
elapsed: "Took {seconds}s",
|
|
9466
|
+
failed: "Failed: {reason}",
|
|
9467
|
+
unknown_error: "unknown error",
|
|
9468
|
+
confirm_sql: "Generated SQL:",
|
|
9469
|
+
confirm_command: "Command to run:",
|
|
9470
|
+
confirm_params: "Parameters:",
|
|
9471
|
+
confirm_destructive_warning: "\u26A0\uFE0F Warning: DELETE operation is destructive and cannot be undone!",
|
|
9472
|
+
confirm_destructive_prompt: "Are you sure you want to execute this DELETE operation? This cannot be undone.",
|
|
9473
|
+
confirm_destructive_warning_ddl: "\u26A0\uFE0F Warning: this schema change is destructive and cannot be undone!",
|
|
9474
|
+
confirm_ddl_prompt: "Are you sure you want to run this {operation}? This cannot be undone.",
|
|
9475
|
+
confirm_prompt: "Proceed with this operation?",
|
|
9476
|
+
gate_summary: "This will {operation} rows in {table}.",
|
|
9477
|
+
gate_summary_untargeted: "This will run a {operation} statement.",
|
|
9478
|
+
gate_statement: "Statement:",
|
|
9479
|
+
gate_cancelled: "Cancelled. Nothing was run.",
|
|
9480
|
+
gate_full_table_warning: "\u26A0\uFE0F This affects EVERY row in {table} and cannot be undone.",
|
|
9481
|
+
gate_ddl_warning: "\u26A0\uFE0F This destroys {table} and cannot be undone.",
|
|
9482
|
+
gate_ddl_warning_untargeted: "\u26A0\uFE0F This is a destructive schema change and cannot be undone.",
|
|
9483
|
+
gate_unparseable_warning: "\u26A0\uFE0F dbcli could not parse this statement, so it is treated as affecting everything.",
|
|
9484
|
+
gate_multiple_statements_warning: "\u26A0\uFE0F This is more than one statement, and dbcli will not judge them one at a time.",
|
|
9485
|
+
gate_typed_prompt: "Type {phrase} to run it",
|
|
9486
|
+
gate_typed_mismatch: "That did not match {phrase}. Nothing was run.",
|
|
9487
|
+
gate_refused: "Refused: this statement is not limited to specific rows and nobody is here to confirm it (reason={reason}).",
|
|
9488
|
+
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.",
|
|
9489
|
+
gate_remedy_ddl_destruction: "No flag skips this. Run it from an interactive terminal, or apply the schema change through a reviewed migration.",
|
|
9490
|
+
gate_remedy_unparseable: "dbcli could not parse this statement and will not guess. Simplify it, or run it from an interactive terminal.",
|
|
9491
|
+
gate_remedy_multiple_statements: "Run one statement per command.",
|
|
9492
|
+
gate_remedy_non_unique_where: "Match on a primary key or a uniquely indexed column, or run it from an interactive terminal.",
|
|
9493
|
+
recovery_none: "This cannot be undone automatically \u2014 restore from a backup to get the previous values back.",
|
|
9494
|
+
recovery_retry: "Re-run with --recovery to write a recovery plan, then read it with: dbcli recover",
|
|
9495
|
+
blacklist_hint: "Run `dbcli blacklist list` to see what is protected"
|
|
9496
|
+
};
|
|
9497
|
+
});
|
|
9498
|
+
|
|
9499
|
+
// resources/lang/zh-TW/ceremony.json
|
|
9500
|
+
var ceremony_default2;
|
|
9501
|
+
var init_ceremony2 = __esm(() => {
|
|
9502
|
+
ceremony_default2 = {
|
|
9503
|
+
updated: "\u5DF2\u66F4\u65B0 {table} \u7684 {count} \u5217",
|
|
9504
|
+
deleted: "\u5DF2\u5F9E {table} \u522A\u9664 {count} \u5217",
|
|
9505
|
+
inserted: "\u5DF2\u5BEB\u5165 {count} \u5217\u5230 {table}",
|
|
9506
|
+
matched_nothing: "{table} \u6C92\u6709\u4EFB\u4F55\u5217\u7B26\u5408\u689D\u4EF6\uFF0C\u8CC7\u6599\u672A\u8B8A\u52D5",
|
|
9507
|
+
cancelled: "\u5DF2\u53D6\u6D88\uFF0C{table} \u672A\u8B8A\u52D5",
|
|
9508
|
+
dry_run: "\u50C5\u9810\u89BD\uFF0C{table} \u672A\u8B8A\u52D5",
|
|
9509
|
+
elapsed: "\u8017\u6642 {seconds} \u79D2",
|
|
9510
|
+
failed: "\u5931\u6557\uFF1A{reason}",
|
|
9511
|
+
unknown_error: "\u672A\u77E5\u7684\u932F\u8AA4",
|
|
9512
|
+
confirm_sql: "\u7522\u751F\u7684 SQL\uFF1A",
|
|
9513
|
+
confirm_command: "\u5373\u5C07\u57F7\u884C\u7684\u6307\u4EE4\uFF1A",
|
|
9514
|
+
confirm_params: "\u53C3\u6578\uFF1A",
|
|
9515
|
+
confirm_destructive_warning: "\u26A0\uFE0F \u8B66\u544A\uFF1ADELETE \u5177\u7834\u58DE\u6027\uFF0C\u57F7\u884C\u5F8C\u7121\u6CD5\u5FA9\u539F\uFF01",
|
|
9516
|
+
confirm_destructive_prompt: "\u78BA\u5B9A\u8981\u57F7\u884C\u9019\u500B DELETE \u55CE\uFF1F\u6B64\u64CD\u4F5C\u7121\u6CD5\u5FA9\u539F\u3002",
|
|
9517
|
+
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",
|
|
9518
|
+
confirm_ddl_prompt: "\u78BA\u5B9A\u8981\u57F7\u884C\u9019\u500B {operation} \u55CE\uFF1F\u6B64\u64CD\u4F5C\u7121\u6CD5\u5FA9\u539F\u3002",
|
|
9519
|
+
confirm_prompt: "\u8981\u7E7C\u7E8C\u57F7\u884C\u9019\u500B\u64CD\u4F5C\u55CE\uFF1F",
|
|
9520
|
+
gate_summary: "\u9019\u53E5\u6703\u5C0D {table} \u57F7\u884C {operation}\u3002",
|
|
9521
|
+
gate_summary_untargeted: "\u9019\u53E5\u6703\u57F7\u884C {operation}\u3002",
|
|
9522
|
+
gate_statement: "\u8A9E\u53E5\uFF1A",
|
|
9523
|
+
gate_cancelled: "\u5DF2\u53D6\u6D88\uFF0C\u6C92\u6709\u57F7\u884C\u4EFB\u4F55\u8A9E\u53E5\u3002",
|
|
9524
|
+
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",
|
|
9525
|
+
gate_ddl_warning: "\u26A0\uFE0F \u9019\u53E5\u6703\u6467\u6BC0 {table}\uFF0C\u57F7\u884C\u5F8C\u7121\u6CD5\u5FA9\u539F\u3002",
|
|
9526
|
+
gate_ddl_warning_untargeted: "\u26A0\uFE0F \u9019\u662F\u7834\u58DE\u6027\u7684 schema \u8B8A\u66F4\uFF0C\u57F7\u884C\u5F8C\u7121\u6CD5\u5FA9\u539F\u3002",
|
|
9527
|
+
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",
|
|
9528
|
+
gate_multiple_statements_warning: "\u26A0\uFE0F \u9019\u88E1\u4E0D\u53EA\u4E00\u53E5\u8A9E\u53E5\uFF0Cdbcli \u4E0D\u6703\u9010\u53E5\u5224\u65B7\u3002",
|
|
9529
|
+
gate_typed_prompt: "\u8F38\u5165 {phrase} \u624D\u6703\u57F7\u884C",
|
|
9530
|
+
gate_typed_mismatch: "\u8F38\u5165\u7684\u5167\u5BB9\u8207 {phrase} \u4E0D\u7B26\uFF0C\u6C92\u6709\u57F7\u884C\u4EFB\u4F55\u8A9E\u53E5\u3002",
|
|
9531
|
+
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",
|
|
9532
|
+
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",
|
|
9533
|
+
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",
|
|
9534
|
+
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",
|
|
9535
|
+
gate_remedy_multiple_statements: "\u4E00\u6B21\u53EA\u57F7\u884C\u4E00\u53E5\u8A9E\u53E5\u3002",
|
|
9536
|
+
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",
|
|
9537
|
+
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",
|
|
9538
|
+
recovery_retry: "\u52A0\u4E0A --recovery \u91CD\u8DD1\u53EF\u7522\u751F\u56DE\u5FA9\u8A08\u756B\uFF0C\u518D\u7528 dbcli recover \u8B80\u53D6",
|
|
9539
|
+
blacklist_hint: "\u57F7\u884C `dbcli blacklist list` \u67E5\u770B\u53D7\u4FDD\u8B77\u7684\u9805\u76EE"
|
|
9540
|
+
};
|
|
9541
|
+
});
|
|
9542
|
+
|
|
9433
9543
|
// src/i18n/message-loader.ts
|
|
9434
9544
|
class MessageLoader {
|
|
9435
9545
|
static instance = null;
|
|
@@ -9496,9 +9606,15 @@ var init_message_loader = __esm(() => {
|
|
|
9496
9606
|
init_messages2();
|
|
9497
9607
|
init_shell();
|
|
9498
9608
|
init_shell2();
|
|
9609
|
+
init_ceremony();
|
|
9610
|
+
init_ceremony2();
|
|
9499
9611
|
BUNDLED_MESSAGES = {
|
|
9500
|
-
en: { ...messages_default, shell: shell_default },
|
|
9501
|
-
"zh-TW": {
|
|
9612
|
+
en: { ...messages_default, shell: shell_default, ceremony: ceremony_default },
|
|
9613
|
+
"zh-TW": {
|
|
9614
|
+
...messages_default2,
|
|
9615
|
+
shell: shell_default2,
|
|
9616
|
+
ceremony: ceremony_default2
|
|
9617
|
+
}
|
|
9502
9618
|
};
|
|
9503
9619
|
messageLoader = MessageLoader.getInstance();
|
|
9504
9620
|
});
|
|
@@ -9648,7 +9764,8 @@ async function readLineFromStdin(prompt = "") {
|
|
|
9648
9764
|
}
|
|
9649
9765
|
async function textFallback(message, defaultValue) {
|
|
9650
9766
|
const displayMessage = defaultValue ? `${message} [${defaultValue}]: ` : `${message}: `;
|
|
9651
|
-
|
|
9767
|
+
process.stderr.write(displayMessage);
|
|
9768
|
+
const answer = await readLineFromStdin();
|
|
9652
9769
|
return answer.trim() || defaultValue || "";
|
|
9653
9770
|
}
|
|
9654
9771
|
async function selectFallback(message, choices) {
|
|
@@ -9664,7 +9781,8 @@ async function selectFallback(message, choices) {
|
|
|
9664
9781
|
return choices[0] ?? "";
|
|
9665
9782
|
}
|
|
9666
9783
|
async function confirmFallback(message) {
|
|
9667
|
-
|
|
9784
|
+
process.stderr.write(`${message} (y/n): `);
|
|
9785
|
+
const answer = await readLineFromStdin();
|
|
9668
9786
|
return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
|
|
9669
9787
|
}
|
|
9670
9788
|
async function text(message, defaultValue) {
|
|
@@ -9673,7 +9791,7 @@ async function text(message, defaultValue) {
|
|
|
9673
9791
|
const inquirer = await loadInquirer();
|
|
9674
9792
|
if (!inquirer)
|
|
9675
9793
|
return textFallback(message, defaultValue);
|
|
9676
|
-
return await inquirer.input({ message, default: defaultValue });
|
|
9794
|
+
return await inquirer.input({ message, default: defaultValue }, { output: process.stderr });
|
|
9677
9795
|
}
|
|
9678
9796
|
async function select(message, choices) {
|
|
9679
9797
|
if (!process.stdin.isTTY)
|
|
@@ -9696,7 +9814,7 @@ async function confirm(message) {
|
|
|
9696
9814
|
const inquirer = await loadInquirer();
|
|
9697
9815
|
if (!inquirer)
|
|
9698
9816
|
return await confirmFallback(message);
|
|
9699
|
-
return await inquirer.confirm({ message });
|
|
9817
|
+
return await inquirer.confirm({ message }, { output: process.stderr });
|
|
9700
9818
|
}
|
|
9701
9819
|
var promptsUnavailableReported = false, promptUser;
|
|
9702
9820
|
var init_prompts = __esm(() => {
|
|
@@ -18555,33 +18673,7 @@ var init_sql_lexical = __esm(() => {
|
|
|
18555
18673
|
DOLLAR_QUOTE_DELIMITER = /^\$(?:(?:[A-Za-z_]|[\u0080-\uFFFF])(?:[A-Za-z0-9_]|[\u0080-\uFFFF])*)?\$/;
|
|
18556
18674
|
});
|
|
18557
18675
|
|
|
18558
|
-
// src/core/permission-
|
|
18559
|
-
var exports_permission_guard = {};
|
|
18560
|
-
__export(exports_permission_guard, {
|
|
18561
|
-
toSqlDialect: () => toSqlDialect,
|
|
18562
|
-
stripCommentsAndStrings: () => stripCommentsAndStrings,
|
|
18563
|
-
removeParameterMarkers: () => removeParameterMarkers,
|
|
18564
|
-
permissionAtLeast: () => permissionAtLeast,
|
|
18565
|
-
normalizeSQL: () => normalizeSQL,
|
|
18566
|
-
mapKeywordToType: () => mapKeywordToType,
|
|
18567
|
-
isDestructiveOperation: () => isDestructiveOperation,
|
|
18568
|
-
findWriteKeyword: () => findWriteKeyword,
|
|
18569
|
-
extractFirstKeyword: () => extractFirstKeyword,
|
|
18570
|
-
extractAllKeywords: () => extractAllKeywords,
|
|
18571
|
-
enforceRedisPermission: () => enforceRedisPermission,
|
|
18572
|
-
enforcePermission: () => enforcePermission,
|
|
18573
|
-
enforceElasticsearchPermission: () => enforceElasticsearchPermission,
|
|
18574
|
-
determineConfidence: () => determineConfidence,
|
|
18575
|
-
detectCompositePatterns: () => detectCompositePatterns,
|
|
18576
|
-
containsMultipleStatements: () => containsMultipleStatements,
|
|
18577
|
-
classifyStatement: () => classifyStatement,
|
|
18578
|
-
classifyRedisCommand: () => classifyRedisCommand,
|
|
18579
|
-
classifyElasticsearchRequest: () => classifyElasticsearchRequest,
|
|
18580
|
-
checkPermission: () => checkPermission,
|
|
18581
|
-
SQL_WRITE_OR_DDL_KEYWORDS: () => SQL_WRITE_OR_DDL_KEYWORDS,
|
|
18582
|
-
SQL_DIALECTS: () => SQL_DIALECTS,
|
|
18583
|
-
PermissionError: () => PermissionError
|
|
18584
|
-
});
|
|
18676
|
+
// src/core/permission/sql-analysis.ts
|
|
18585
18677
|
function normalizeSQL(sql) {
|
|
18586
18678
|
return sql.replace(/--[^\n]*\n/g, `
|
|
18587
18679
|
`).replace(/\/\*[\s\S]*?\*\//g, " ").trim().replace(/\s+/g, " ");
|
|
@@ -18828,6 +18920,29 @@ function determineConfidence(type, _keyword, _sql) {
|
|
|
18828
18920
|
function removeParameterMarkers(sql) {
|
|
18829
18921
|
return sql.replace(/\$\d+/g, "").replace(/\?/g, "");
|
|
18830
18922
|
}
|
|
18923
|
+
var init_sql_analysis = __esm(() => {
|
|
18924
|
+
init_sql_lexical();
|
|
18925
|
+
});
|
|
18926
|
+
|
|
18927
|
+
// src/core/permission-guard.ts
|
|
18928
|
+
var exports_permission_guard = {};
|
|
18929
|
+
__export(exports_permission_guard, {
|
|
18930
|
+
toSqlDialect: () => toSqlDialect,
|
|
18931
|
+
permitsOperation: () => permitsOperation,
|
|
18932
|
+
permissionAtLeast: () => permissionAtLeast,
|
|
18933
|
+
minimumPermissionFor: () => minimumPermissionFor,
|
|
18934
|
+
findWriteKeyword: () => findWriteKeyword,
|
|
18935
|
+
enforcePermissionForType: () => enforcePermissionForType,
|
|
18936
|
+
enforcePermission: () => enforcePermission,
|
|
18937
|
+
containsMultipleStatements: () => containsMultipleStatements,
|
|
18938
|
+
classifyStatement: () => classifyStatement,
|
|
18939
|
+
classificationForType: () => classificationForType,
|
|
18940
|
+
checkPermissionForClassification: () => checkPermissionForClassification,
|
|
18941
|
+
checkPermission: () => checkPermission,
|
|
18942
|
+
SQL_WRITE_OR_DDL_KEYWORDS: () => SQL_WRITE_OR_DDL_KEYWORDS,
|
|
18943
|
+
SQL_DIALECTS: () => SQL_DIALECTS,
|
|
18944
|
+
PermissionError: () => PermissionError
|
|
18945
|
+
});
|
|
18831
18946
|
function classifyStatement(sql) {
|
|
18832
18947
|
const normalized = normalizeSQL(sql);
|
|
18833
18948
|
const stripped = stripCommentsAndStrings(normalized);
|
|
@@ -18893,15 +19008,33 @@ function checkPermission(sql, permission, dialect) {
|
|
|
18893
19008
|
if (permission !== "admin" && containsMultipleStatements(sql, dialect)) {
|
|
18894
19009
|
return {
|
|
18895
19010
|
allowed: false,
|
|
18896
|
-
reason: "
|
|
18897
|
-
classification
|
|
19011
|
+
reason: t("errors.multiple_statements_refused"),
|
|
19012
|
+
classification,
|
|
19013
|
+
requiredPermission: "admin"
|
|
18898
19014
|
};
|
|
18899
19015
|
}
|
|
19016
|
+
return checkPermissionForClassification(classification, permission);
|
|
19017
|
+
}
|
|
19018
|
+
function minimumPermissionFor(type) {
|
|
19019
|
+
return TIER_GRANTS.find((tier) => tier.types.includes(type))?.permission ?? "admin";
|
|
19020
|
+
}
|
|
19021
|
+
function refusalReason(type, permission) {
|
|
19022
|
+
return t_vars("errors.permission_requires_level", {
|
|
19023
|
+
type,
|
|
19024
|
+
minimum: minimumPermissionFor(type),
|
|
19025
|
+
permission
|
|
19026
|
+
});
|
|
19027
|
+
}
|
|
19028
|
+
function checkPermissionForClassification(classification, permission) {
|
|
18900
19029
|
if (permission !== "admin" && classification.escalatedFrom) {
|
|
18901
19030
|
return {
|
|
18902
19031
|
allowed: false,
|
|
18903
|
-
reason:
|
|
18904
|
-
|
|
19032
|
+
reason: t_vars("errors.escalated_write_requires_admin", {
|
|
19033
|
+
keyword: classification.escalatedFrom,
|
|
19034
|
+
permission
|
|
19035
|
+
}),
|
|
19036
|
+
classification,
|
|
19037
|
+
requiredPermission: "admin"
|
|
18905
19038
|
};
|
|
18906
19039
|
}
|
|
18907
19040
|
if (permission === "admin") {
|
|
@@ -18911,220 +19044,64 @@ function checkPermission(sql, permission, dialect) {
|
|
|
18911
19044
|
classification
|
|
18912
19045
|
};
|
|
18913
19046
|
}
|
|
18914
|
-
|
|
18915
|
-
|
|
19047
|
+
const tierIndex = TIER_GRANTS.findIndex((tier) => tier.permission === permission);
|
|
19048
|
+
if (tierIndex !== -1) {
|
|
19049
|
+
const allowedTypes = TIER_GRANTS.slice(0, tierIndex + 1).flatMap((tier) => tier.types);
|
|
18916
19050
|
if (allowedTypes.includes(classification.type)) {
|
|
18917
19051
|
return {
|
|
18918
19052
|
allowed: true,
|
|
18919
|
-
reason: `${classification.type} operation allowed in
|
|
19053
|
+
reason: `${classification.type} operation allowed in ${permission} mode`,
|
|
18920
19054
|
classification
|
|
18921
19055
|
};
|
|
18922
19056
|
}
|
|
19057
|
+
const isUnknown = permission === "query-only" && classification.type === "UNKNOWN";
|
|
18923
19058
|
return {
|
|
18924
19059
|
allowed: false,
|
|
18925
|
-
reason:
|
|
18926
|
-
classification
|
|
18927
|
-
|
|
18928
|
-
}
|
|
18929
|
-
if (permission === "read-write") {
|
|
18930
|
-
const allowedTypes = ["SELECT", "INSERT", "UPDATE", "SHOW", "DESCRIBE", "EXPLAIN"];
|
|
18931
|
-
if (allowedTypes.includes(classification.type)) {
|
|
18932
|
-
return {
|
|
18933
|
-
allowed: true,
|
|
18934
|
-
reason: `${classification.type} operation allowed in read-write mode`,
|
|
18935
|
-
classification
|
|
18936
|
-
};
|
|
18937
|
-
}
|
|
18938
|
-
return {
|
|
18939
|
-
allowed: false,
|
|
18940
|
-
reason: `${classification.type} operation requires data-admin or admin permission`,
|
|
18941
|
-
classification
|
|
18942
|
-
};
|
|
18943
|
-
}
|
|
18944
|
-
if (permission === "query-only") {
|
|
18945
|
-
const allowedTypes = ["SELECT", "SHOW", "DESCRIBE", "EXPLAIN"];
|
|
18946
|
-
if (allowedTypes.includes(classification.type)) {
|
|
18947
|
-
return {
|
|
18948
|
-
allowed: true,
|
|
18949
|
-
reason: `${classification.type} operation allowed in query-only mode`,
|
|
18950
|
-
classification
|
|
18951
|
-
};
|
|
18952
|
-
}
|
|
18953
|
-
const isUnknown = classification.type === "UNKNOWN";
|
|
18954
|
-
return {
|
|
18955
|
-
allowed: false,
|
|
18956
|
-
reason: isUnknown ? `Unrecognised SQL statement (current level: query-only). Security policy requires read-write+ for unknown statements. If this is a legitimate read-only statement, please open an issue at https://github.com/CarlLee1983/dbcli/issues.` : `${classification.type} operation requires read-write or admin permission`,
|
|
18957
|
-
classification
|
|
19060
|
+
reason: isUnknown ? t("errors.unknown_statement_query_only") : refusalReason(classification.type, permission),
|
|
19061
|
+
classification,
|
|
19062
|
+
requiredPermission: isUnknown ? "read-write" : minimumPermissionFor(classification.type)
|
|
18958
19063
|
};
|
|
18959
19064
|
}
|
|
18960
19065
|
return {
|
|
18961
19066
|
allowed: false,
|
|
18962
19067
|
reason: `Unknown permission level: ${permission}`,
|
|
18963
|
-
classification
|
|
19068
|
+
classification,
|
|
19069
|
+
requiredPermission: "admin"
|
|
18964
19070
|
};
|
|
18965
19071
|
}
|
|
18966
|
-
function
|
|
18967
|
-
const result = checkPermission(sql, permission, dialect);
|
|
18968
|
-
if (!result.allowed) {
|
|
18969
|
-
throw new PermissionError(result.reason, result.classification, permission);
|
|
18970
|
-
}
|
|
18971
|
-
return result.classification;
|
|
18972
|
-
}
|
|
18973
|
-
function classifyRedisCommand(command) {
|
|
18974
|
-
const head = command.trim().split(/\s+/)[0]?.toUpperCase() ?? "";
|
|
18975
|
-
const required = REDIS_COMMAND_PERMISSION[head];
|
|
18976
|
-
let type;
|
|
18977
|
-
if (!required)
|
|
18978
|
-
type = "UNKNOWN";
|
|
18979
|
-
else if (required === "query-only")
|
|
18980
|
-
type = "SELECT";
|
|
18981
|
-
else if (required === "read-write")
|
|
18982
|
-
type = "UPDATE";
|
|
18983
|
-
else if (required === "data-admin")
|
|
18984
|
-
type = "DELETE";
|
|
18985
|
-
else
|
|
18986
|
-
type = "DROP";
|
|
19072
|
+
function classificationForType(type) {
|
|
18987
19073
|
return {
|
|
18988
|
-
command: head,
|
|
18989
|
-
requiredPermission: required ?? "unknown",
|
|
18990
19074
|
type,
|
|
18991
|
-
isDangerous:
|
|
18992
|
-
|
|
18993
|
-
}
|
|
18994
|
-
function permissionAtLeast(actual, required) {
|
|
18995
|
-
return PERMISSION_RANK[actual] >= PERMISSION_RANK[required];
|
|
18996
|
-
}
|
|
18997
|
-
function enforceRedisPermission(command, permission) {
|
|
18998
|
-
const classification = classifyRedisCommand(command);
|
|
18999
|
-
const required = classification.requiredPermission;
|
|
19000
|
-
const stmt = {
|
|
19001
|
-
type: classification.type,
|
|
19002
|
-
isDangerous: classification.isDangerous,
|
|
19003
|
-
keywords: [classification.command],
|
|
19075
|
+
isDangerous: isDestructiveOperation(type),
|
|
19076
|
+
keywords: [type],
|
|
19004
19077
|
isComposite: false,
|
|
19005
|
-
confidence:
|
|
19078
|
+
confidence: "HIGH"
|
|
19006
19079
|
};
|
|
19007
|
-
if (required === "unknown") {
|
|
19008
|
-
throw new PermissionError(`Redis command "${classification.command}" is not whitelisted; refusing to execute`, stmt, "admin");
|
|
19009
|
-
}
|
|
19010
|
-
if (!permissionAtLeast(permission, required)) {
|
|
19011
|
-
throw new PermissionError(`Redis command "${classification.command}" requires ${required} permission`, stmt, required);
|
|
19012
|
-
}
|
|
19013
|
-
return classification;
|
|
19014
19080
|
}
|
|
19015
|
-
function
|
|
19016
|
-
|
|
19017
|
-
const path = request.apiPath.toLowerCase();
|
|
19018
|
-
if (path.includes("_bulk")) {
|
|
19019
|
-
return classifyElasticsearchBulk(request.body ?? "");
|
|
19020
|
-
}
|
|
19021
|
-
if (path.includes("_search") || path.includes("_count") || path.includes("_mapping") || path.includes("_settings") || path.includes("_alias") || method === "GET" && (path.includes("_doc") || path.includes("_source"))) {
|
|
19022
|
-
return {
|
|
19023
|
-
type: "SELECT",
|
|
19024
|
-
isDangerous: false,
|
|
19025
|
-
keywords: [method, path],
|
|
19026
|
-
isComposite: false,
|
|
19027
|
-
confidence: "HIGH"
|
|
19028
|
-
};
|
|
19029
|
-
}
|
|
19030
|
-
if (path.includes("_update") || method === "POST" && path.includes("_doc")) {
|
|
19031
|
-
return {
|
|
19032
|
-
type: "UPDATE",
|
|
19033
|
-
isDangerous: false,
|
|
19034
|
-
keywords: [method, path],
|
|
19035
|
-
isComposite: false,
|
|
19036
|
-
confidence: "HIGH"
|
|
19037
|
-
};
|
|
19038
|
-
}
|
|
19039
|
-
if (method === "PUT" && (path.includes("_doc") || path.includes("_create"))) {
|
|
19040
|
-
return {
|
|
19041
|
-
type: "INSERT",
|
|
19042
|
-
isDangerous: false,
|
|
19043
|
-
keywords: [method, path],
|
|
19044
|
-
isComposite: false,
|
|
19045
|
-
confidence: "HIGH"
|
|
19046
|
-
};
|
|
19047
|
-
}
|
|
19048
|
-
if (method === "DELETE") {
|
|
19049
|
-
return {
|
|
19050
|
-
type: "DELETE",
|
|
19051
|
-
isDangerous: true,
|
|
19052
|
-
keywords: [method, path],
|
|
19053
|
-
isComposite: false,
|
|
19054
|
-
confidence: "HIGH"
|
|
19055
|
-
};
|
|
19056
|
-
}
|
|
19057
|
-
return {
|
|
19058
|
-
type: "DROP",
|
|
19059
|
-
isDangerous: true,
|
|
19060
|
-
keywords: [method, path],
|
|
19061
|
-
isComposite: false,
|
|
19062
|
-
confidence: "LOW"
|
|
19063
|
-
};
|
|
19081
|
+
function permitsOperation(type, permission) {
|
|
19082
|
+
return checkPermissionForClassification(classificationForType(type), permission).allowed;
|
|
19064
19083
|
}
|
|
19065
|
-
function
|
|
19066
|
-
const
|
|
19067
|
-
|
|
19068
|
-
|
|
19069
|
-
|
|
19070
|
-
for (const line of lines) {
|
|
19071
|
-
try {
|
|
19072
|
-
const action = JSON.parse(line);
|
|
19073
|
-
const op = Object.keys(action)[0];
|
|
19074
|
-
if (op === "delete") {
|
|
19075
|
-
highestType = "DELETE";
|
|
19076
|
-
isDangerous = true;
|
|
19077
|
-
break;
|
|
19078
|
-
}
|
|
19079
|
-
if (op === "update" && highestType !== "DELETE") {
|
|
19080
|
-
highestType = "UPDATE";
|
|
19081
|
-
}
|
|
19082
|
-
if ((op === "index" || op === "create") && !["DELETE", "UPDATE"].includes(highestType)) {
|
|
19083
|
-
highestType = "INSERT";
|
|
19084
|
-
}
|
|
19085
|
-
} catch {}
|
|
19084
|
+
function enforcePermissionForType(type, permission) {
|
|
19085
|
+
const classification = classificationForType(type);
|
|
19086
|
+
const result = checkPermissionForClassification(classification, permission);
|
|
19087
|
+
if (!result.allowed) {
|
|
19088
|
+
throw new PermissionError(result.reason, classification, minimumPermissionFor(type));
|
|
19086
19089
|
}
|
|
19087
|
-
return {
|
|
19088
|
-
type: highestType,
|
|
19089
|
-
isDangerous,
|
|
19090
|
-
keywords: ["BULK"],
|
|
19091
|
-
isComposite: true,
|
|
19092
|
-
confidence: "HIGH"
|
|
19093
|
-
};
|
|
19094
19090
|
}
|
|
19095
|
-
function
|
|
19096
|
-
const
|
|
19097
|
-
const result = checkElasticsearchPermission(classification, permission);
|
|
19091
|
+
function enforcePermission(sql, permission, dialect) {
|
|
19092
|
+
const result = checkPermission(sql, permission, dialect);
|
|
19098
19093
|
if (!result.allowed) {
|
|
19099
|
-
throw new PermissionError(result.reason, classification,
|
|
19094
|
+
throw new PermissionError(result.reason, result.classification, result.requiredPermission ?? "admin");
|
|
19100
19095
|
}
|
|
19101
|
-
return classification;
|
|
19096
|
+
return result.classification;
|
|
19102
19097
|
}
|
|
19103
|
-
function
|
|
19104
|
-
|
|
19105
|
-
return { allowed: true, reason: "Admin" };
|
|
19106
|
-
if (permission === "data-admin") {
|
|
19107
|
-
const allowed = ["SELECT", "INSERT", "UPDATE", "DELETE"];
|
|
19108
|
-
if (allowed.includes(classification.type))
|
|
19109
|
-
return { allowed: true, reason: "Data-Admin" };
|
|
19110
|
-
}
|
|
19111
|
-
if (permission === "read-write") {
|
|
19112
|
-
const allowed = ["SELECT", "INSERT", "UPDATE"];
|
|
19113
|
-
if (allowed.includes(classification.type))
|
|
19114
|
-
return { allowed: true, reason: "Read-Write" };
|
|
19115
|
-
}
|
|
19116
|
-
if (permission === "query-only") {
|
|
19117
|
-
if (classification.type === "SELECT")
|
|
19118
|
-
return { allowed: true, reason: "Query-Only" };
|
|
19119
|
-
}
|
|
19120
|
-
return {
|
|
19121
|
-
allowed: false,
|
|
19122
|
-
reason: `Elasticsearch ${classification.type} operation requires higher permission tier`
|
|
19123
|
-
};
|
|
19098
|
+
function permissionAtLeast(actual, required) {
|
|
19099
|
+
return PERMISSION_RANK[actual] >= PERMISSION_RANK[required];
|
|
19124
19100
|
}
|
|
19125
|
-
var PermissionError, SQL_DIALECTS, SQL_WRITE_OR_DDL_KEYWORDS, SQL_LOCK_CLAUSE, ESCALATABLE_READ_TYPES,
|
|
19101
|
+
var PermissionError, SQL_DIALECTS, SQL_WRITE_OR_DDL_KEYWORDS, SQL_LOCK_CLAUSE, ESCALATABLE_READ_TYPES, TIER_GRANTS, PERMISSION_RANK;
|
|
19126
19102
|
var init_permission_guard = __esm(() => {
|
|
19127
|
-
|
|
19103
|
+
init_message_loader();
|
|
19104
|
+
init_sql_analysis();
|
|
19128
19105
|
PermissionError = class PermissionError extends Error {
|
|
19129
19106
|
classification;
|
|
19130
19107
|
requiredPermission;
|
|
@@ -19140,90 +19117,11 @@ var init_permission_guard = __esm(() => {
|
|
|
19140
19117
|
SQL_WRITE_OR_DDL_KEYWORDS = /(?<![.\w])(INSERT|UPDATE|DELETE|MERGE|UPSERT|REPLACE|TRUNCATE|DROP|ALTER|CREATE|GRANT|REVOKE|RENAME|INTO)\b(?!\s*\()/i;
|
|
19141
19118
|
SQL_LOCK_CLAUSE = /\bFOR\s+(?:NO\s+KEY\s+)?UPDATE\b|\bFOR\s+(?:KEY\s+)?SHARE\b/gi;
|
|
19142
19119
|
ESCALATABLE_READ_TYPES = new Set(["SELECT", "EXPLAIN", "DESCRIBE"]);
|
|
19143
|
-
|
|
19144
|
-
|
|
19145
|
-
|
|
19146
|
-
|
|
19147
|
-
|
|
19148
|
-
TTL: "query-only",
|
|
19149
|
-
PTTL: "query-only",
|
|
19150
|
-
TYPE: "query-only",
|
|
19151
|
-
SCAN: "query-only",
|
|
19152
|
-
HGET: "query-only",
|
|
19153
|
-
HGETALL: "query-only",
|
|
19154
|
-
HKEYS: "query-only",
|
|
19155
|
-
HVALS: "query-only",
|
|
19156
|
-
HLEN: "query-only",
|
|
19157
|
-
HEXISTS: "query-only",
|
|
19158
|
-
HMGET: "query-only",
|
|
19159
|
-
LRANGE: "query-only",
|
|
19160
|
-
LLEN: "query-only",
|
|
19161
|
-
LINDEX: "query-only",
|
|
19162
|
-
SMEMBERS: "query-only",
|
|
19163
|
-
SCARD: "query-only",
|
|
19164
|
-
SISMEMBER: "query-only",
|
|
19165
|
-
ZRANGE: "query-only",
|
|
19166
|
-
ZREVRANGE: "query-only",
|
|
19167
|
-
ZRANGEBYSCORE: "query-only",
|
|
19168
|
-
ZCARD: "query-only",
|
|
19169
|
-
ZSCORE: "query-only",
|
|
19170
|
-
PING: "query-only",
|
|
19171
|
-
ECHO: "query-only",
|
|
19172
|
-
SET: "read-write",
|
|
19173
|
-
SETEX: "read-write",
|
|
19174
|
-
SETNX: "read-write",
|
|
19175
|
-
PSETEX: "read-write",
|
|
19176
|
-
MSET: "read-write",
|
|
19177
|
-
MSETNX: "read-write",
|
|
19178
|
-
APPEND: "read-write",
|
|
19179
|
-
INCR: "read-write",
|
|
19180
|
-
INCRBY: "read-write",
|
|
19181
|
-
DECR: "read-write",
|
|
19182
|
-
DECRBY: "read-write",
|
|
19183
|
-
HSET: "read-write",
|
|
19184
|
-
HSETNX: "read-write",
|
|
19185
|
-
HMSET: "read-write",
|
|
19186
|
-
HINCRBY: "read-write",
|
|
19187
|
-
LPUSH: "read-write",
|
|
19188
|
-
RPUSH: "read-write",
|
|
19189
|
-
LPOP: "read-write",
|
|
19190
|
-
RPOP: "read-write",
|
|
19191
|
-
LSET: "read-write",
|
|
19192
|
-
LREM: "read-write",
|
|
19193
|
-
SADD: "read-write",
|
|
19194
|
-
SREM: "read-write",
|
|
19195
|
-
ZADD: "read-write",
|
|
19196
|
-
ZREM: "read-write",
|
|
19197
|
-
XADD: "read-write",
|
|
19198
|
-
XDEL: "data-admin",
|
|
19199
|
-
XLEN: "query-only",
|
|
19200
|
-
XREAD: "query-only",
|
|
19201
|
-
XRANGE: "query-only",
|
|
19202
|
-
XREVRANGE: "query-only",
|
|
19203
|
-
EXPIRE: "read-write",
|
|
19204
|
-
EXPIREAT: "read-write",
|
|
19205
|
-
PEXPIRE: "read-write",
|
|
19206
|
-
PERSIST: "read-write",
|
|
19207
|
-
RENAME: "read-write",
|
|
19208
|
-
DEL: "data-admin",
|
|
19209
|
-
UNLINK: "data-admin",
|
|
19210
|
-
HDEL: "data-admin",
|
|
19211
|
-
FLUSHDB: "admin",
|
|
19212
|
-
FLUSHALL: "admin",
|
|
19213
|
-
CONFIG: "admin",
|
|
19214
|
-
INFO: "admin",
|
|
19215
|
-
CLIENT: "admin",
|
|
19216
|
-
DEBUG: "admin",
|
|
19217
|
-
SHUTDOWN: "admin",
|
|
19218
|
-
KEYS: "admin",
|
|
19219
|
-
MONITOR: "admin",
|
|
19220
|
-
SAVE: "admin",
|
|
19221
|
-
BGSAVE: "admin",
|
|
19222
|
-
BGREWRITEAOF: "admin",
|
|
19223
|
-
REPLICAOF: "admin",
|
|
19224
|
-
SLAVEOF: "admin",
|
|
19225
|
-
ACL: "admin"
|
|
19226
|
-
};
|
|
19120
|
+
TIER_GRANTS = [
|
|
19121
|
+
{ permission: "query-only", types: ["SELECT", "SHOW", "DESCRIBE", "EXPLAIN"] },
|
|
19122
|
+
{ permission: "read-write", types: ["INSERT", "UPDATE"] },
|
|
19123
|
+
{ permission: "data-admin", types: ["DELETE"] }
|
|
19124
|
+
];
|
|
19227
19125
|
PERMISSION_RANK = {
|
|
19228
19126
|
"query-only": 1,
|
|
19229
19127
|
"read-write": 2,
|
|
@@ -21914,6 +21812,7 @@ function inferColumnType(value) {
|
|
|
21914
21812
|
}
|
|
21915
21813
|
var init_query_executor = __esm(() => {
|
|
21916
21814
|
init_permission_guard();
|
|
21815
|
+
init_sql_analysis();
|
|
21917
21816
|
init_error_suggester();
|
|
21918
21817
|
init_sql_tables();
|
|
21919
21818
|
init_applied_limit();
|
|
@@ -22213,6 +22112,617 @@ function assertFanOutReadOnlySql(sql, dialect) {
|
|
|
22213
22112
|
var init_query_fanout = __esm(() => {
|
|
22214
22113
|
init_cli_error();
|
|
22215
22114
|
init_permission_guard();
|
|
22115
|
+
init_sql_analysis();
|
|
22116
|
+
});
|
|
22117
|
+
|
|
22118
|
+
// src/core/permission/elasticsearch.ts
|
|
22119
|
+
function classifyElasticsearchRequest(request) {
|
|
22120
|
+
const method = request.method.toUpperCase();
|
|
22121
|
+
const path2 = request.apiPath.toLowerCase();
|
|
22122
|
+
if (path2.includes("_bulk")) {
|
|
22123
|
+
return classifyElasticsearchBulk(request.body ?? "");
|
|
22124
|
+
}
|
|
22125
|
+
const readMethod = method === "GET" || method === "HEAD";
|
|
22126
|
+
const searchPath = path2.includes("_search") || path2.includes("_count");
|
|
22127
|
+
if (searchPath && (readMethod || method === "POST") || readMethod && (path2.includes("_mapping") || path2.includes("_settings") || path2.includes("_alias") || path2.includes("_doc") || path2.includes("_source"))) {
|
|
22128
|
+
return {
|
|
22129
|
+
type: "SELECT",
|
|
22130
|
+
isDangerous: false,
|
|
22131
|
+
keywords: [method, path2],
|
|
22132
|
+
isComposite: false,
|
|
22133
|
+
confidence: "HIGH"
|
|
22134
|
+
};
|
|
22135
|
+
}
|
|
22136
|
+
if (path2.includes("_update") || method === "POST" && path2.includes("_doc")) {
|
|
22137
|
+
return {
|
|
22138
|
+
type: "UPDATE",
|
|
22139
|
+
isDangerous: false,
|
|
22140
|
+
keywords: [method, path2],
|
|
22141
|
+
isComposite: false,
|
|
22142
|
+
confidence: "HIGH"
|
|
22143
|
+
};
|
|
22144
|
+
}
|
|
22145
|
+
if (method === "PUT" && (path2.includes("_doc") || path2.includes("_create"))) {
|
|
22146
|
+
return {
|
|
22147
|
+
type: "INSERT",
|
|
22148
|
+
isDangerous: false,
|
|
22149
|
+
keywords: [method, path2],
|
|
22150
|
+
isComposite: false,
|
|
22151
|
+
confidence: "HIGH"
|
|
22152
|
+
};
|
|
22153
|
+
}
|
|
22154
|
+
if (method === "DELETE") {
|
|
22155
|
+
if (ES_DOCUMENT_PATH.test(path2)) {
|
|
22156
|
+
return {
|
|
22157
|
+
type: "DELETE",
|
|
22158
|
+
isDangerous: true,
|
|
22159
|
+
keywords: [method, path2],
|
|
22160
|
+
isComposite: false,
|
|
22161
|
+
confidence: "HIGH"
|
|
22162
|
+
};
|
|
22163
|
+
}
|
|
22164
|
+
return {
|
|
22165
|
+
type: "DROP",
|
|
22166
|
+
isDangerous: true,
|
|
22167
|
+
keywords: [method, path2],
|
|
22168
|
+
isComposite: false,
|
|
22169
|
+
confidence: "HIGH"
|
|
22170
|
+
};
|
|
22171
|
+
}
|
|
22172
|
+
return {
|
|
22173
|
+
type: "DROP",
|
|
22174
|
+
isDangerous: true,
|
|
22175
|
+
keywords: [method, path2],
|
|
22176
|
+
isComposite: false,
|
|
22177
|
+
confidence: "LOW"
|
|
22178
|
+
};
|
|
22179
|
+
}
|
|
22180
|
+
function classifyElasticsearchBulk(body) {
|
|
22181
|
+
const lines = body.split(`
|
|
22182
|
+
`).filter((l) => l.trim().length > 0);
|
|
22183
|
+
let highestType = "SELECT";
|
|
22184
|
+
let isDangerous = false;
|
|
22185
|
+
for (const line of lines) {
|
|
22186
|
+
try {
|
|
22187
|
+
const action = JSON.parse(line);
|
|
22188
|
+
const op = Object.keys(action)[0];
|
|
22189
|
+
if (op === "delete") {
|
|
22190
|
+
highestType = "DELETE";
|
|
22191
|
+
isDangerous = true;
|
|
22192
|
+
break;
|
|
22193
|
+
}
|
|
22194
|
+
if (op === "update" && highestType !== "DELETE") {
|
|
22195
|
+
highestType = "UPDATE";
|
|
22196
|
+
}
|
|
22197
|
+
if ((op === "index" || op === "create") && !["DELETE", "UPDATE"].includes(highestType)) {
|
|
22198
|
+
highestType = "INSERT";
|
|
22199
|
+
}
|
|
22200
|
+
} catch {}
|
|
22201
|
+
}
|
|
22202
|
+
return {
|
|
22203
|
+
type: highestType,
|
|
22204
|
+
isDangerous,
|
|
22205
|
+
keywords: ["BULK"],
|
|
22206
|
+
isComposite: true,
|
|
22207
|
+
confidence: "HIGH"
|
|
22208
|
+
};
|
|
22209
|
+
}
|
|
22210
|
+
function enforceElasticsearchPermission(request, permission) {
|
|
22211
|
+
const classification = classifyElasticsearchRequest(request);
|
|
22212
|
+
const result = checkElasticsearchPermission(classification, permission);
|
|
22213
|
+
if (!result.allowed) {
|
|
22214
|
+
throw new PermissionError(result.reason, classification, result.requiredPermission ?? "admin");
|
|
22215
|
+
}
|
|
22216
|
+
return classification;
|
|
22217
|
+
}
|
|
22218
|
+
function checkElasticsearchPermission(classification, permission) {
|
|
22219
|
+
if (permitsOperation(classification.type, permission)) {
|
|
22220
|
+
return { allowed: true, reason: `${classification.type} allowed in ${permission} mode` };
|
|
22221
|
+
}
|
|
22222
|
+
const minimum = minimumPermissionFor(classification.type);
|
|
22223
|
+
return {
|
|
22224
|
+
allowed: false,
|
|
22225
|
+
reason: t_vars("errors.elasticsearch_requires_level", {
|
|
22226
|
+
type: classification.type,
|
|
22227
|
+
minimum,
|
|
22228
|
+
permission
|
|
22229
|
+
}),
|
|
22230
|
+
requiredPermission: minimum
|
|
22231
|
+
};
|
|
22232
|
+
}
|
|
22233
|
+
var ES_DOCUMENT_PATH;
|
|
22234
|
+
var init_elasticsearch = __esm(() => {
|
|
22235
|
+
init_permission_guard();
|
|
22236
|
+
init_message_loader();
|
|
22237
|
+
ES_DOCUMENT_PATH = /\/(?:_doc|_source)\/[^/?]+/;
|
|
22238
|
+
});
|
|
22239
|
+
|
|
22240
|
+
// src/commands/mutation-outcome.ts
|
|
22241
|
+
var exports_mutation_outcome = {};
|
|
22242
|
+
__export(exports_mutation_outcome, {
|
|
22243
|
+
shouldRenderForHuman: () => shouldRenderForHuman,
|
|
22244
|
+
renderMutationOutcome: () => renderMutationOutcome,
|
|
22245
|
+
printMutationOutcome: () => printMutationOutcome,
|
|
22246
|
+
printMutationFailure: () => printMutationFailure,
|
|
22247
|
+
humanOutputContext: () => humanOutputContext,
|
|
22248
|
+
cancelledOutcome: () => cancelledOutcome
|
|
22249
|
+
});
|
|
22250
|
+
function shouldRenderForHuman(context) {
|
|
22251
|
+
if (context.format === "json")
|
|
22252
|
+
return false;
|
|
22253
|
+
return context.isTTY;
|
|
22254
|
+
}
|
|
22255
|
+
function humanOutputContext(options) {
|
|
22256
|
+
return { isTTY: process.stdout.isTTY === true, ...options.format && { format: options.format } };
|
|
22257
|
+
}
|
|
22258
|
+
function renderMutationOutcome(result, table, elapsedMs) {
|
|
22259
|
+
if (result.status === "error") {
|
|
22260
|
+
return [
|
|
22261
|
+
t_vars("ceremony.failed", { reason: result.error ?? t("ceremony.unknown_error") }),
|
|
22262
|
+
t("ceremony.recovery_retry")
|
|
22263
|
+
];
|
|
22264
|
+
}
|
|
22265
|
+
if (result.status === "cancelled") {
|
|
22266
|
+
return [t_vars("ceremony.cancelled", { table })];
|
|
22267
|
+
}
|
|
22268
|
+
if (result.status === "dry_run") {
|
|
22269
|
+
return [t_vars("ceremony.dry_run", { table })];
|
|
22270
|
+
}
|
|
22271
|
+
const elapsed = t_vars("ceremony.elapsed", { seconds: (elapsedMs / 1000).toFixed(2) });
|
|
22272
|
+
if (result.rows_affected === 0) {
|
|
22273
|
+
return [t_vars("ceremony.matched_nothing", { table }), elapsed];
|
|
22274
|
+
}
|
|
22275
|
+
return [
|
|
22276
|
+
t_vars(VERB_KEY[result.operation], { count: result.rows_affected, table }),
|
|
22277
|
+
elapsed,
|
|
22278
|
+
t("ceremony.recovery_none")
|
|
22279
|
+
];
|
|
22280
|
+
}
|
|
22281
|
+
function printMutationOutcome(result, table, elapsedMs, context, envelopeExtras = {}) {
|
|
22282
|
+
if (shouldRenderForHuman(context)) {
|
|
22283
|
+
const writeLine = result.status === "error" ? console.error : console.log;
|
|
22284
|
+
for (const line of renderMutationOutcome(result, table, elapsedMs))
|
|
22285
|
+
writeLine(line);
|
|
22286
|
+
return;
|
|
22287
|
+
}
|
|
22288
|
+
console.log(JSON.stringify({ ...mutationEnvelope(result), ...envelopeExtras }, null, 2));
|
|
22289
|
+
}
|
|
22290
|
+
function printMutationFailure(error, operation, context) {
|
|
22291
|
+
if (shouldRenderForHuman(context)) {
|
|
22292
|
+
console.error(t_vars("ceremony.failed", { reason: error.message }));
|
|
22293
|
+
if (error instanceof BlacklistError) {
|
|
22294
|
+
console.error(t("ceremony.blacklist_hint"));
|
|
22295
|
+
}
|
|
22296
|
+
return;
|
|
22297
|
+
}
|
|
22298
|
+
console.log(JSON.stringify({ status: "error", operation, rows_affected: 0, error: error.message }, null, 2));
|
|
22299
|
+
}
|
|
22300
|
+
function cancelledOutcome(operation, preview) {
|
|
22301
|
+
return {
|
|
22302
|
+
status: "cancelled",
|
|
22303
|
+
operation,
|
|
22304
|
+
rows_affected: 0,
|
|
22305
|
+
sql: preview,
|
|
22306
|
+
timestamp: new Date().toISOString()
|
|
22307
|
+
};
|
|
22308
|
+
}
|
|
22309
|
+
function mutationEnvelope(result) {
|
|
22310
|
+
return {
|
|
22311
|
+
status: result.status,
|
|
22312
|
+
operation: result.operation,
|
|
22313
|
+
rows_affected: result.rows_affected,
|
|
22314
|
+
...result.timestamp && { timestamp: result.timestamp },
|
|
22315
|
+
...result.sql && { sql: result.sql },
|
|
22316
|
+
...result.error && { error: result.error }
|
|
22317
|
+
};
|
|
22318
|
+
}
|
|
22319
|
+
var VERB_KEY;
|
|
22320
|
+
var init_mutation_outcome = __esm(() => {
|
|
22321
|
+
init_message_loader();
|
|
22322
|
+
init_blacklist();
|
|
22323
|
+
VERB_KEY = {
|
|
22324
|
+
insert: "ceremony.inserted",
|
|
22325
|
+
update: "ceremony.updated",
|
|
22326
|
+
delete: "ceremony.deleted"
|
|
22327
|
+
};
|
|
22328
|
+
});
|
|
22329
|
+
|
|
22330
|
+
// src/commands/write-gate-prompt.ts
|
|
22331
|
+
var exports_write_gate_prompt = {};
|
|
22332
|
+
__export(exports_write_gate_prompt, {
|
|
22333
|
+
enforceWriteGate: () => enforceWriteGate,
|
|
22334
|
+
WriteGateRefusal: () => WriteGateRefusal
|
|
22335
|
+
});
|
|
22336
|
+
async function enforceWriteGate(request) {
|
|
22337
|
+
const { verdict, human } = request;
|
|
22338
|
+
if (verdict.tier === "none")
|
|
22339
|
+
return true;
|
|
22340
|
+
const attended = isAttended(human);
|
|
22341
|
+
if (verdict.tier === "two")
|
|
22342
|
+
return await enforceTierTwo(request, attended);
|
|
22343
|
+
if (request.yes === true)
|
|
22344
|
+
return true;
|
|
22345
|
+
if (!attended)
|
|
22346
|
+
return true;
|
|
22347
|
+
writeSummary(request);
|
|
22348
|
+
const proceed = await promptUser.confirm(t("ceremony.confirm_prompt"));
|
|
22349
|
+
if (!proceed)
|
|
22350
|
+
process.stderr.write(`${t("ceremony.gate_cancelled")}
|
|
22351
|
+
`);
|
|
22352
|
+
return proceed;
|
|
22353
|
+
}
|
|
22354
|
+
function isAttended(human) {
|
|
22355
|
+
return shouldRenderForHuman(human) && process.stdin.isTTY === true;
|
|
22356
|
+
}
|
|
22357
|
+
async function enforceTierTwo(request, attended) {
|
|
22358
|
+
const { verdict } = request;
|
|
22359
|
+
const reason = verdict.reason ?? "no_where";
|
|
22360
|
+
if (!attended) {
|
|
22361
|
+
throw new WriteGateRefusal(`${t_vars("ceremony.gate_refused", { reason })} ${t(REMEDY_KEY[reason])}`, reason, verdict.table);
|
|
22362
|
+
}
|
|
22363
|
+
writeSummary(request);
|
|
22364
|
+
const warning = verdict.table === undefined && reason === "ddl_destruction" ? t("ceremony.gate_ddl_warning_untargeted") : t_vars(WARNING_KEY[reason], { table: verdict.table ?? "" });
|
|
22365
|
+
process.stderr.write(`
|
|
22366
|
+
${warning}
|
|
22367
|
+
${t(REMEDY_KEY[reason])}
|
|
22368
|
+
`);
|
|
22369
|
+
const typed = await promptUser.text(t_vars("ceremony.gate_typed_prompt", { phrase: verdict.confirmationPhrase }));
|
|
22370
|
+
const matched = typed.trim().toLowerCase() === verdict.confirmationPhrase.toLowerCase();
|
|
22371
|
+
if (!matched) {
|
|
22372
|
+
process.stderr.write(`${t_vars("ceremony.gate_typed_mismatch", { phrase: verdict.confirmationPhrase })}
|
|
22373
|
+
`);
|
|
22374
|
+
}
|
|
22375
|
+
return matched;
|
|
22376
|
+
}
|
|
22377
|
+
function writeSummary(request) {
|
|
22378
|
+
const { verdict, operation, statement } = request;
|
|
22379
|
+
const summary = verdict.table ? t_vars("ceremony.gate_summary", { operation, table: verdict.table }) : t_vars("ceremony.gate_summary_untargeted", { operation });
|
|
22380
|
+
process.stderr.write(`
|
|
22381
|
+
${summary}
|
|
22382
|
+
${t("ceremony.gate_statement")}
|
|
22383
|
+
${statement}
|
|
22384
|
+
`);
|
|
22385
|
+
}
|
|
22386
|
+
var WriteGateRefusal, REMEDY_KEY, WARNING_KEY;
|
|
22387
|
+
var init_write_gate_prompt = __esm(() => {
|
|
22388
|
+
init_message_loader();
|
|
22389
|
+
init_prompts();
|
|
22390
|
+
init_mutation_outcome();
|
|
22391
|
+
WriteGateRefusal = class WriteGateRefusal extends Error {
|
|
22392
|
+
code = "WRITE_GATE_REFUSED";
|
|
22393
|
+
reason;
|
|
22394
|
+
table;
|
|
22395
|
+
constructor(message, reason, table) {
|
|
22396
|
+
super(message);
|
|
22397
|
+
this.name = "WriteGateRefusal";
|
|
22398
|
+
this.reason = reason;
|
|
22399
|
+
this.table = table;
|
|
22400
|
+
}
|
|
22401
|
+
};
|
|
22402
|
+
REMEDY_KEY = {
|
|
22403
|
+
no_where: "ceremony.gate_remedy_no_where",
|
|
22404
|
+
ddl_destruction: "ceremony.gate_remedy_ddl_destruction",
|
|
22405
|
+
unparseable: "ceremony.gate_remedy_unparseable",
|
|
22406
|
+
non_unique_where: "ceremony.gate_remedy_non_unique_where",
|
|
22407
|
+
multiple_statements: "ceremony.gate_remedy_multiple_statements"
|
|
22408
|
+
};
|
|
22409
|
+
WARNING_KEY = {
|
|
22410
|
+
no_where: "ceremony.gate_full_table_warning",
|
|
22411
|
+
ddl_destruction: "ceremony.gate_ddl_warning",
|
|
22412
|
+
unparseable: "ceremony.gate_unparseable_warning",
|
|
22413
|
+
non_unique_where: "ceremony.gate_full_table_warning",
|
|
22414
|
+
multiple_statements: "ceremony.gate_multiple_statements_warning"
|
|
22415
|
+
};
|
|
22416
|
+
});
|
|
22417
|
+
|
|
22418
|
+
// src/core/sql-parser.ts
|
|
22419
|
+
var exports_sql_parser = {};
|
|
22420
|
+
__export(exports_sql_parser, {
|
|
22421
|
+
sqlParser: () => sqlParser
|
|
22422
|
+
});
|
|
22423
|
+
function sqlParser() {
|
|
22424
|
+
if (!cached2) {
|
|
22425
|
+
const { Parser } = __require("node-sql-parser");
|
|
22426
|
+
cached2 = new Parser;
|
|
22427
|
+
}
|
|
22428
|
+
return cached2;
|
|
22429
|
+
}
|
|
22430
|
+
var cached2 = null;
|
|
22431
|
+
|
|
22432
|
+
// src/commands/write-gate.ts
|
|
22433
|
+
var exports_write_gate = {};
|
|
22434
|
+
__export(exports_write_gate, {
|
|
22435
|
+
classifyStructuredWriteGate: () => classifyStructuredWriteGate,
|
|
22436
|
+
classifySqlWriteGate: () => classifySqlWriteGate,
|
|
22437
|
+
WRITE_GATE_FALLBACK_PHRASE: () => WRITE_GATE_FALLBACK_PHRASE
|
|
22438
|
+
});
|
|
22439
|
+
async function classifySqlWriteGate(sql, options = {}) {
|
|
22440
|
+
const { dialect } = options;
|
|
22441
|
+
const classification = classifyStatement(sql);
|
|
22442
|
+
const hiddenWrite = findWriteKeyword(sql, dialect ? [dialect] : undefined);
|
|
22443
|
+
const type = WRITE_TYPES.has(classification.type) ? classification.type : hiddenWrite ? keywordToType(hiddenWrite) : undefined;
|
|
22444
|
+
if (type === undefined)
|
|
22445
|
+
return { tier: "none", confirmationPhrase: WRITE_GATE_FALLBACK_PHRASE };
|
|
22446
|
+
if (containsMultipleStatements(sql, dialect)) {
|
|
22447
|
+
return verdict("two", "multiple_statements", undefined, type);
|
|
22448
|
+
}
|
|
22449
|
+
if (type === "DROP" || type === "TRUNCATE") {
|
|
22450
|
+
const matched = DDL_TARGET.exec(sql)?.[1];
|
|
22451
|
+
const table = matched ? matched.replace(/[`"[\]]/g, "") : undefined;
|
|
22452
|
+
return verdict("two", "ddl_destruction", table, type);
|
|
22453
|
+
}
|
|
22454
|
+
if (type !== "UPDATE" && type !== "DELETE")
|
|
22455
|
+
return verdict("one", undefined, undefined, type);
|
|
22456
|
+
return await classifyQualification(sql, dialect, type);
|
|
22457
|
+
}
|
|
22458
|
+
async function classifyQualification(sql, dialect, type) {
|
|
22459
|
+
const { sqlParser: sqlParser2 } = await Promise.resolve().then(() => exports_sql_parser);
|
|
22460
|
+
let ast;
|
|
22461
|
+
try {
|
|
22462
|
+
ast = sqlParser2().astify(sql, { database: PARSER_DIALECTS[dialect ?? "mysql"] });
|
|
22463
|
+
} catch {
|
|
22464
|
+
return unparseable(sql, dialect, type);
|
|
22465
|
+
}
|
|
22466
|
+
const statement = Array.isArray(ast) ? ast[0] : ast;
|
|
22467
|
+
if (!statement)
|
|
22468
|
+
return unparseable(sql, dialect, type);
|
|
22469
|
+
const table = tableName(statement.from?.[0] ?? statement.table?.[0]);
|
|
22470
|
+
if (statement.where != null || statement.limit != null) {
|
|
22471
|
+
return verdict("one", undefined, table, type);
|
|
22472
|
+
}
|
|
22473
|
+
return verdict("two", "no_where", table, type);
|
|
22474
|
+
}
|
|
22475
|
+
function unparseable(sql, dialect, type) {
|
|
22476
|
+
const executable = stripCommentsAndStrings(sql, dialect ? { dialect } : {});
|
|
22477
|
+
if (/\bWHERE\b|\bLIMIT\b/i.test(executable))
|
|
22478
|
+
return verdict("one", undefined, undefined, type);
|
|
22479
|
+
return verdict("two", "unparseable", undefined, type);
|
|
22480
|
+
}
|
|
22481
|
+
function classifyStructuredWriteGate(request) {
|
|
22482
|
+
const conditions = new Set(Object.keys(request.where).map((key) => key.toLowerCase()));
|
|
22483
|
+
const identifies = uniqueSelectors(request.schema).some((selector) => selector.length > 0 && selector.every((column) => conditions.has(column.toLowerCase())));
|
|
22484
|
+
if (identifies)
|
|
22485
|
+
return verdict("one", undefined, request.table);
|
|
22486
|
+
return verdict("two", "non_unique_where", request.table);
|
|
22487
|
+
}
|
|
22488
|
+
function uniqueSelectors(schema) {
|
|
22489
|
+
const declared = schema.primaryKey;
|
|
22490
|
+
const primaryKey = Array.isArray(declared) ? declared : typeof declared === "string" ? [declared] : (schema.columns ?? []).filter((column) => column.primaryKey).map((column) => column.name);
|
|
22491
|
+
const unique = (schema.indexes ?? []).filter((index) => index.unique).map((index) => index.columns);
|
|
22492
|
+
return [primaryKey, ...unique];
|
|
22493
|
+
}
|
|
22494
|
+
function tableName(ref) {
|
|
22495
|
+
if (!ref?.table)
|
|
22496
|
+
return;
|
|
22497
|
+
return ref.db ? `${ref.db}.${ref.table}` : ref.table;
|
|
22498
|
+
}
|
|
22499
|
+
function keywordToType(keyword) {
|
|
22500
|
+
const upper = keyword.toUpperCase();
|
|
22501
|
+
if (WRITE_TYPES.has(upper))
|
|
22502
|
+
return upper;
|
|
22503
|
+
return "INSERT";
|
|
22504
|
+
}
|
|
22505
|
+
function verdict(tier, reason, table, operation) {
|
|
22506
|
+
return {
|
|
22507
|
+
tier,
|
|
22508
|
+
...reason && { reason },
|
|
22509
|
+
...table && { table },
|
|
22510
|
+
...operation && { operation },
|
|
22511
|
+
confirmationPhrase: table ?? WRITE_GATE_FALLBACK_PHRASE
|
|
22512
|
+
};
|
|
22513
|
+
}
|
|
22514
|
+
var WRITE_GATE_FALLBACK_PHRASE = "CONFIRM", PARSER_DIALECTS, WRITE_TYPES, DDL_TARGET;
|
|
22515
|
+
var init_write_gate = __esm(() => {
|
|
22516
|
+
init_permission_guard();
|
|
22517
|
+
init_sql_analysis();
|
|
22518
|
+
PARSER_DIALECTS = {
|
|
22519
|
+
mysql: "MySQL",
|
|
22520
|
+
mariadb: "MariaDB",
|
|
22521
|
+
postgresql: "Postgresql"
|
|
22522
|
+
};
|
|
22523
|
+
WRITE_TYPES = new Set([
|
|
22524
|
+
"INSERT",
|
|
22525
|
+
"UPDATE",
|
|
22526
|
+
"DELETE",
|
|
22527
|
+
"TRUNCATE",
|
|
22528
|
+
"DROP",
|
|
22529
|
+
"ALTER",
|
|
22530
|
+
"CREATE"
|
|
22531
|
+
]);
|
|
22532
|
+
DDL_TARGET = /\b(?:DROP\s+TABLE|TRUNCATE(?:\s+TABLE)?)\s+(?:IF\s+EXISTS\s+)?([`"[\]\w$.]+)\s*;?\s*$/i;
|
|
22533
|
+
});
|
|
22534
|
+
|
|
22535
|
+
// src/commands/write-gate-guard.ts
|
|
22536
|
+
var exports_write_gate_guard = {};
|
|
22537
|
+
__export(exports_write_gate_guard, {
|
|
22538
|
+
recordGateDecision: () => recordGateDecision,
|
|
22539
|
+
guardStructuredWrite: () => guardStructuredWrite
|
|
22540
|
+
});
|
|
22541
|
+
async function recordGateDecision(request) {
|
|
22542
|
+
await writeAuditEntry(request.config, request.command, request.options, {
|
|
22543
|
+
success: request.outcome === "allowed",
|
|
22544
|
+
...request.sql && { sql: request.sql },
|
|
22545
|
+
...request.verdict.table && { target: request.verdict.table },
|
|
22546
|
+
metadata: {
|
|
22547
|
+
write_gate_tier: request.tier,
|
|
22548
|
+
write_gate_outcome: request.outcome,
|
|
22549
|
+
write_gate_reason: request.verdict.reason ?? "no_where"
|
|
22550
|
+
}
|
|
22551
|
+
});
|
|
22552
|
+
}
|
|
22553
|
+
async function guardStructuredWrite(request) {
|
|
22554
|
+
const verdict2 = classifyStructuredWriteGate({
|
|
22555
|
+
table: request.table,
|
|
22556
|
+
where: request.where,
|
|
22557
|
+
schema: request.schema
|
|
22558
|
+
});
|
|
22559
|
+
if (verdict2.tier !== "two")
|
|
22560
|
+
return true;
|
|
22561
|
+
const operation = request.operation.toUpperCase();
|
|
22562
|
+
const preview = `${operation} ${request.table} WHERE ${Object.keys(request.where).join(", ")}`;
|
|
22563
|
+
const record = (outcome) => recordGateDecision({
|
|
22564
|
+
config: request.config,
|
|
22565
|
+
command: request.operation,
|
|
22566
|
+
options: request.options,
|
|
22567
|
+
verdict: verdict2,
|
|
22568
|
+
outcome,
|
|
22569
|
+
tier: "two",
|
|
22570
|
+
sql: preview
|
|
22571
|
+
});
|
|
22572
|
+
try {
|
|
22573
|
+
const proceed = await enforceWriteGate({
|
|
22574
|
+
verdict: verdict2,
|
|
22575
|
+
statement: preview,
|
|
22576
|
+
operation,
|
|
22577
|
+
human: humanOutputContext(request.options)
|
|
22578
|
+
});
|
|
22579
|
+
await record(proceed ? "allowed" : "declined");
|
|
22580
|
+
return proceed;
|
|
22581
|
+
} catch (error) {
|
|
22582
|
+
if (error instanceof WriteGateRefusal)
|
|
22583
|
+
await record("refused");
|
|
22584
|
+
throw error;
|
|
22585
|
+
}
|
|
22586
|
+
}
|
|
22587
|
+
var init_write_gate_guard = __esm(() => {
|
|
22588
|
+
init_integration_helper();
|
|
22589
|
+
init_mutation_outcome();
|
|
22590
|
+
init_write_gate();
|
|
22591
|
+
init_write_gate_prompt();
|
|
22592
|
+
});
|
|
22593
|
+
|
|
22594
|
+
// src/core/permission/redis.ts
|
|
22595
|
+
var exports_redis = {};
|
|
22596
|
+
__export(exports_redis, {
|
|
22597
|
+
enforceRedisPermission: () => enforceRedisPermission,
|
|
22598
|
+
classifyRedisCommand: () => classifyRedisCommand
|
|
22599
|
+
});
|
|
22600
|
+
function classifyRedisCommand(command) {
|
|
22601
|
+
const head = command.trim().split(/\s+/)[0]?.toUpperCase() ?? "";
|
|
22602
|
+
const required = REDIS_COMMAND_PERMISSION[head];
|
|
22603
|
+
let type;
|
|
22604
|
+
if (!required)
|
|
22605
|
+
type = "UNKNOWN";
|
|
22606
|
+
else if (required === "query-only")
|
|
22607
|
+
type = "SELECT";
|
|
22608
|
+
else if (required === "read-write")
|
|
22609
|
+
type = "UPDATE";
|
|
22610
|
+
else if (required === "data-admin")
|
|
22611
|
+
type = "DELETE";
|
|
22612
|
+
else
|
|
22613
|
+
type = "DROP";
|
|
22614
|
+
return {
|
|
22615
|
+
command: head,
|
|
22616
|
+
requiredPermission: required ?? "unknown",
|
|
22617
|
+
type,
|
|
22618
|
+
isDangerous: required === "admin" || required === "data-admin"
|
|
22619
|
+
};
|
|
22620
|
+
}
|
|
22621
|
+
function enforceRedisPermission(command, permission) {
|
|
22622
|
+
const classification = classifyRedisCommand(command);
|
|
22623
|
+
const required = classification.requiredPermission;
|
|
22624
|
+
const stmt = {
|
|
22625
|
+
type: classification.type,
|
|
22626
|
+
isDangerous: classification.isDangerous,
|
|
22627
|
+
keywords: [classification.command],
|
|
22628
|
+
isComposite: false,
|
|
22629
|
+
confidence: required === "unknown" ? "LOW" : "HIGH"
|
|
22630
|
+
};
|
|
22631
|
+
if (required === "unknown") {
|
|
22632
|
+
throw new PermissionError(`Redis command "${classification.command}" is not whitelisted; refusing to execute`, stmt, "admin");
|
|
22633
|
+
}
|
|
22634
|
+
if (!permissionAtLeast(permission, required)) {
|
|
22635
|
+
throw new PermissionError(`Redis command "${classification.command}" requires ${required} permission`, stmt, required);
|
|
22636
|
+
}
|
|
22637
|
+
return classification;
|
|
22638
|
+
}
|
|
22639
|
+
var REDIS_COMMAND_PERMISSION;
|
|
22640
|
+
var init_redis = __esm(() => {
|
|
22641
|
+
init_permission_guard();
|
|
22642
|
+
REDIS_COMMAND_PERMISSION = {
|
|
22643
|
+
GET: "query-only",
|
|
22644
|
+
MGET: "query-only",
|
|
22645
|
+
STRLEN: "query-only",
|
|
22646
|
+
EXISTS: "query-only",
|
|
22647
|
+
TTL: "query-only",
|
|
22648
|
+
PTTL: "query-only",
|
|
22649
|
+
TYPE: "query-only",
|
|
22650
|
+
SCAN: "query-only",
|
|
22651
|
+
HGET: "query-only",
|
|
22652
|
+
HGETALL: "query-only",
|
|
22653
|
+
HKEYS: "query-only",
|
|
22654
|
+
HVALS: "query-only",
|
|
22655
|
+
HLEN: "query-only",
|
|
22656
|
+
HEXISTS: "query-only",
|
|
22657
|
+
HMGET: "query-only",
|
|
22658
|
+
LRANGE: "query-only",
|
|
22659
|
+
LLEN: "query-only",
|
|
22660
|
+
LINDEX: "query-only",
|
|
22661
|
+
SMEMBERS: "query-only",
|
|
22662
|
+
SCARD: "query-only",
|
|
22663
|
+
SISMEMBER: "query-only",
|
|
22664
|
+
ZRANGE: "query-only",
|
|
22665
|
+
ZREVRANGE: "query-only",
|
|
22666
|
+
ZRANGEBYSCORE: "query-only",
|
|
22667
|
+
ZCARD: "query-only",
|
|
22668
|
+
ZSCORE: "query-only",
|
|
22669
|
+
PING: "query-only",
|
|
22670
|
+
ECHO: "query-only",
|
|
22671
|
+
SET: "read-write",
|
|
22672
|
+
SETEX: "read-write",
|
|
22673
|
+
SETNX: "read-write",
|
|
22674
|
+
PSETEX: "read-write",
|
|
22675
|
+
MSET: "read-write",
|
|
22676
|
+
MSETNX: "read-write",
|
|
22677
|
+
APPEND: "read-write",
|
|
22678
|
+
INCR: "read-write",
|
|
22679
|
+
INCRBY: "read-write",
|
|
22680
|
+
DECR: "read-write",
|
|
22681
|
+
DECRBY: "read-write",
|
|
22682
|
+
HSET: "read-write",
|
|
22683
|
+
HSETNX: "read-write",
|
|
22684
|
+
HMSET: "read-write",
|
|
22685
|
+
HINCRBY: "read-write",
|
|
22686
|
+
LPUSH: "read-write",
|
|
22687
|
+
RPUSH: "read-write",
|
|
22688
|
+
LPOP: "read-write",
|
|
22689
|
+
RPOP: "read-write",
|
|
22690
|
+
LSET: "read-write",
|
|
22691
|
+
LREM: "read-write",
|
|
22692
|
+
SADD: "read-write",
|
|
22693
|
+
SREM: "read-write",
|
|
22694
|
+
ZADD: "read-write",
|
|
22695
|
+
ZREM: "read-write",
|
|
22696
|
+
XADD: "read-write",
|
|
22697
|
+
XDEL: "data-admin",
|
|
22698
|
+
XLEN: "query-only",
|
|
22699
|
+
XREAD: "query-only",
|
|
22700
|
+
XRANGE: "query-only",
|
|
22701
|
+
XREVRANGE: "query-only",
|
|
22702
|
+
EXPIRE: "read-write",
|
|
22703
|
+
EXPIREAT: "read-write",
|
|
22704
|
+
PEXPIRE: "read-write",
|
|
22705
|
+
PERSIST: "read-write",
|
|
22706
|
+
RENAME: "read-write",
|
|
22707
|
+
DEL: "data-admin",
|
|
22708
|
+
UNLINK: "data-admin",
|
|
22709
|
+
HDEL: "data-admin",
|
|
22710
|
+
FLUSHDB: "admin",
|
|
22711
|
+
FLUSHALL: "admin",
|
|
22712
|
+
CONFIG: "admin",
|
|
22713
|
+
INFO: "admin",
|
|
22714
|
+
CLIENT: "admin",
|
|
22715
|
+
DEBUG: "admin",
|
|
22716
|
+
SHUTDOWN: "admin",
|
|
22717
|
+
KEYS: "admin",
|
|
22718
|
+
MONITOR: "admin",
|
|
22719
|
+
SAVE: "admin",
|
|
22720
|
+
BGSAVE: "admin",
|
|
22721
|
+
BGREWRITEAOF: "admin",
|
|
22722
|
+
REPLICAOF: "admin",
|
|
22723
|
+
SLAVEOF: "admin",
|
|
22724
|
+
ACL: "admin"
|
|
22725
|
+
};
|
|
22216
22726
|
});
|
|
22217
22727
|
|
|
22218
22728
|
// src/commands/query-table-schema.ts
|
|
@@ -22407,10 +22917,24 @@ async function executeQueryCommand(sql, options, command, runtimeOverrides = {})
|
|
|
22407
22917
|
connectionName = connectionNames[0];
|
|
22408
22918
|
config = await runtime.loadConfig(configPath, connectionName);
|
|
22409
22919
|
const context = { config, configPath, connectionName };
|
|
22920
|
+
if (!await runtime.enforceGate(resolvedSql, options, context))
|
|
22921
|
+
return;
|
|
22410
22922
|
await runtime.preflight(resolvedSql, options, context, fieldSelection, false);
|
|
22411
22923
|
const execution = await runtime.executeConnection(resolvedSql, options, context, fieldSelection);
|
|
22412
22924
|
await runtime.presentResult(resolvedSql, options, execution, tableCellLimit);
|
|
22413
22925
|
} catch (error) {
|
|
22926
|
+
const { WriteGateRefusal: WriteGateRefusal2 } = await Promise.resolve().then(() => (init_write_gate_prompt(), exports_write_gate_prompt));
|
|
22927
|
+
if (error instanceof WriteGateRefusal2) {
|
|
22928
|
+
if (options.format === "json") {
|
|
22929
|
+
console.log(JSON.stringify({
|
|
22930
|
+
status: "refused",
|
|
22931
|
+
code: error.code,
|
|
22932
|
+
reason: error.reason,
|
|
22933
|
+
...error.table && { table: error.table }
|
|
22934
|
+
}, null, 2));
|
|
22935
|
+
}
|
|
22936
|
+
throw error;
|
|
22937
|
+
}
|
|
22414
22938
|
let auditId = null;
|
|
22415
22939
|
let envelopeId;
|
|
22416
22940
|
if (options.recovery === true && !multiConnectionRequest) {
|
|
@@ -22497,6 +23021,48 @@ function scopeMultiConnectionNotices(execution) {
|
|
|
22497
23021
|
notices: []
|
|
22498
23022
|
};
|
|
22499
23023
|
}
|
|
23024
|
+
async function enforceQueryWriteGate(query, options, context) {
|
|
23025
|
+
const { config } = context;
|
|
23026
|
+
const { toSqlDialect: toSqlDialect2, enforcePermission: enforcePermission2 } = await Promise.resolve().then(() => (init_permission_guard(), exports_permission_guard));
|
|
23027
|
+
const dialect = toSqlDialect2(config.connection?.system);
|
|
23028
|
+
if (!dialect)
|
|
23029
|
+
return true;
|
|
23030
|
+
const { classifySqlWriteGate: classifySqlWriteGate2 } = await Promise.resolve().then(() => (init_write_gate(), exports_write_gate));
|
|
23031
|
+
const verdict2 = await classifySqlWriteGate2(query, { dialect });
|
|
23032
|
+
if (verdict2.tier === "none")
|
|
23033
|
+
return true;
|
|
23034
|
+
enforcePermission2(query, config.permission, dialect);
|
|
23035
|
+
const { enforceWriteGate: enforceWriteGate2, WriteGateRefusal: WriteGateRefusal2 } = await Promise.resolve().then(() => (init_write_gate_prompt(), exports_write_gate_prompt));
|
|
23036
|
+
const { humanOutputContext: humanOutputContext2 } = await Promise.resolve().then(() => (init_mutation_outcome(), exports_mutation_outcome));
|
|
23037
|
+
const { recordGateDecision: recordGateDecision2 } = await Promise.resolve().then(() => (init_write_gate_guard(), exports_write_gate_guard));
|
|
23038
|
+
const record = (outcome) => recordGateDecision2({
|
|
23039
|
+
config,
|
|
23040
|
+
command: "query",
|
|
23041
|
+
options: { ...options, config: context.configPath, connectionName: context.connectionName },
|
|
23042
|
+
verdict: verdict2,
|
|
23043
|
+
outcome,
|
|
23044
|
+
tier: verdict2.tier === "two" ? "two" : "one",
|
|
23045
|
+
sql: query
|
|
23046
|
+
});
|
|
23047
|
+
try {
|
|
23048
|
+
const proceed = await enforceWriteGate2({
|
|
23049
|
+
verdict: verdict2,
|
|
23050
|
+
statement: query,
|
|
23051
|
+
operation: verdict2.operation ?? "WRITE",
|
|
23052
|
+
human: humanOutputContext2(options),
|
|
23053
|
+
...options.yes !== undefined && { yes: options.yes }
|
|
23054
|
+
});
|
|
23055
|
+
if (verdict2.tier === "two")
|
|
23056
|
+
await record(proceed ? "allowed" : "declined");
|
|
23057
|
+
else if (!proceed)
|
|
23058
|
+
await record("declined");
|
|
23059
|
+
return proceed;
|
|
23060
|
+
} catch (error) {
|
|
23061
|
+
if (error instanceof WriteGateRefusal2)
|
|
23062
|
+
await record("refused");
|
|
23063
|
+
throw error;
|
|
23064
|
+
}
|
|
23065
|
+
}
|
|
22500
23066
|
async function preflightQuery(query, options, context, fieldSelection, multiConnection) {
|
|
22501
23067
|
const { config } = context;
|
|
22502
23068
|
if (!config.connection)
|
|
@@ -22509,7 +23075,7 @@ async function preflightQuery(query, options, context, fieldSelection, multiConn
|
|
|
22509
23075
|
if (multiConnection) {
|
|
22510
23076
|
throw new Error("Redis queries do not support multiple connections");
|
|
22511
23077
|
}
|
|
22512
|
-
const { enforceRedisPermission: enforceRedisPermission2 } = await Promise.resolve().then(() => (
|
|
23078
|
+
const { enforceRedisPermission: enforceRedisPermission2 } = await Promise.resolve().then(() => (init_redis(), exports_redis));
|
|
22513
23079
|
enforceRedisPermission2(query, config.permission);
|
|
22514
23080
|
return;
|
|
22515
23081
|
}
|
|
@@ -22930,7 +23496,7 @@ var init_query = __esm(() => {
|
|
|
22930
23496
|
init_field_projection();
|
|
22931
23497
|
init_connection_selector();
|
|
22932
23498
|
init_query_fanout();
|
|
22933
|
-
|
|
23499
|
+
init_elasticsearch();
|
|
22934
23500
|
init_slow_query_advisory();
|
|
22935
23501
|
ALLOWED_FORMATS3 = ["table", "json", "csv"];
|
|
22936
23502
|
defaultQueryCommandRuntime = {
|
|
@@ -22940,6 +23506,7 @@ var init_query = __esm(() => {
|
|
|
22940
23506
|
executeConnection: executeConnectionQuery,
|
|
22941
23507
|
presentResult: presentSingleResult,
|
|
22942
23508
|
writeAudit: writeAuditEntry,
|
|
23509
|
+
enforceGate: enforceQueryWriteGate,
|
|
22943
23510
|
randomUUID: () => crypto3.randomUUID(),
|
|
22944
23511
|
emitRecovery: async (error, context, options) => {
|
|
22945
23512
|
const { emitRecoveryEnvelope: emitRecoveryEnvelope2 } = await Promise.resolve().then(() => (init_recovery(), exports_recovery));
|
|
@@ -22964,7 +23531,7 @@ __export(exports_inline_registrars, {
|
|
|
22964
23531
|
registerDeleteCommand: () => registerDeleteCommand
|
|
22965
23532
|
});
|
|
22966
23533
|
function registerQueryCommand(program2, run) {
|
|
22967
|
-
program2.command("query [sql]").description(t("query.description")).option("-f, --query-file <path>", "Read query text from a UTF-8 file, or - for stdin").option("--format <type>", "Output format: table, json, csv, html", "table").option("--ui", "Show interactive dashboard in browser", false).option("--limit <number>", "Limit result rows (overrides auto-limit)", parsePositiveInteger).option("--no-limit", "Disable auto-limit in query-only mode").option("--collection <name>", "MongoDB collection name; Elasticsearch index name").option("--index <name>", "Elasticsearch index name (alias for --collection)").option("--fields <list>", "Include fields, or exclude them with --fields=-field_a,-field_b").option("--truncate <number>", "Limit serialized table cells to N Unicode characters", parsePositiveInteger).option("--no-truncate", "Disable the default table-cell truncation").option("--slow-ms <number>", "Emit a passive performance hint at or above this execution time; 0 disables it", parseSlowMs, 1000).addOption(createConnectionSelectorOption()).option("--recovery", "On failure, emit a structured recovery envelope to stdout (suppresses human stderr message)", false).action(async (sql, options, command) => {
|
|
23534
|
+
program2.command("query [sql]").description(t("query.description")).option("-f, --query-file <path>", "Read query text from a UTF-8 file, or - for stdin").option("--format <type>", "Output format: table, json, csv, html", "table").option("--ui", "Show interactive dashboard in browser", false).option("--limit <number>", "Limit result rows (overrides auto-limit)", parsePositiveInteger).option("--no-limit", "Disable auto-limit in query-only mode").option("--collection <name>", "MongoDB collection name; Elasticsearch index name").option("--index <name>", "Elasticsearch index name (alias for --collection)").option("--fields <list>", "Include fields, or exclude them with --fields=-field_a,-field_b").option("--truncate <number>", "Limit serialized table cells to N Unicode characters", parsePositiveInteger).option("--no-truncate", "Disable the default table-cell truncation").option("--slow-ms <number>", "Emit a passive performance hint at or above this execution time; 0 disables it", parseSlowMs, 1000).addOption(createConnectionSelectorOption()).option("--yes", "Skip the confirmation shown before an ordinary write; statements that are not limited to specific rows ignore it", false).option("--recovery", "On failure, emit a structured recovery envelope to stdout (suppresses human stderr message)", false).action(async (sql, options, command) => {
|
|
22968
23535
|
const rawArgs = command.parent?.rawArgs ?? [];
|
|
22969
23536
|
const rawTruncate = findLongOptionValue(rawArgs, "--truncate");
|
|
22970
23537
|
const rootUse = command.parent?.opts().use;
|
|
@@ -22989,7 +23556,7 @@ function registerQCommand(program2, run) {
|
|
|
22989
23556
|
});
|
|
22990
23557
|
}
|
|
22991
23558
|
function registerInsertCommand(program2, run) {
|
|
22992
|
-
program2.command("insert <table>").description(t("insert.description")).option("--data <json>", "JSON object to insert").option("--dry-run", "Show generated SQL without executing").option("--force", "Skip confirmation prompt").option("--plan", "Analyze risk without connecting or executing").option("--format <type>", "Output format
|
|
23559
|
+
program2.command("insert <table>").description(t("insert.description")).option("--data <json>", "JSON object to insert").option("--dry-run", "Show generated SQL without executing").option("--force", "Skip confirmation prompt").option("--plan", "Analyze risk without connecting or executing").option("--format <type>", "Output format: text or json", "text").option("--recovery", "On failure, emit a structured recovery envelope to stdout (suppresses human stderr message)", false).action(async (table, options, command) => {
|
|
22993
23560
|
try {
|
|
22994
23561
|
await run(table, options, command);
|
|
22995
23562
|
} catch (error) {
|
|
@@ -23003,7 +23570,7 @@ function registerInsertCommand(program2, run) {
|
|
|
23003
23570
|
});
|
|
23004
23571
|
}
|
|
23005
23572
|
function registerUpdateCommand(program2, run) {
|
|
23006
|
-
program2.command("update <table>").description(t("update.description")).option("--where <condition>", 'WHERE clause (required, e.g. "id=1")').option("--set <json>", `JSON with fields to update (required, e.g. '{"name":"Bob"}')`).option("--dry-run", "Show generated SQL without executing").option("--force", "Skip confirmation prompt").option("--plan", "Analyze risk without connecting or executing").option("--format <type>", "Output format
|
|
23573
|
+
program2.command("update <table>").description(t("update.description")).option("--where <condition>", 'WHERE clause (required, e.g. "id=1")').option("--set <json>", `JSON with fields to update (required, e.g. '{"name":"Bob"}')`).option("--dry-run", "Show generated SQL without executing").option("--force", "Skip confirmation prompt").option("--plan", "Analyze risk without connecting or executing").option("--format <type>", "Output format: text or json", "text").option("--recovery", "On failure, emit a structured recovery envelope to stdout (suppresses human stderr message)", false).action(async (table, options, command) => {
|
|
23007
23574
|
try {
|
|
23008
23575
|
await run(table, options, command);
|
|
23009
23576
|
} catch (error) {
|
|
@@ -23017,7 +23584,7 @@ function registerUpdateCommand(program2, run) {
|
|
|
23017
23584
|
});
|
|
23018
23585
|
}
|
|
23019
23586
|
function registerDeleteCommand(program2, run) {
|
|
23020
|
-
program2.command("delete <table>").description(t("delete.description")).option("--where <condition>", 'WHERE clause (required, e.g. "id=1")').option("--dry-run", "Show generated SQL without executing").option("--force", "Skip confirmation prompt").option("--plan", "Analyze risk without connecting or executing").option("--format <type>", "Output format
|
|
23587
|
+
program2.command("delete <table>").description(t("delete.description")).option("--where <condition>", 'WHERE clause (required, e.g. "id=1")').option("--dry-run", "Show generated SQL without executing").option("--force", "Skip confirmation prompt").option("--plan", "Analyze risk without connecting or executing").option("--format <type>", "Output format: text or json", "text").option("--recovery", "On failure, emit a structured recovery envelope to stdout (suppresses human stderr message)", false).action(async (table, options, command) => {
|
|
23021
23588
|
try {
|
|
23022
23589
|
await run(table, options, command);
|
|
23023
23590
|
} catch (error) {
|
|
@@ -23280,8 +23847,8 @@ function applyBlacklistRules(facts, blacklist, factors) {
|
|
|
23280
23847
|
}
|
|
23281
23848
|
function findBlacklistedColumnsForTable(blacklist, table) {
|
|
23282
23849
|
const columns = blacklist.columns ?? {};
|
|
23283
|
-
for (const [
|
|
23284
|
-
if (
|
|
23850
|
+
for (const [tableName2, columnNames] of Object.entries(columns)) {
|
|
23851
|
+
if (tableName2.toLowerCase() === table.toLowerCase()) {
|
|
23285
23852
|
return new Set(columnNames);
|
|
23286
23853
|
}
|
|
23287
23854
|
}
|
|
@@ -23738,7 +24305,7 @@ function deepHasScript(value) {
|
|
|
23738
24305
|
return Object.values(obj).some(deepHasScript);
|
|
23739
24306
|
}
|
|
23740
24307
|
var NAME_RE2, ES_SIZE_CAP = 1000, ES_MAX_RESULT_WINDOW = 1e4, esStrategy;
|
|
23741
|
-
var
|
|
24308
|
+
var init_elasticsearch2 = __esm(() => {
|
|
23742
24309
|
init_types5();
|
|
23743
24310
|
NAME_RE2 = /:([a-zA-Z_][a-zA-Z0-9_]*)/g;
|
|
23744
24311
|
esStrategy = {
|
|
@@ -23836,7 +24403,7 @@ function applyRedisSizeGuard(command, noLimit) {
|
|
|
23836
24403
|
return { command, warnings };
|
|
23837
24404
|
}
|
|
23838
24405
|
var REDIS_NAME_RE, REDIS_RANGE_CAP = 1000, RANGE_VERBS, SCAN_VERBS, REDIS_READONLY_VERBS, REDIS_HARD_REJECT, redisStrategy;
|
|
23839
|
-
var
|
|
24406
|
+
var init_redis2 = __esm(() => {
|
|
23840
24407
|
init_types5();
|
|
23841
24408
|
REDIS_NAME_RE = /:([a-zA-Z_][a-zA-Z0-9_]*)/g;
|
|
23842
24409
|
RANGE_VERBS = new Set(["LRANGE", "ZRANGE", "ZRANGEBYSCORE", "ZRANGEBYLEX"]);
|
|
@@ -24035,8 +24602,8 @@ function getStrategy(family) {
|
|
|
24035
24602
|
}
|
|
24036
24603
|
var init_strategies = __esm(() => {
|
|
24037
24604
|
init_sql();
|
|
24038
|
-
|
|
24039
|
-
|
|
24605
|
+
init_elasticsearch2();
|
|
24606
|
+
init_redis2();
|
|
24040
24607
|
init_mongodb();
|
|
24041
24608
|
});
|
|
24042
24609
|
|
|
@@ -25245,12 +25812,12 @@ class DataExecutor {
|
|
|
25245
25812
|
this.dbSystem = dbSystem;
|
|
25246
25813
|
this.blacklistValidator = blacklistValidator;
|
|
25247
25814
|
}
|
|
25248
|
-
buildInsertSql(
|
|
25815
|
+
buildInsertSql(tableName2, data, schema) {
|
|
25249
25816
|
const columnNames = schema.columns.map((col) => col.name);
|
|
25250
25817
|
const dataKeys = Object.keys(data);
|
|
25251
25818
|
for (const key of dataKeys) {
|
|
25252
25819
|
if (!columnNames.includes(key)) {
|
|
25253
|
-
throw new Error(`Column "${key}" not found in table "${
|
|
25820
|
+
throw new Error(`Column "${key}" not found in table "${tableName2}". Valid columns: ${columnNames.join(", ")}`);
|
|
25254
25821
|
}
|
|
25255
25822
|
}
|
|
25256
25823
|
const columns = dataKeys;
|
|
@@ -25258,49 +25825,38 @@ class DataExecutor {
|
|
|
25258
25825
|
const systemType = this.getSystemType();
|
|
25259
25826
|
const placeholders = systemType === "postgresql" ? dataKeys.map((_, index) => `$${index + 1}`).join(", ") : dataKeys.map(() => "?").join(", ");
|
|
25260
25827
|
const quote = this.getQuoteChar();
|
|
25261
|
-
const sql = `INSERT INTO ${quote}${
|
|
25828
|
+
const sql = `INSERT INTO ${quote}${tableName2}${quote} (${columns.map((col) => `${quote}${col}${quote}`).join(", ")}) VALUES (${placeholders})`;
|
|
25262
25829
|
return {
|
|
25263
25830
|
sql,
|
|
25264
25831
|
params: values
|
|
25265
25832
|
};
|
|
25266
25833
|
}
|
|
25267
|
-
async executeInsert(
|
|
25834
|
+
async executeInsert(tableName2, data, schema, options) {
|
|
25268
25835
|
const timestamp = new Date().toISOString();
|
|
25269
25836
|
try {
|
|
25270
|
-
this.checkBlacklist("INSERT",
|
|
25271
|
-
|
|
25272
|
-
return await this.executeMutation("insert", this.buildInsertSql(
|
|
25837
|
+
this.checkBlacklist("INSERT", tableName2);
|
|
25838
|
+
enforcePermissionForType("INSERT", this.permission);
|
|
25839
|
+
return await this.executeMutation("insert", this.buildInsertSql(tableName2, data, schema), timestamp, options, { destructive: false });
|
|
25273
25840
|
} catch (error) {
|
|
25274
25841
|
return this.handleMutationError("insert", timestamp, error);
|
|
25275
25842
|
}
|
|
25276
25843
|
}
|
|
25277
|
-
async executeUpdate(
|
|
25844
|
+
async executeUpdate(tableName2, data, where, schema, options) {
|
|
25278
25845
|
const timestamp = new Date().toISOString();
|
|
25279
25846
|
try {
|
|
25280
|
-
this.checkBlacklist("UPDATE",
|
|
25281
|
-
|
|
25282
|
-
return await this.executeMutation("update", this.buildUpdateSql(
|
|
25847
|
+
this.checkBlacklist("UPDATE", tableName2);
|
|
25848
|
+
enforcePermissionForType("UPDATE", this.permission);
|
|
25849
|
+
return await this.executeMutation("update", this.buildUpdateSql(tableName2, data, where, schema), timestamp, options, { destructive: false });
|
|
25283
25850
|
} catch (error) {
|
|
25284
25851
|
return this.handleMutationError("update", timestamp, error);
|
|
25285
25852
|
}
|
|
25286
25853
|
}
|
|
25287
|
-
async executeDelete(
|
|
25854
|
+
async executeDelete(tableName2, where, schema, options) {
|
|
25288
25855
|
const timestamp = new Date().toISOString();
|
|
25289
25856
|
try {
|
|
25290
|
-
this.checkBlacklist("DELETE",
|
|
25291
|
-
|
|
25292
|
-
|
|
25293
|
-
status: "error",
|
|
25294
|
-
operation: "delete",
|
|
25295
|
-
rows_affected: 0,
|
|
25296
|
-
timestamp,
|
|
25297
|
-
error: "Permission denied: DELETE operation requires Data-Admin or Admin permission."
|
|
25298
|
-
};
|
|
25299
|
-
}
|
|
25300
|
-
return await this.executeMutation("delete", this.buildDeleteSql(tableName, where, schema), timestamp, options, {
|
|
25301
|
-
warning: "\u26A0\uFE0F Warning: DELETE operation is destructive and cannot be undone!",
|
|
25302
|
-
prompt: "Are you sure you want to execute this DELETE operation? This cannot be undone."
|
|
25303
|
-
});
|
|
25857
|
+
this.checkBlacklist("DELETE", tableName2);
|
|
25858
|
+
enforcePermissionForType("DELETE", this.permission);
|
|
25859
|
+
return await this.executeMutation("delete", this.buildDeleteSql(tableName2, where, schema), timestamp, options, { destructive: true });
|
|
25304
25860
|
} catch (error) {
|
|
25305
25861
|
return this.handleMutationError("delete", timestamp, error);
|
|
25306
25862
|
}
|
|
@@ -25311,34 +25867,34 @@ class DataExecutor {
|
|
|
25311
25867
|
getQuoteChar() {
|
|
25312
25868
|
return this.dbSystem === "mysql" ? "`" : '"';
|
|
25313
25869
|
}
|
|
25314
|
-
checkBlacklist(operation,
|
|
25315
|
-
this.blacklistValidator?.checkTableBlacklist(operation,
|
|
25870
|
+
checkBlacklist(operation, tableName2) {
|
|
25871
|
+
this.blacklistValidator?.checkTableBlacklist(operation, tableName2, []);
|
|
25316
25872
|
}
|
|
25317
|
-
async executeMutation(operation, statement, timestamp, options,
|
|
25873
|
+
async executeMutation(operation, statement, timestamp, options, { destructive }) {
|
|
25318
25874
|
if (options?.dryRun) {
|
|
25319
|
-
return this.
|
|
25875
|
+
return this.outcomeResult("dry_run", operation, 0, timestamp, statement.sql);
|
|
25320
25876
|
}
|
|
25321
25877
|
if (!options?.force) {
|
|
25322
|
-
if (
|
|
25323
|
-
|
|
25324
|
-
${confirmation.warning}`);
|
|
25878
|
+
if (!options?.confirm) {
|
|
25879
|
+
throw new Error(`Refusing to ${operation} without confirmation: no confirmation handler was supplied. ` + "Pass options.confirm to ask the user, or options.force to proceed unattended.");
|
|
25325
25880
|
}
|
|
25326
|
-
|
|
25327
|
-
|
|
25328
|
-
|
|
25329
|
-
|
|
25330
|
-
|
|
25331
|
-
|
|
25332
|
-
|
|
25333
|
-
|
|
25881
|
+
const proceed = await options.confirm({
|
|
25882
|
+
operation,
|
|
25883
|
+
engine: "sql",
|
|
25884
|
+
sql: statement.sql,
|
|
25885
|
+
params: statement.params,
|
|
25886
|
+
destructive
|
|
25887
|
+
});
|
|
25888
|
+
if (!proceed) {
|
|
25889
|
+
return this.outcomeResult("cancelled", operation, 0, timestamp, statement.sql);
|
|
25334
25890
|
}
|
|
25335
25891
|
}
|
|
25336
25892
|
const result = await this.adapter.execute(statement.sql, statement.params);
|
|
25337
|
-
return this.
|
|
25893
|
+
return this.outcomeResult("success", operation, result.affectedRows, timestamp, statement.sql);
|
|
25338
25894
|
}
|
|
25339
|
-
|
|
25895
|
+
outcomeResult(status2, operation, rowsAffected, timestamp, sql) {
|
|
25340
25896
|
return {
|
|
25341
|
-
status:
|
|
25897
|
+
status: status2,
|
|
25342
25898
|
operation,
|
|
25343
25899
|
rows_affected: rowsAffected,
|
|
25344
25900
|
timestamp,
|
|
@@ -25349,13 +25905,13 @@ Parameters:`);
|
|
|
25349
25905
|
if (error instanceof BlacklistError) {
|
|
25350
25906
|
throw error;
|
|
25351
25907
|
}
|
|
25352
|
-
if (error instanceof PermissionError
|
|
25908
|
+
if (error instanceof PermissionError) {
|
|
25353
25909
|
return {
|
|
25354
25910
|
status: "error",
|
|
25355
25911
|
operation,
|
|
25356
25912
|
rows_affected: 0,
|
|
25357
25913
|
timestamp,
|
|
25358
|
-
error:
|
|
25914
|
+
error: t_vars("errors.permission_denied_reason", { reason: error.message })
|
|
25359
25915
|
};
|
|
25360
25916
|
}
|
|
25361
25917
|
const errorMessage2 = error instanceof Error ? error.message : String(error);
|
|
@@ -25367,13 +25923,13 @@ Parameters:`);
|
|
|
25367
25923
|
error: `${operation.toUpperCase()} failed: ${errorMessage2}`
|
|
25368
25924
|
};
|
|
25369
25925
|
}
|
|
25370
|
-
buildUpdateSql(
|
|
25926
|
+
buildUpdateSql(tableName2, data, where, schema) {
|
|
25371
25927
|
const dataKeys = Object.keys(data);
|
|
25372
25928
|
const whereKeys = Object.keys(where);
|
|
25373
25929
|
const columnNames = schema.columns.map((col) => col.name);
|
|
25374
25930
|
for (const key of [...dataKeys, ...whereKeys]) {
|
|
25375
25931
|
if (!columnNames.includes(key)) {
|
|
25376
|
-
throw new Error(`Column "${key}" not found in table "${
|
|
25932
|
+
throw new Error(`Column "${key}" not found in table "${tableName2}"`);
|
|
25377
25933
|
}
|
|
25378
25934
|
}
|
|
25379
25935
|
const systemType = this.getSystemType();
|
|
@@ -25387,16 +25943,16 @@ Parameters:`);
|
|
|
25387
25943
|
const placeholder = systemType === "postgresql" ? `$${paramIndex++}` : "?";
|
|
25388
25944
|
return `${quote}${key}${quote} = ${placeholder}`;
|
|
25389
25945
|
}).join(" AND ");
|
|
25390
|
-
const sql = `UPDATE ${quote}${
|
|
25946
|
+
const sql = `UPDATE ${quote}${tableName2}${quote} SET ${setClause} WHERE ${whereClause}`;
|
|
25391
25947
|
const params = [...dataKeys.map((key) => data[key]), ...whereKeys.map((key) => where[key])];
|
|
25392
25948
|
return { sql, params };
|
|
25393
25949
|
}
|
|
25394
|
-
buildDeleteSql(
|
|
25950
|
+
buildDeleteSql(tableName2, where, schema) {
|
|
25395
25951
|
const whereKeys = Object.keys(where);
|
|
25396
25952
|
const columnNames = schema.columns.map((col) => col.name);
|
|
25397
25953
|
for (const key of whereKeys) {
|
|
25398
25954
|
if (!columnNames.includes(key)) {
|
|
25399
|
-
throw new Error(`Column "${key}" not found in table "${
|
|
25955
|
+
throw new Error(`Column "${key}" not found in table "${tableName2}"`);
|
|
25400
25956
|
}
|
|
25401
25957
|
}
|
|
25402
25958
|
const systemType = this.getSystemType();
|
|
@@ -25406,17 +25962,80 @@ Parameters:`);
|
|
|
25406
25962
|
const placeholder = systemType === "postgresql" ? `$${paramIndex++}` : "?";
|
|
25407
25963
|
return `${quote}${key}${quote} = ${placeholder}`;
|
|
25408
25964
|
}).join(" AND ");
|
|
25409
|
-
const sql = `DELETE FROM ${quote}${
|
|
25965
|
+
const sql = `DELETE FROM ${quote}${tableName2}${quote} WHERE ${whereClause}`;
|
|
25410
25966
|
const params = whereKeys.map((key) => where[key]);
|
|
25411
25967
|
return { sql, params };
|
|
25412
25968
|
}
|
|
25413
25969
|
}
|
|
25414
25970
|
var init_data_executor = __esm(() => {
|
|
25415
25971
|
init_permission_guard();
|
|
25416
|
-
|
|
25972
|
+
init_message_loader();
|
|
25417
25973
|
init_blacklist();
|
|
25418
25974
|
});
|
|
25419
25975
|
|
|
25976
|
+
// src/commands/mutation-confirm.ts
|
|
25977
|
+
async function confirmDirectMutation(request) {
|
|
25978
|
+
if (request.force === true)
|
|
25979
|
+
return true;
|
|
25980
|
+
return confirmMutationInteractively({
|
|
25981
|
+
operation: request.operation,
|
|
25982
|
+
engine: request.engine,
|
|
25983
|
+
sql: request.preview,
|
|
25984
|
+
params: [],
|
|
25985
|
+
destructive: request.destructive
|
|
25986
|
+
});
|
|
25987
|
+
}
|
|
25988
|
+
var confirmMutationInteractively = async (request) => {
|
|
25989
|
+
if (request.destructive) {
|
|
25990
|
+
process.stderr.write(`
|
|
25991
|
+
${t("ceremony.confirm_destructive_warning")}
|
|
25992
|
+
`);
|
|
25993
|
+
}
|
|
25994
|
+
const label = request.engine === "sql" ? "ceremony.confirm_sql" : "ceremony.confirm_command";
|
|
25995
|
+
process.stderr.write(`
|
|
25996
|
+
${t(label)}
|
|
25997
|
+
${request.sql}
|
|
25998
|
+
`);
|
|
25999
|
+
if (request.params.length > 0) {
|
|
26000
|
+
process.stderr.write(`
|
|
26001
|
+
${t("ceremony.confirm_params")}
|
|
26002
|
+
${JSON.stringify(request.params, null, 2)}
|
|
26003
|
+
`);
|
|
26004
|
+
}
|
|
26005
|
+
return promptUser.confirm(t(request.destructive ? "ceremony.confirm_destructive_prompt" : "ceremony.confirm_prompt"));
|
|
26006
|
+
}, confirmDdlInteractively = async (request) => {
|
|
26007
|
+
process.stderr.write(`
|
|
26008
|
+
${t("ceremony.confirm_destructive_warning_ddl")}
|
|
26009
|
+
`);
|
|
26010
|
+
process.stderr.write(`
|
|
26011
|
+
${t("ceremony.confirm_sql")}
|
|
26012
|
+
${request.sql}
|
|
26013
|
+
`);
|
|
26014
|
+
return promptUser.confirm(t_vars("ceremony.confirm_ddl_prompt", { operation: request.operation }));
|
|
26015
|
+
};
|
|
26016
|
+
var init_mutation_confirm = __esm(() => {
|
|
26017
|
+
init_message_loader();
|
|
26018
|
+
init_prompts();
|
|
26019
|
+
});
|
|
26020
|
+
|
|
26021
|
+
// src/commands/mutation-audit.ts
|
|
26022
|
+
function auditOutcomeForMutation(result, table, recoveryRef) {
|
|
26023
|
+
return {
|
|
26024
|
+
success: result.status === "success" || result.status === "dry_run",
|
|
26025
|
+
target: table,
|
|
26026
|
+
...recoveryRef && { recovery_ref: recoveryRef },
|
|
26027
|
+
...result.sql && { sql: result.sql },
|
|
26028
|
+
metadata: {
|
|
26029
|
+
rows_affected: result.rows_affected,
|
|
26030
|
+
outcome: result.status,
|
|
26031
|
+
...result.status === "dry_run" && { dry_run: true }
|
|
26032
|
+
},
|
|
26033
|
+
...result.status === "error" && {
|
|
26034
|
+
error: new Error(result.error ?? `${result.operation} failed`)
|
|
26035
|
+
}
|
|
26036
|
+
};
|
|
26037
|
+
}
|
|
26038
|
+
|
|
25420
26039
|
// src/core/mongo/dry-run-formatter.ts
|
|
25421
26040
|
function pretty(value) {
|
|
25422
26041
|
return JSON.stringify(value, null, INDENT);
|
|
@@ -26258,6 +26877,9 @@ async function insertCommand(table, options, command) {
|
|
|
26258
26877
|
throw new Error("Table name required");
|
|
26259
26878
|
}
|
|
26260
26879
|
table = table.trim();
|
|
26880
|
+
if (options.format !== undefined && options.format !== "text" && options.format !== "json") {
|
|
26881
|
+
throw new Error(t_vars("errors.invalid_output_format", { format: options.format }));
|
|
26882
|
+
}
|
|
26261
26883
|
let jsonInput = "";
|
|
26262
26884
|
if (options.data) {
|
|
26263
26885
|
jsonInput = options.data;
|
|
@@ -26298,30 +26920,40 @@ async function insertCommand(table, options, command) {
|
|
|
26298
26920
|
return;
|
|
26299
26921
|
}
|
|
26300
26922
|
if (config.connection.system === "redis") {
|
|
26301
|
-
|
|
26923
|
+
enforcePermissionForType("INSERT", config.permission);
|
|
26302
26924
|
const blacklistManager = new BlacklistManager(config);
|
|
26303
26925
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
26304
26926
|
blacklistValidator.checkTableBlacklist("INSERT", table, []);
|
|
26305
26927
|
blacklistValidator.checkColumnBlacklistOnWrite(table, Object.keys(data), "INSERT");
|
|
26928
|
+
const preview = `SET ${table} ... (Redis Insert)`;
|
|
26306
26929
|
if (options.dryRun) {
|
|
26307
26930
|
const output = {
|
|
26308
|
-
status: "
|
|
26931
|
+
status: "dry_run",
|
|
26309
26932
|
operation: "insert",
|
|
26310
26933
|
rows_affected: 0,
|
|
26311
|
-
sql:
|
|
26934
|
+
sql: preview,
|
|
26312
26935
|
timestamp: new Date().toISOString()
|
|
26313
26936
|
};
|
|
26314
|
-
|
|
26315
|
-
await writeAuditEntry(config, "insert", options,
|
|
26316
|
-
|
|
26317
|
-
|
|
26318
|
-
|
|
26319
|
-
|
|
26937
|
+
printMutationOutcome(output, table, 0, humanOutputContext(options));
|
|
26938
|
+
await writeAuditEntry(config, "insert", options, auditOutcomeForMutation(output, table));
|
|
26939
|
+
return;
|
|
26940
|
+
}
|
|
26941
|
+
if (!await confirmDirectMutation({
|
|
26942
|
+
operation: "insert",
|
|
26943
|
+
engine: "redis",
|
|
26944
|
+
preview,
|
|
26945
|
+
destructive: false,
|
|
26946
|
+
force: options.force
|
|
26947
|
+
})) {
|
|
26948
|
+
const output = cancelledOutcome("insert", preview);
|
|
26949
|
+
printMutationOutcome(output, table, 0, humanOutputContext(options));
|
|
26950
|
+
await writeAuditEntry(config, "insert", options, auditOutcomeForMutation(output, table));
|
|
26320
26951
|
return;
|
|
26321
26952
|
}
|
|
26322
26953
|
const adapter2 = AdapterFactory.createRedisAdapter(config.connection, config.blacklist?.tables ?? []);
|
|
26323
26954
|
await adapter2.connect();
|
|
26324
26955
|
try {
|
|
26956
|
+
const startedAt = performance.now();
|
|
26325
26957
|
const result = await adapter2.insert(table, data);
|
|
26326
26958
|
const output = {
|
|
26327
26959
|
status: "success",
|
|
@@ -26329,12 +26961,8 @@ async function insertCommand(table, options, command) {
|
|
|
26329
26961
|
rows_affected: result.affectedRows,
|
|
26330
26962
|
timestamp: new Date().toISOString()
|
|
26331
26963
|
};
|
|
26332
|
-
|
|
26333
|
-
await writeAuditEntry(config, "insert", options,
|
|
26334
|
-
success: true,
|
|
26335
|
-
target: table,
|
|
26336
|
-
metadata: { rows_affected: result.affectedRows }
|
|
26337
|
-
});
|
|
26964
|
+
printMutationOutcome(output, table, performance.now() - startedAt, humanOutputContext(options));
|
|
26965
|
+
await writeAuditEntry(config, "insert", options, auditOutcomeForMutation(output, table));
|
|
26338
26966
|
return;
|
|
26339
26967
|
} finally {
|
|
26340
26968
|
await adapter2.disconnect();
|
|
@@ -26346,7 +26974,7 @@ async function insertCommand(table, options, command) {
|
|
|
26346
26974
|
operation: "insert",
|
|
26347
26975
|
rows_affected: 0,
|
|
26348
26976
|
timestamp: new Date().toISOString(),
|
|
26349
|
-
error: "
|
|
26977
|
+
error: t("insert.elasticsearch_unsupported")
|
|
26350
26978
|
};
|
|
26351
26979
|
await writeAuditEntry(config, "insert", options, {
|
|
26352
26980
|
success: false,
|
|
@@ -26357,44 +26985,49 @@ async function insertCommand(table, options, command) {
|
|
|
26357
26985
|
process.exit(1);
|
|
26358
26986
|
}
|
|
26359
26987
|
if (config.connection.system === "mongodb") {
|
|
26360
|
-
|
|
26988
|
+
enforcePermissionForType("INSERT", config.permission);
|
|
26361
26989
|
const blacklistManager = new BlacklistManager(config);
|
|
26362
26990
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
26363
26991
|
blacklistValidator.checkTableBlacklist("INSERT", table, []);
|
|
26364
26992
|
blacklistValidator.checkColumnBlacklistOnWrite(table, Object.keys(data), "INSERT");
|
|
26993
|
+
const preview = previewInsert(table, data);
|
|
26365
26994
|
if (options.dryRun) {
|
|
26366
26995
|
const output = {
|
|
26367
|
-
status: "
|
|
26996
|
+
status: "dry_run",
|
|
26368
26997
|
operation: "insert",
|
|
26369
26998
|
rows_affected: 0,
|
|
26370
|
-
sql:
|
|
26999
|
+
sql: preview,
|
|
26371
27000
|
timestamp: new Date().toISOString()
|
|
26372
27001
|
};
|
|
26373
|
-
|
|
26374
|
-
await writeAuditEntry(config, "insert", options,
|
|
26375
|
-
|
|
26376
|
-
|
|
26377
|
-
|
|
26378
|
-
|
|
27002
|
+
printMutationOutcome(output, table, 0, humanOutputContext(options));
|
|
27003
|
+
await writeAuditEntry(config, "insert", options, auditOutcomeForMutation(output, table));
|
|
27004
|
+
return;
|
|
27005
|
+
}
|
|
27006
|
+
if (!await confirmDirectMutation({
|
|
27007
|
+
operation: "insert",
|
|
27008
|
+
engine: "mongodb",
|
|
27009
|
+
preview,
|
|
27010
|
+
destructive: false,
|
|
27011
|
+
force: options.force
|
|
27012
|
+
})) {
|
|
27013
|
+
const output = cancelledOutcome("insert", preview);
|
|
27014
|
+
printMutationOutcome(output, table, 0, humanOutputContext(options));
|
|
27015
|
+
await writeAuditEntry(config, "insert", options, auditOutcomeForMutation(output, table));
|
|
26379
27016
|
return;
|
|
26380
27017
|
}
|
|
26381
27018
|
const adapter2 = AdapterFactory.createMongoDBAdapter(config.connection);
|
|
26382
27019
|
await adapter2.connect();
|
|
26383
27020
|
try {
|
|
27021
|
+
const startedAt = performance.now();
|
|
26384
27022
|
const result = await adapter2.insert(table, data);
|
|
26385
27023
|
const output = {
|
|
26386
27024
|
status: "success",
|
|
26387
27025
|
operation: "insert",
|
|
26388
27026
|
rows_affected: result.affectedRows,
|
|
26389
|
-
timestamp: new Date().toISOString()
|
|
26390
|
-
lastInsertId: result.lastInsertId
|
|
27027
|
+
timestamp: new Date().toISOString()
|
|
26391
27028
|
};
|
|
26392
|
-
|
|
26393
|
-
await writeAuditEntry(config, "insert", options,
|
|
26394
|
-
success: true,
|
|
26395
|
-
target: table,
|
|
26396
|
-
metadata: { rows_affected: result.affectedRows }
|
|
26397
|
-
});
|
|
27029
|
+
printMutationOutcome(output, table, performance.now() - startedAt, humanOutputContext(options), { lastInsertId: result.lastInsertId });
|
|
27030
|
+
await writeAuditEntry(config, "insert", options, auditOutcomeForMutation(output, table));
|
|
26398
27031
|
return;
|
|
26399
27032
|
} finally {
|
|
26400
27033
|
await adapter2.disconnect();
|
|
@@ -26408,31 +27041,20 @@ async function insertCommand(table, options, command) {
|
|
|
26408
27041
|
const blacklistManager = new BlacklistManager(config);
|
|
26409
27042
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
26410
27043
|
const executor3 = new DataExecutor(adapter, config.permission, dbSystem, blacklistValidator);
|
|
27044
|
+
const startedAt = performance.now();
|
|
26411
27045
|
const result = await executor3.executeInsert(table, data, schema, {
|
|
26412
27046
|
dryRun: options.dryRun,
|
|
26413
|
-
force: options.force
|
|
26414
|
-
|
|
26415
|
-
const output = {
|
|
26416
|
-
status: result.status,
|
|
26417
|
-
operation: result.operation,
|
|
26418
|
-
rows_affected: result.rows_affected,
|
|
26419
|
-
timestamp: result.timestamp,
|
|
26420
|
-
...result.sql && { sql: result.sql },
|
|
26421
|
-
...result.error && { error: result.error }
|
|
26422
|
-
};
|
|
26423
|
-
console.log(JSON.stringify(output, null, 2));
|
|
26424
|
-
await writeAuditEntry(config, "insert", options, {
|
|
26425
|
-
success: result.status === "success",
|
|
26426
|
-
target: table,
|
|
26427
|
-
...result.sql && { sql: result.sql },
|
|
26428
|
-
metadata: {
|
|
26429
|
-
rows_affected: result.rows_affected,
|
|
26430
|
-
...options.dryRun && { dry_run: true }
|
|
26431
|
-
},
|
|
26432
|
-
...result.status === "error" && {
|
|
26433
|
-
error: new Error(result.error ?? "insert failed")
|
|
26434
|
-
}
|
|
27047
|
+
force: options.force,
|
|
27048
|
+
confirm: confirmMutationInteractively
|
|
26435
27049
|
});
|
|
27050
|
+
const elapsedMs = performance.now() - startedAt;
|
|
27051
|
+
const recoveryEnvelopeId = result.status === "error" && options.recovery === true ? crypto5.randomUUID() : undefined;
|
|
27052
|
+
const auditId = await writeAuditEntry(config, "insert", options, auditOutcomeForMutation(result, table, recoveryEnvelopeId));
|
|
27053
|
+
if (recoveryEnvelopeId !== undefined) {
|
|
27054
|
+
const { emitRecoveryEnvelope: emitRecoveryEnvelope2 } = await Promise.resolve().then(() => (init_recovery(), exports_recovery));
|
|
27055
|
+
emitRecoveryEnvelope2(new Error(result.error ?? "INSERT failed"), { operation: "insert", table, writeOperation: "INSERT" }, { envelopeId: recoveryEnvelopeId, auditRef: auditId ?? undefined });
|
|
27056
|
+
}
|
|
27057
|
+
printMutationOutcome(result, table, elapsedMs, humanOutputContext(options));
|
|
26436
27058
|
if (result.status === "error") {
|
|
26437
27059
|
process.exit(1);
|
|
26438
27060
|
}
|
|
@@ -26458,13 +27080,7 @@ async function insertCommand(table, options, command) {
|
|
|
26458
27080
|
emitRecoveryEnvelope2(error, { operation: "insert", table, writeOperation: "INSERT" }, { envelopeId, auditRef: auditId ?? undefined });
|
|
26459
27081
|
}
|
|
26460
27082
|
if (error instanceof BlacklistError) {
|
|
26461
|
-
|
|
26462
|
-
status: "error",
|
|
26463
|
-
operation: "insert",
|
|
26464
|
-
rows_affected: 0,
|
|
26465
|
-
error: error.message
|
|
26466
|
-
};
|
|
26467
|
-
console.log(JSON.stringify(output2, null, 2));
|
|
27083
|
+
printMutationFailure(error, "insert", humanOutputContext(options));
|
|
26468
27084
|
process.exit(1);
|
|
26469
27085
|
}
|
|
26470
27086
|
if (error instanceof PermissionError) {
|
|
@@ -26477,13 +27093,7 @@ async function insertCommand(table, options, command) {
|
|
|
26477
27093
|
printLocalizedCliError(formatCliError(presentConnectionError(error)), error);
|
|
26478
27094
|
process.exit(1);
|
|
26479
27095
|
}
|
|
26480
|
-
|
|
26481
|
-
status: "error",
|
|
26482
|
-
operation: "insert",
|
|
26483
|
-
rows_affected: 0,
|
|
26484
|
-
error: error.message
|
|
26485
|
-
};
|
|
26486
|
-
console.log(JSON.stringify(output, null, 2));
|
|
27096
|
+
printMutationFailure(error, "insert", humanOutputContext(options));
|
|
26487
27097
|
process.exit(1);
|
|
26488
27098
|
}
|
|
26489
27099
|
}
|
|
@@ -26493,6 +27103,8 @@ var init_insert = __esm(() => {
|
|
|
26493
27103
|
init_connection_error_message();
|
|
26494
27104
|
init_adapters();
|
|
26495
27105
|
init_data_executor();
|
|
27106
|
+
init_mutation_confirm();
|
|
27107
|
+
init_mutation_outcome();
|
|
26496
27108
|
init_config();
|
|
26497
27109
|
init_permission_guard();
|
|
26498
27110
|
init_blacklist_validator();
|
|
@@ -26521,6 +27133,9 @@ async function updateCommand(table, options, command) {
|
|
|
26521
27133
|
throw new Error("Table name required");
|
|
26522
27134
|
}
|
|
26523
27135
|
table = table.trim();
|
|
27136
|
+
if (options.format !== undefined && options.format !== "text" && options.format !== "json") {
|
|
27137
|
+
throw new Error(t_vars("errors.invalid_output_format", { format: options.format }));
|
|
27138
|
+
}
|
|
26524
27139
|
if (!options.plan && (!options.where || options.where.trim() === "")) {
|
|
26525
27140
|
throw new Error('UPDATE requires --where clause (e.g. --where "id=1")');
|
|
26526
27141
|
}
|
|
@@ -26581,29 +27196,39 @@ async function updateCommand(table, options, command) {
|
|
|
26581
27196
|
return;
|
|
26582
27197
|
}
|
|
26583
27198
|
if (config.connection?.system === "redis") {
|
|
26584
|
-
|
|
27199
|
+
enforcePermissionForType("UPDATE", config.permission);
|
|
26585
27200
|
const blacklistManager = new BlacklistManager(config);
|
|
26586
27201
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
26587
27202
|
blacklistValidator.checkTableBlacklist("UPDATE", table, []);
|
|
27203
|
+
const preview = `HSET ${table} ... (Redis Update)`;
|
|
26588
27204
|
if (options.dryRun) {
|
|
26589
27205
|
const output = {
|
|
26590
|
-
status: "
|
|
27206
|
+
status: "dry_run",
|
|
26591
27207
|
operation: "update",
|
|
26592
27208
|
rows_affected: 0,
|
|
26593
|
-
sql:
|
|
27209
|
+
sql: preview,
|
|
26594
27210
|
timestamp: new Date().toISOString()
|
|
26595
27211
|
};
|
|
26596
|
-
|
|
26597
|
-
await writeAuditEntry(config, "update", options,
|
|
26598
|
-
|
|
26599
|
-
|
|
26600
|
-
|
|
26601
|
-
|
|
27212
|
+
printMutationOutcome(output, table, 0, humanOutputContext(options));
|
|
27213
|
+
await writeAuditEntry(config, "update", options, auditOutcomeForMutation(output, table));
|
|
27214
|
+
return;
|
|
27215
|
+
}
|
|
27216
|
+
if (!await confirmDirectMutation({
|
|
27217
|
+
operation: "update",
|
|
27218
|
+
engine: "redis",
|
|
27219
|
+
preview,
|
|
27220
|
+
destructive: false,
|
|
27221
|
+
force: options.force
|
|
27222
|
+
})) {
|
|
27223
|
+
const output = cancelledOutcome("update", preview);
|
|
27224
|
+
printMutationOutcome(output, table, 0, humanOutputContext(options));
|
|
27225
|
+
await writeAuditEntry(config, "update", options, auditOutcomeForMutation(output, table));
|
|
26602
27226
|
return;
|
|
26603
27227
|
}
|
|
26604
27228
|
const adapter2 = AdapterFactory.createRedisAdapter(config.connection, config.blacklist?.tables ?? []);
|
|
26605
27229
|
await adapter2.connect();
|
|
26606
27230
|
try {
|
|
27231
|
+
const startedAt = performance.now();
|
|
26607
27232
|
const result = await adapter2.update(table, {}, setData);
|
|
26608
27233
|
const output = {
|
|
26609
27234
|
status: "success",
|
|
@@ -26611,12 +27236,8 @@ async function updateCommand(table, options, command) {
|
|
|
26611
27236
|
rows_affected: result.affectedRows,
|
|
26612
27237
|
timestamp: new Date().toISOString()
|
|
26613
27238
|
};
|
|
26614
|
-
|
|
26615
|
-
await writeAuditEntry(config, "update", options,
|
|
26616
|
-
success: true,
|
|
26617
|
-
target: table,
|
|
26618
|
-
metadata: { rows_affected: result.affectedRows }
|
|
26619
|
-
});
|
|
27239
|
+
printMutationOutcome(output, table, performance.now() - startedAt, humanOutputContext(options));
|
|
27240
|
+
await writeAuditEntry(config, "update", options, auditOutcomeForMutation(output, table));
|
|
26620
27241
|
return;
|
|
26621
27242
|
} finally {
|
|
26622
27243
|
await adapter2.disconnect();
|
|
@@ -26628,7 +27249,7 @@ async function updateCommand(table, options, command) {
|
|
|
26628
27249
|
operation: "update",
|
|
26629
27250
|
rows_affected: 0,
|
|
26630
27251
|
timestamp: new Date().toISOString(),
|
|
26631
|
-
error: "
|
|
27252
|
+
error: t("update.elasticsearch_unsupported")
|
|
26632
27253
|
};
|
|
26633
27254
|
await writeAuditEntry(config, "update", options, {
|
|
26634
27255
|
success: false,
|
|
@@ -26639,7 +27260,7 @@ async function updateCommand(table, options, command) {
|
|
|
26639
27260
|
process.exit(1);
|
|
26640
27261
|
}
|
|
26641
27262
|
if (config.connection?.system === "mongodb") {
|
|
26642
|
-
|
|
27263
|
+
enforcePermissionForType("UPDATE", config.permission);
|
|
26643
27264
|
const hasOperator = Object.keys(setData).some((key) => key.startsWith("$"));
|
|
26644
27265
|
const updateDoc = hasOperator ? setData : { $set: setData };
|
|
26645
27266
|
const writtenFields = new Set;
|
|
@@ -26665,25 +27286,35 @@ async function updateCommand(table, options, command) {
|
|
|
26665
27286
|
} catch {
|
|
26666
27287
|
filter = parseWhereClause(options.where);
|
|
26667
27288
|
}
|
|
27289
|
+
const preview = previewUpdate(table, filter, updateDoc);
|
|
26668
27290
|
if (options.dryRun) {
|
|
26669
27291
|
const output = {
|
|
26670
|
-
status: "
|
|
27292
|
+
status: "dry_run",
|
|
26671
27293
|
operation: "update",
|
|
26672
27294
|
rows_affected: 0,
|
|
26673
|
-
sql:
|
|
27295
|
+
sql: preview,
|
|
26674
27296
|
timestamp: new Date().toISOString()
|
|
26675
27297
|
};
|
|
26676
|
-
|
|
26677
|
-
await writeAuditEntry(config, "update", options,
|
|
26678
|
-
|
|
26679
|
-
|
|
26680
|
-
|
|
26681
|
-
|
|
27298
|
+
printMutationOutcome(output, table, 0, humanOutputContext(options));
|
|
27299
|
+
await writeAuditEntry(config, "update", options, auditOutcomeForMutation(output, table));
|
|
27300
|
+
return;
|
|
27301
|
+
}
|
|
27302
|
+
if (!await confirmDirectMutation({
|
|
27303
|
+
operation: "update",
|
|
27304
|
+
engine: "mongodb",
|
|
27305
|
+
preview,
|
|
27306
|
+
destructive: false,
|
|
27307
|
+
force: options.force
|
|
27308
|
+
})) {
|
|
27309
|
+
const output = cancelledOutcome("update", preview);
|
|
27310
|
+
printMutationOutcome(output, table, 0, humanOutputContext(options));
|
|
27311
|
+
await writeAuditEntry(config, "update", options, auditOutcomeForMutation(output, table));
|
|
26682
27312
|
return;
|
|
26683
27313
|
}
|
|
26684
27314
|
const adapter2 = AdapterFactory.createMongoDBAdapter(config.connection);
|
|
26685
27315
|
await adapter2.connect();
|
|
26686
27316
|
try {
|
|
27317
|
+
const startedAt = performance.now();
|
|
26687
27318
|
const result = await adapter2.update(table, filter, updateDoc);
|
|
26688
27319
|
const output = {
|
|
26689
27320
|
status: "success",
|
|
@@ -26691,12 +27322,8 @@ async function updateCommand(table, options, command) {
|
|
|
26691
27322
|
rows_affected: result.affectedRows,
|
|
26692
27323
|
timestamp: new Date().toISOString()
|
|
26693
27324
|
};
|
|
26694
|
-
|
|
26695
|
-
await writeAuditEntry(config, "update", options,
|
|
26696
|
-
success: true,
|
|
26697
|
-
target: table,
|
|
26698
|
-
metadata: { rows_affected: result.affectedRows }
|
|
26699
|
-
});
|
|
27325
|
+
printMutationOutcome(output, table, performance.now() - startedAt, humanOutputContext(options));
|
|
27326
|
+
await writeAuditEntry(config, "update", options, auditOutcomeForMutation(output, table));
|
|
26700
27327
|
return;
|
|
26701
27328
|
} finally {
|
|
26702
27329
|
await adapter2.disconnect();
|
|
@@ -26712,35 +27339,38 @@ async function updateCommand(table, options, command) {
|
|
|
26712
27339
|
await adapter.connect();
|
|
26713
27340
|
try {
|
|
26714
27341
|
const schema = await adapter.getTableSchema(table);
|
|
27342
|
+
const { guardStructuredWrite: guardStructuredWrite2 } = await Promise.resolve().then(() => (init_write_gate_guard(), exports_write_gate_guard));
|
|
27343
|
+
if (options.dryRun !== true && !await guardStructuredWrite2({
|
|
27344
|
+
operation: "update",
|
|
27345
|
+
table,
|
|
27346
|
+
where: whereConditions,
|
|
27347
|
+
schema,
|
|
27348
|
+
config,
|
|
27349
|
+
options
|
|
27350
|
+
})) {
|
|
27351
|
+
const cancelled = cancelledOutcome("update", `UPDATE ${table}`);
|
|
27352
|
+
printMutationOutcome(cancelled, table, 0, humanOutputContext(options));
|
|
27353
|
+
await writeAuditEntry(config, "update", options, auditOutcomeForMutation(cancelled, table));
|
|
27354
|
+
return;
|
|
27355
|
+
}
|
|
26715
27356
|
const dbSystem = config.connection.system === "postgresql" ? "postgresql" : "mysql";
|
|
26716
27357
|
const blacklistManager = new BlacklistManager(config);
|
|
26717
27358
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
26718
27359
|
const executor3 = new DataExecutor(adapter, config.permission, dbSystem, blacklistValidator);
|
|
27360
|
+
const startedAt = performance.now();
|
|
26719
27361
|
const result = await executor3.executeUpdate(table, setData, whereConditions, schema, {
|
|
26720
27362
|
dryRun: options.dryRun,
|
|
26721
|
-
force: options.force
|
|
26722
|
-
|
|
26723
|
-
const output = {
|
|
26724
|
-
status: result.status,
|
|
26725
|
-
operation: result.operation,
|
|
26726
|
-
rows_affected: result.rows_affected,
|
|
26727
|
-
timestamp: result.timestamp,
|
|
26728
|
-
...result.sql && { sql: result.sql },
|
|
26729
|
-
...result.error && { error: result.error }
|
|
26730
|
-
};
|
|
26731
|
-
console.log(JSON.stringify(output, null, 2));
|
|
26732
|
-
await writeAuditEntry(config, "update", options, {
|
|
26733
|
-
success: result.status === "success",
|
|
26734
|
-
target: table,
|
|
26735
|
-
...result.sql && { sql: result.sql },
|
|
26736
|
-
metadata: {
|
|
26737
|
-
rows_affected: result.rows_affected,
|
|
26738
|
-
...options.dryRun && { dry_run: true }
|
|
26739
|
-
},
|
|
26740
|
-
...result.status === "error" && {
|
|
26741
|
-
error: new Error(result.error ?? "update failed")
|
|
26742
|
-
}
|
|
27363
|
+
force: options.force,
|
|
27364
|
+
confirm: confirmMutationInteractively
|
|
26743
27365
|
});
|
|
27366
|
+
const elapsedMs = performance.now() - startedAt;
|
|
27367
|
+
const recoveryEnvelopeId = result.status === "error" && options.recovery === true ? crypto6.randomUUID() : undefined;
|
|
27368
|
+
const auditId = await writeAuditEntry(config, "update", options, auditOutcomeForMutation(result, table, recoveryEnvelopeId));
|
|
27369
|
+
if (recoveryEnvelopeId !== undefined) {
|
|
27370
|
+
const { emitRecoveryEnvelope: emitRecoveryEnvelope2 } = await Promise.resolve().then(() => (init_recovery(), exports_recovery));
|
|
27371
|
+
emitRecoveryEnvelope2(new Error(result.error ?? "UPDATE failed"), { operation: "update", table, writeOperation: "UPDATE" }, { envelopeId: recoveryEnvelopeId, auditRef: auditId ?? undefined });
|
|
27372
|
+
}
|
|
27373
|
+
printMutationOutcome(result, table, elapsedMs, humanOutputContext(options));
|
|
26744
27374
|
if (result.status === "error") {
|
|
26745
27375
|
process.exit(1);
|
|
26746
27376
|
}
|
|
@@ -26748,6 +27378,11 @@ async function updateCommand(table, options, command) {
|
|
|
26748
27378
|
await adapter.disconnect();
|
|
26749
27379
|
}
|
|
26750
27380
|
} catch (error) {
|
|
27381
|
+
const { WriteGateRefusal: WriteGateRefusal2 } = await Promise.resolve().then(() => (init_write_gate_prompt(), exports_write_gate_prompt));
|
|
27382
|
+
if (error instanceof WriteGateRefusal2) {
|
|
27383
|
+
printMutationFailure(error, "update", humanOutputContext(options));
|
|
27384
|
+
process.exit(1);
|
|
27385
|
+
}
|
|
26751
27386
|
let auditId = null;
|
|
26752
27387
|
let envelopeId;
|
|
26753
27388
|
if (options.recovery === true) {
|
|
@@ -26766,13 +27401,7 @@ async function updateCommand(table, options, command) {
|
|
|
26766
27401
|
emitRecoveryEnvelope2(error, { operation: "update", table, writeOperation: "UPDATE" }, { envelopeId, auditRef: auditId ?? undefined });
|
|
26767
27402
|
}
|
|
26768
27403
|
if (error instanceof BlacklistError) {
|
|
26769
|
-
|
|
26770
|
-
status: "error",
|
|
26771
|
-
operation: "update",
|
|
26772
|
-
rows_affected: 0,
|
|
26773
|
-
error: error.message
|
|
26774
|
-
};
|
|
26775
|
-
console.log(JSON.stringify(output2, null, 2));
|
|
27404
|
+
printMutationFailure(error, "update", humanOutputContext(options));
|
|
26776
27405
|
process.exit(1);
|
|
26777
27406
|
}
|
|
26778
27407
|
if (error instanceof PermissionError) {
|
|
@@ -26785,13 +27414,7 @@ async function updateCommand(table, options, command) {
|
|
|
26785
27414
|
printLocalizedCliError(formatCliError(presentConnectionError(error)), error);
|
|
26786
27415
|
process.exit(1);
|
|
26787
27416
|
}
|
|
26788
|
-
|
|
26789
|
-
status: "error",
|
|
26790
|
-
operation: "update",
|
|
26791
|
-
rows_affected: 0,
|
|
26792
|
-
error: error.message
|
|
26793
|
-
};
|
|
26794
|
-
console.log(JSON.stringify(output, null, 2));
|
|
27417
|
+
printMutationFailure(error, "update", humanOutputContext(options));
|
|
26795
27418
|
process.exit(1);
|
|
26796
27419
|
}
|
|
26797
27420
|
}
|
|
@@ -26801,6 +27424,8 @@ var init_update = __esm(() => {
|
|
|
26801
27424
|
init_connection_error_message();
|
|
26802
27425
|
init_adapters();
|
|
26803
27426
|
init_data_executor();
|
|
27427
|
+
init_mutation_confirm();
|
|
27428
|
+
init_mutation_outcome();
|
|
26804
27429
|
init_config();
|
|
26805
27430
|
init_permission_guard();
|
|
26806
27431
|
init_blacklist_validator();
|
|
@@ -26829,6 +27454,9 @@ async function deleteCommand(table, options, command) {
|
|
|
26829
27454
|
throw new Error("Table name required");
|
|
26830
27455
|
}
|
|
26831
27456
|
table = table.trim();
|
|
27457
|
+
if (options.format !== undefined && options.format !== "text" && options.format !== "json") {
|
|
27458
|
+
throw new Error(t_vars("errors.invalid_output_format", { format: options.format }));
|
|
27459
|
+
}
|
|
26832
27460
|
if (!options.plan && (!options.where || options.where.trim() === "")) {
|
|
26833
27461
|
throw new Error('DELETE requires --where clause (e.g. --where "id=1")');
|
|
26834
27462
|
}
|
|
@@ -26875,14 +27503,8 @@ async function deleteCommand(table, options, command) {
|
|
|
26875
27503
|
});
|
|
26876
27504
|
return;
|
|
26877
27505
|
}
|
|
26878
|
-
if (
|
|
26879
|
-
throw new PermissionError(t("delete.admin_only"),
|
|
26880
|
-
type: "DELETE",
|
|
26881
|
-
isDangerous: true,
|
|
26882
|
-
keywords: ["DELETE"],
|
|
26883
|
-
isComposite: false,
|
|
26884
|
-
confidence: "HIGH"
|
|
26885
|
-
}, config.permission);
|
|
27506
|
+
if (!permitsOperation("DELETE", config.permission)) {
|
|
27507
|
+
throw new PermissionError(t("delete.admin_only"), classificationForType("DELETE"), minimumPermissionFor("DELETE"));
|
|
26886
27508
|
}
|
|
26887
27509
|
if (config.connection?.system === "redis") {
|
|
26888
27510
|
const blacklistManager = new BlacklistManager(config);
|
|
@@ -26894,25 +27516,35 @@ async function deleteCommand(table, options, command) {
|
|
|
26894
27516
|
} catch {
|
|
26895
27517
|
filter = parseWhereClause(options.where);
|
|
26896
27518
|
}
|
|
27519
|
+
const preview = `DEL ${table} (Redis Delete)`;
|
|
26897
27520
|
if (options.dryRun) {
|
|
26898
27521
|
const output = {
|
|
26899
|
-
status: "
|
|
27522
|
+
status: "dry_run",
|
|
26900
27523
|
operation: "delete",
|
|
26901
27524
|
rows_affected: 0,
|
|
26902
|
-
sql:
|
|
27525
|
+
sql: preview,
|
|
26903
27526
|
timestamp: new Date().toISOString()
|
|
26904
27527
|
};
|
|
26905
|
-
|
|
26906
|
-
await writeAuditEntry(config, "delete", options,
|
|
26907
|
-
|
|
26908
|
-
|
|
26909
|
-
|
|
26910
|
-
|
|
27528
|
+
printMutationOutcome(output, table, 0, humanOutputContext(options));
|
|
27529
|
+
await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(output, table));
|
|
27530
|
+
return;
|
|
27531
|
+
}
|
|
27532
|
+
if (!await confirmDirectMutation({
|
|
27533
|
+
operation: "delete",
|
|
27534
|
+
engine: "redis",
|
|
27535
|
+
preview,
|
|
27536
|
+
destructive: true,
|
|
27537
|
+
force: options.force
|
|
27538
|
+
})) {
|
|
27539
|
+
const output = cancelledOutcome("delete", preview);
|
|
27540
|
+
printMutationOutcome(output, table, 0, humanOutputContext(options));
|
|
27541
|
+
await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(output, table));
|
|
26911
27542
|
return;
|
|
26912
27543
|
}
|
|
26913
27544
|
const adapter2 = AdapterFactory.createRedisAdapter(config.connection, config.blacklist?.tables ?? []);
|
|
26914
27545
|
await adapter2.connect();
|
|
26915
27546
|
try {
|
|
27547
|
+
const startedAt = performance.now();
|
|
26916
27548
|
const result = await adapter2.delete(table, filter);
|
|
26917
27549
|
const output = {
|
|
26918
27550
|
status: "success",
|
|
@@ -26920,12 +27552,8 @@ async function deleteCommand(table, options, command) {
|
|
|
26920
27552
|
rows_affected: result.affectedRows,
|
|
26921
27553
|
timestamp: new Date().toISOString()
|
|
26922
27554
|
};
|
|
26923
|
-
|
|
26924
|
-
await writeAuditEntry(config, "delete", options,
|
|
26925
|
-
success: true,
|
|
26926
|
-
target: table,
|
|
26927
|
-
metadata: { rows_affected: result.affectedRows }
|
|
26928
|
-
});
|
|
27555
|
+
printMutationOutcome(output, table, performance.now() - startedAt, humanOutputContext(options));
|
|
27556
|
+
await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(output, table));
|
|
26929
27557
|
return;
|
|
26930
27558
|
} finally {
|
|
26931
27559
|
await adapter2.disconnect();
|
|
@@ -26937,7 +27565,7 @@ async function deleteCommand(table, options, command) {
|
|
|
26937
27565
|
operation: "delete",
|
|
26938
27566
|
rows_affected: 0,
|
|
26939
27567
|
timestamp: new Date().toISOString(),
|
|
26940
|
-
error: "
|
|
27568
|
+
error: t("delete.elasticsearch_unsupported")
|
|
26941
27569
|
};
|
|
26942
27570
|
await writeAuditEntry(config, "delete", options, {
|
|
26943
27571
|
success: false,
|
|
@@ -26957,25 +27585,35 @@ async function deleteCommand(table, options, command) {
|
|
|
26957
27585
|
} catch {
|
|
26958
27586
|
filter = parseWhereClause(options.where);
|
|
26959
27587
|
}
|
|
27588
|
+
const preview = previewDelete(table, filter);
|
|
26960
27589
|
if (options.dryRun) {
|
|
26961
27590
|
const output = {
|
|
26962
|
-
status: "
|
|
27591
|
+
status: "dry_run",
|
|
26963
27592
|
operation: "delete",
|
|
26964
27593
|
rows_affected: 0,
|
|
26965
|
-
sql:
|
|
27594
|
+
sql: preview,
|
|
26966
27595
|
timestamp: new Date().toISOString()
|
|
26967
27596
|
};
|
|
26968
|
-
|
|
26969
|
-
await writeAuditEntry(config, "delete", options,
|
|
26970
|
-
|
|
26971
|
-
|
|
26972
|
-
|
|
26973
|
-
|
|
27597
|
+
printMutationOutcome(output, table, 0, humanOutputContext(options));
|
|
27598
|
+
await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(output, table));
|
|
27599
|
+
return;
|
|
27600
|
+
}
|
|
27601
|
+
if (!await confirmDirectMutation({
|
|
27602
|
+
operation: "delete",
|
|
27603
|
+
engine: "mongodb",
|
|
27604
|
+
preview,
|
|
27605
|
+
destructive: true,
|
|
27606
|
+
force: options.force
|
|
27607
|
+
})) {
|
|
27608
|
+
const output = cancelledOutcome("delete", preview);
|
|
27609
|
+
printMutationOutcome(output, table, 0, humanOutputContext(options));
|
|
27610
|
+
await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(output, table));
|
|
26974
27611
|
return;
|
|
26975
27612
|
}
|
|
26976
27613
|
const adapter2 = AdapterFactory.createMongoDBAdapter(config.connection);
|
|
26977
27614
|
await adapter2.connect();
|
|
26978
27615
|
try {
|
|
27616
|
+
const startedAt = performance.now();
|
|
26979
27617
|
const result = await adapter2.delete(table, filter);
|
|
26980
27618
|
const output = {
|
|
26981
27619
|
status: "success",
|
|
@@ -26983,12 +27621,8 @@ async function deleteCommand(table, options, command) {
|
|
|
26983
27621
|
rows_affected: result.affectedRows,
|
|
26984
27622
|
timestamp: new Date().toISOString()
|
|
26985
27623
|
};
|
|
26986
|
-
|
|
26987
|
-
await writeAuditEntry(config, "delete", options,
|
|
26988
|
-
success: true,
|
|
26989
|
-
target: table,
|
|
26990
|
-
metadata: { rows_affected: result.affectedRows }
|
|
26991
|
-
});
|
|
27624
|
+
printMutationOutcome(output, table, performance.now() - startedAt, humanOutputContext(options));
|
|
27625
|
+
await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(output, table));
|
|
26992
27626
|
return;
|
|
26993
27627
|
} finally {
|
|
26994
27628
|
await adapter2.disconnect();
|
|
@@ -27004,35 +27638,38 @@ async function deleteCommand(table, options, command) {
|
|
|
27004
27638
|
await adapter.connect();
|
|
27005
27639
|
try {
|
|
27006
27640
|
const schema = await adapter.getTableSchema(table);
|
|
27641
|
+
const { guardStructuredWrite: guardStructuredWrite2 } = await Promise.resolve().then(() => (init_write_gate_guard(), exports_write_gate_guard));
|
|
27642
|
+
if (options.dryRun !== true && !await guardStructuredWrite2({
|
|
27643
|
+
operation: "delete",
|
|
27644
|
+
table,
|
|
27645
|
+
where: whereConditions,
|
|
27646
|
+
schema,
|
|
27647
|
+
config,
|
|
27648
|
+
options
|
|
27649
|
+
})) {
|
|
27650
|
+
const cancelled = cancelledOutcome("delete", `DELETE FROM ${table}`);
|
|
27651
|
+
printMutationOutcome(cancelled, table, 0, humanOutputContext(options));
|
|
27652
|
+
await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(cancelled, table));
|
|
27653
|
+
return;
|
|
27654
|
+
}
|
|
27007
27655
|
const dbSystem = config.connection.system === "postgresql" ? "postgresql" : "mysql";
|
|
27008
27656
|
const blacklistManager = new BlacklistManager(config);
|
|
27009
27657
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
27010
27658
|
const executor3 = new DataExecutor(adapter, config.permission, dbSystem, blacklistValidator);
|
|
27659
|
+
const startedAt = performance.now();
|
|
27011
27660
|
const result = await executor3.executeDelete(table, whereConditions, schema, {
|
|
27012
27661
|
dryRun: options.dryRun,
|
|
27013
|
-
force: options.force
|
|
27014
|
-
|
|
27015
|
-
const output = {
|
|
27016
|
-
status: result.status,
|
|
27017
|
-
operation: result.operation,
|
|
27018
|
-
rows_affected: result.rows_affected,
|
|
27019
|
-
timestamp: result.timestamp,
|
|
27020
|
-
...result.sql && { sql: result.sql },
|
|
27021
|
-
...result.error && { error: result.error }
|
|
27022
|
-
};
|
|
27023
|
-
console.log(JSON.stringify(output, null, 2));
|
|
27024
|
-
await writeAuditEntry(config, "delete", options, {
|
|
27025
|
-
success: result.status === "success",
|
|
27026
|
-
target: table,
|
|
27027
|
-
...result.sql && { sql: result.sql },
|
|
27028
|
-
metadata: {
|
|
27029
|
-
rows_affected: result.rows_affected,
|
|
27030
|
-
...options.dryRun && { dry_run: true }
|
|
27031
|
-
},
|
|
27032
|
-
...result.status === "error" && {
|
|
27033
|
-
error: new Error(result.error ?? "delete failed")
|
|
27034
|
-
}
|
|
27662
|
+
force: options.force,
|
|
27663
|
+
confirm: confirmMutationInteractively
|
|
27035
27664
|
});
|
|
27665
|
+
const elapsedMs = performance.now() - startedAt;
|
|
27666
|
+
const recoveryEnvelopeId = result.status === "error" && options.recovery === true ? crypto7.randomUUID() : undefined;
|
|
27667
|
+
const auditId = await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(result, table, recoveryEnvelopeId));
|
|
27668
|
+
if (recoveryEnvelopeId !== undefined) {
|
|
27669
|
+
const { emitRecoveryEnvelope: emitRecoveryEnvelope2 } = await Promise.resolve().then(() => (init_recovery(), exports_recovery));
|
|
27670
|
+
emitRecoveryEnvelope2(new Error(result.error ?? "DELETE failed"), { operation: "delete", table, writeOperation: "DELETE" }, { envelopeId: recoveryEnvelopeId, auditRef: auditId ?? undefined });
|
|
27671
|
+
}
|
|
27672
|
+
printMutationOutcome(result, table, elapsedMs, humanOutputContext(options));
|
|
27036
27673
|
if (result.status === "error") {
|
|
27037
27674
|
process.exit(1);
|
|
27038
27675
|
}
|
|
@@ -27040,6 +27677,11 @@ async function deleteCommand(table, options, command) {
|
|
|
27040
27677
|
await adapter.disconnect();
|
|
27041
27678
|
}
|
|
27042
27679
|
} catch (error) {
|
|
27680
|
+
const { WriteGateRefusal: WriteGateRefusal2 } = await Promise.resolve().then(() => (init_write_gate_prompt(), exports_write_gate_prompt));
|
|
27681
|
+
if (error instanceof WriteGateRefusal2) {
|
|
27682
|
+
printMutationFailure(error, "delete", humanOutputContext(options));
|
|
27683
|
+
process.exit(1);
|
|
27684
|
+
}
|
|
27043
27685
|
let auditId = null;
|
|
27044
27686
|
let envelopeId;
|
|
27045
27687
|
if (options.recovery === true) {
|
|
@@ -27058,17 +27700,11 @@ async function deleteCommand(table, options, command) {
|
|
|
27058
27700
|
emitRecoveryEnvelope2(error, { operation: "delete", table, writeOperation: "DELETE" }, { envelopeId, auditRef: auditId ?? undefined });
|
|
27059
27701
|
}
|
|
27060
27702
|
if (error instanceof BlacklistError) {
|
|
27061
|
-
|
|
27062
|
-
status: "error",
|
|
27063
|
-
operation: "delete",
|
|
27064
|
-
rows_affected: 0,
|
|
27065
|
-
error: error.message
|
|
27066
|
-
};
|
|
27067
|
-
console.log(JSON.stringify(output2, null, 2));
|
|
27703
|
+
printMutationFailure(error, "delete", humanOutputContext(options));
|
|
27068
27704
|
process.exit(1);
|
|
27069
27705
|
}
|
|
27070
27706
|
if (error instanceof PermissionError) {
|
|
27071
|
-
console.error(t_vars("errors.permission_denied", { required:
|
|
27707
|
+
console.error(t_vars("errors.permission_denied", { required: error.requiredPermission }));
|
|
27072
27708
|
console.error(` Operation: ${error.classification.type}`);
|
|
27073
27709
|
console.error(` Message: ${error.message}`);
|
|
27074
27710
|
process.exit(1);
|
|
@@ -27077,13 +27713,7 @@ async function deleteCommand(table, options, command) {
|
|
|
27077
27713
|
printLocalizedCliError(formatCliError(presentConnectionError(error)), error);
|
|
27078
27714
|
process.exit(1);
|
|
27079
27715
|
}
|
|
27080
|
-
|
|
27081
|
-
status: "error",
|
|
27082
|
-
operation: "delete",
|
|
27083
|
-
rows_affected: 0,
|
|
27084
|
-
error: error.message
|
|
27085
|
-
};
|
|
27086
|
-
console.log(JSON.stringify(output, null, 2));
|
|
27716
|
+
printMutationFailure(error, "delete", humanOutputContext(options));
|
|
27087
27717
|
process.exit(1);
|
|
27088
27718
|
}
|
|
27089
27719
|
}
|
|
@@ -27093,6 +27723,8 @@ var init_delete = __esm(() => {
|
|
|
27093
27723
|
init_connection_error_message();
|
|
27094
27724
|
init_adapters();
|
|
27095
27725
|
init_data_executor();
|
|
27726
|
+
init_mutation_confirm();
|
|
27727
|
+
init_mutation_outcome();
|
|
27096
27728
|
init_config();
|
|
27097
27729
|
init_permission_guard();
|
|
27098
27730
|
init_blacklist_validator();
|
|
@@ -27256,7 +27888,7 @@ async function exportCommand(sql, options, command) {
|
|
|
27256
27888
|
}
|
|
27257
27889
|
}
|
|
27258
27890
|
async function redisExportBranch(command, options, config) {
|
|
27259
|
-
const { enforceRedisPermission: enforceRedisPermission2 } = await Promise.resolve().then(() => (
|
|
27891
|
+
const { enforceRedisPermission: enforceRedisPermission2 } = await Promise.resolve().then(() => (init_redis(), exports_redis));
|
|
27260
27892
|
enforceRedisPermission2(command, config.permission);
|
|
27261
27893
|
const redisAdapter = AdapterFactory.createRedisAdapter(config.connection, config.blacklist?.tables ?? [], config.redis?.mask ?? []);
|
|
27262
27894
|
await redisAdapter.connect();
|
|
@@ -27548,16 +28180,6 @@ var init_export = __esm(() => {
|
|
|
27548
28180
|
SQL_PATTERN = /^\s*(SELECT|INSERT|UPDATE|DELETE|CREATE|DROP|ALTER|SHOW|DESCRIBE)\b/i;
|
|
27549
28181
|
});
|
|
27550
28182
|
|
|
27551
|
-
// src/core/sql-parser.ts
|
|
27552
|
-
function sqlParser() {
|
|
27553
|
-
if (!cached2) {
|
|
27554
|
-
const { Parser } = __require("node-sql-parser");
|
|
27555
|
-
cached2 = new Parser;
|
|
27556
|
-
}
|
|
27557
|
-
return cached2;
|
|
27558
|
-
}
|
|
27559
|
-
var cached2 = null;
|
|
27560
|
-
|
|
27561
28183
|
// src/core/explain/read-only.ts
|
|
27562
28184
|
function containsAnalyzeUnsafeNode(node) {
|
|
27563
28185
|
if (!node || typeof node !== "object")
|
|
@@ -28662,12 +29284,12 @@ function compactVisibleSchema(config) {
|
|
|
28662
29284
|
const blacklistManager = new BlacklistManager(config);
|
|
28663
29285
|
const compactSchema = {};
|
|
28664
29286
|
const rawSchema = config.schema ?? {};
|
|
28665
|
-
for (const [
|
|
28666
|
-
if (blacklistManager.isTableBlacklisted(
|
|
29287
|
+
for (const [tableName2, tableSchema] of Object.entries(rawSchema)) {
|
|
29288
|
+
if (blacklistManager.isTableBlacklisted(tableName2))
|
|
28667
29289
|
continue;
|
|
28668
29290
|
const columns = [];
|
|
28669
29291
|
for (const col of tableSchema.columns || []) {
|
|
28670
|
-
if (blacklistManager.isColumnBlacklisted(
|
|
29292
|
+
if (blacklistManager.isColumnBlacklisted(tableName2, col.name))
|
|
28671
29293
|
continue;
|
|
28672
29294
|
const compactCol = {
|
|
28673
29295
|
name: col.name,
|
|
@@ -28687,7 +29309,7 @@ function compactVisibleSchema(config) {
|
|
|
28687
29309
|
columns.push(compactCol);
|
|
28688
29310
|
}
|
|
28689
29311
|
const compactTable = {
|
|
28690
|
-
name: tableSchema.name ||
|
|
29312
|
+
name: tableSchema.name || tableName2,
|
|
28691
29313
|
columns
|
|
28692
29314
|
};
|
|
28693
29315
|
const rowCount = tableSchema.estimatedRowCount ?? tableSchema.rowCount;
|
|
@@ -28703,7 +29325,7 @@ function compactVisibleSchema(config) {
|
|
|
28703
29325
|
refColumns: fk.refColumns
|
|
28704
29326
|
}));
|
|
28705
29327
|
}
|
|
28706
|
-
compactSchema[
|
|
29328
|
+
compactSchema[tableName2] = compactTable;
|
|
28707
29329
|
}
|
|
28708
29330
|
return compactSchema;
|
|
28709
29331
|
}
|
|
@@ -28811,10 +29433,10 @@ function serializeXml(payload) {
|
|
|
28811
29433
|
}
|
|
28812
29434
|
if (Object.keys(payload.schema).length > 0) {
|
|
28813
29435
|
parts.push(" <tables>");
|
|
28814
|
-
for (const [
|
|
29436
|
+
for (const [tableName2, table] of Object.entries(payload.schema)) {
|
|
28815
29437
|
const rowsAttr = table.rowCount !== undefined ? ` rows="${table.rowCount}"` : "";
|
|
28816
29438
|
const pkAttr = table.primaryKey ? ` primary_key="${table.primaryKey.join(",")}"` : "";
|
|
28817
|
-
parts.push(` <table name="${escapeXml(
|
|
29439
|
+
parts.push(` <table name="${escapeXml(tableName2)}"${rowsAttr}${pkAttr}>`);
|
|
28818
29440
|
for (const col of table.columns) {
|
|
28819
29441
|
const pk = col.primaryKey ? ' primary_key="true"' : "";
|
|
28820
29442
|
const nulls = col.nullable ? ' nullable="true"' : ' nullable="false"';
|
|
@@ -28925,9 +29547,9 @@ function serializeMarkdown(payload) {
|
|
|
28925
29547
|
parts.push(`*No table schemas cached or active.*`);
|
|
28926
29548
|
parts.push(``);
|
|
28927
29549
|
} else {
|
|
28928
|
-
for (const [
|
|
29550
|
+
for (const [tableName2, table] of Object.entries(payload.schema)) {
|
|
28929
29551
|
const rows = table.rowCount !== undefined ? ` (~${table.rowCount.toLocaleString()} rows)` : "";
|
|
28930
|
-
parts.push(`### \`${
|
|
29552
|
+
parts.push(`### \`${tableName2}\`${rows}`);
|
|
28931
29553
|
parts.push(``);
|
|
28932
29554
|
parts.push(`| Column | Type | Nullable | Primary Key | Default | References |`);
|
|
28933
29555
|
parts.push(`| :--- | :--- | :---: | :---: | :--- | :--- |`);
|
|
@@ -30060,37 +30682,37 @@ async function blacklistList(configPath, format = "text") {
|
|
|
30060
30682
|
console.error(`\u26A0 blacklist.columns["${w2.collection}"]: '${w2.raw}' is ignored on mongo connections (${w2.reason}).`);
|
|
30061
30683
|
}
|
|
30062
30684
|
}
|
|
30063
|
-
async function blacklistTableAdd(
|
|
30064
|
-
if (!isValidTableName(
|
|
30065
|
-
throw new Error(t_vars("errors.invalid_table_name", { table:
|
|
30685
|
+
async function blacklistTableAdd(tableName2, configPath) {
|
|
30686
|
+
if (!isValidTableName(tableName2)) {
|
|
30687
|
+
throw new Error(t_vars("errors.invalid_table_name", { table: tableName2 }));
|
|
30066
30688
|
}
|
|
30067
30689
|
const config = await configModule.read(configPath);
|
|
30068
30690
|
const blacklist = getOrInitBlacklist(config);
|
|
30069
|
-
if (blacklist.tables.includes(
|
|
30070
|
-
throw new Error(t_vars("errors.table_already_blacklisted", { table:
|
|
30691
|
+
if (blacklist.tables.includes(tableName2)) {
|
|
30692
|
+
throw new Error(t_vars("errors.table_already_blacklisted", { table: tableName2 }));
|
|
30071
30693
|
}
|
|
30072
30694
|
const newBlacklist = {
|
|
30073
30695
|
...blacklist,
|
|
30074
|
-
tables: [...blacklist.tables,
|
|
30696
|
+
tables: [...blacklist.tables, tableName2]
|
|
30075
30697
|
};
|
|
30076
30698
|
await configModule.write(configPath, { ...config, blacklist: newBlacklist });
|
|
30077
|
-
console.log(t_vars("blacklist.table_added", { table:
|
|
30699
|
+
console.log(t_vars("blacklist.table_added", { table: tableName2 }));
|
|
30078
30700
|
}
|
|
30079
|
-
async function blacklistTableRemove(
|
|
30080
|
-
if (!isValidTableName(
|
|
30081
|
-
throw new Error(t_vars("errors.invalid_table_name", { table:
|
|
30701
|
+
async function blacklistTableRemove(tableName2, configPath) {
|
|
30702
|
+
if (!isValidTableName(tableName2)) {
|
|
30703
|
+
throw new Error(t_vars("errors.invalid_table_name", { table: tableName2 }));
|
|
30082
30704
|
}
|
|
30083
30705
|
const config = await configModule.read(configPath);
|
|
30084
30706
|
const blacklist = getOrInitBlacklist(config);
|
|
30085
|
-
if (!blacklist.tables.includes(
|
|
30086
|
-
throw new Error(t_vars("errors.table_not_in_blacklist", { table:
|
|
30707
|
+
if (!blacklist.tables.includes(tableName2)) {
|
|
30708
|
+
throw new Error(t_vars("errors.table_not_in_blacklist", { table: tableName2 }));
|
|
30087
30709
|
}
|
|
30088
30710
|
const newBlacklist = {
|
|
30089
30711
|
...blacklist,
|
|
30090
|
-
tables: blacklist.tables.filter((t2) => t2 !==
|
|
30712
|
+
tables: blacklist.tables.filter((t2) => t2 !== tableName2)
|
|
30091
30713
|
};
|
|
30092
30714
|
await configModule.write(configPath, { ...config, blacklist: newBlacklist });
|
|
30093
|
-
console.log(t_vars("blacklist.table_removed", { table:
|
|
30715
|
+
console.log(t_vars("blacklist.table_removed", { table: tableName2 }));
|
|
30094
30716
|
}
|
|
30095
30717
|
async function blacklistColumnAdd(identifier, configPath) {
|
|
30096
30718
|
const parsed = parseColumnIdentifier(identifier);
|
|
@@ -30160,17 +30782,17 @@ var init_blacklist2 = __esm(() => {
|
|
|
30160
30782
|
}
|
|
30161
30783
|
});
|
|
30162
30784
|
tableCmd = blacklistCommand.command("table").description(t("blacklist.tables_label"));
|
|
30163
|
-
tableCmd.command("add <table>").description("Add table to blacklist").option("--config <path>", "Path to .dbcli config file", DEFAULT_CONFIG_PATH).action(async (
|
|
30785
|
+
tableCmd.command("add <table>").description("Add table to blacklist").option("--config <path>", "Path to .dbcli config file", DEFAULT_CONFIG_PATH).action(async (tableName2, options) => {
|
|
30164
30786
|
try {
|
|
30165
|
-
await blacklistTableAdd(
|
|
30787
|
+
await blacklistTableAdd(tableName2, options.config || DEFAULT_CONFIG_PATH);
|
|
30166
30788
|
} catch (error) {
|
|
30167
30789
|
console.error(error.message);
|
|
30168
30790
|
process.exit(1);
|
|
30169
30791
|
}
|
|
30170
30792
|
});
|
|
30171
|
-
tableCmd.command("remove <table>").description("Remove table from blacklist").option("--config <path>", "Path to .dbcli config file", DEFAULT_CONFIG_PATH).action(async (
|
|
30793
|
+
tableCmd.command("remove <table>").description("Remove table from blacklist").option("--config <path>", "Path to .dbcli config file", DEFAULT_CONFIG_PATH).action(async (tableName2, options) => {
|
|
30172
30794
|
try {
|
|
30173
|
-
await blacklistTableRemove(
|
|
30795
|
+
await blacklistTableRemove(tableName2, options.config || DEFAULT_CONFIG_PATH);
|
|
30174
30796
|
} catch (error) {
|
|
30175
30797
|
console.error(error.message);
|
|
30176
30798
|
process.exit(1);
|
|
@@ -30804,7 +31426,7 @@ function consumeCreateTable(node, statement, context) {
|
|
|
30804
31426
|
return;
|
|
30805
31427
|
}
|
|
30806
31428
|
const identity = resolveTableIdentifier(parsedIdentifier);
|
|
30807
|
-
const
|
|
31429
|
+
const tableName2 = qualifiedTableName(identity);
|
|
30808
31430
|
const unsupportedTableField = firstMeaningfulUnsupportedField(node, [
|
|
30809
31431
|
"type",
|
|
30810
31432
|
"keyword",
|
|
@@ -30814,14 +31436,14 @@ function consumeCreateTable(node, statement, context) {
|
|
|
30814
31436
|
]);
|
|
30815
31437
|
if (unsupportedTableField) {
|
|
30816
31438
|
context.unparsed.push({
|
|
30817
|
-
location:
|
|
31439
|
+
location: tableName2,
|
|
30818
31440
|
reason: `blocked: unsupported CREATE TABLE ${unsupportedTableField}`
|
|
30819
31441
|
});
|
|
30820
31442
|
return;
|
|
30821
31443
|
}
|
|
30822
31444
|
if (!Array.isArray(node.create_definitions)) {
|
|
30823
31445
|
context.unparsed.push({
|
|
30824
|
-
location:
|
|
31446
|
+
location: tableName2,
|
|
30825
31447
|
reason: "blocked: unsupported CREATE TABLE without column definitions"
|
|
30826
31448
|
});
|
|
30827
31449
|
return;
|
|
@@ -30836,7 +31458,7 @@ function consumeCreateTable(node, statement, context) {
|
|
|
30836
31458
|
const definitions = [];
|
|
30837
31459
|
for (const value of node.create_definitions) {
|
|
30838
31460
|
if (!isAst(value)) {
|
|
30839
|
-
pushUnsupportedDefinition(
|
|
31461
|
+
pushUnsupportedDefinition(tableName2, "malformed", context.unparsed);
|
|
30840
31462
|
continue;
|
|
30841
31463
|
}
|
|
30842
31464
|
definitions.push(value);
|
|
@@ -30862,14 +31484,14 @@ function consumeCreateTable(node, statement, context) {
|
|
|
30862
31484
|
consumeTablePrimaryKey(definition, table, context.unparsed);
|
|
30863
31485
|
continue;
|
|
30864
31486
|
}
|
|
30865
|
-
pushUnsupportedDefinition(
|
|
31487
|
+
pushUnsupportedDefinition(tableName2, stringValue2(definition.constraint_type) ?? stringValue2(definition.resource) ?? "malformed", context.unparsed);
|
|
30866
31488
|
}
|
|
30867
31489
|
const key = tableIdentityKey(identity);
|
|
30868
31490
|
if (context.tablesByIdentity.has(key)) {
|
|
30869
|
-
context.duplicateTableIdentity ??=
|
|
31491
|
+
context.duplicateTableIdentity ??= tableName2;
|
|
30870
31492
|
context.unparsed.push({
|
|
30871
|
-
location:
|
|
30872
|
-
reason: `blocked: duplicate table identity '${
|
|
31493
|
+
location: tableName2,
|
|
31494
|
+
reason: `blocked: duplicate table identity '${tableName2}'`
|
|
30873
31495
|
});
|
|
30874
31496
|
return;
|
|
30875
31497
|
}
|
|
@@ -31012,12 +31634,12 @@ function consumeCreateIndex(node, statement, context) {
|
|
|
31012
31634
|
return;
|
|
31013
31635
|
}
|
|
31014
31636
|
const identity = resolveTableIdentifier(parsedIdentifier);
|
|
31015
|
-
const
|
|
31637
|
+
const tableName2 = qualifiedTableName(identity);
|
|
31016
31638
|
const target = context.tablesByIdentity.get(tableIdentityKey(identity));
|
|
31017
31639
|
if (!target) {
|
|
31018
31640
|
context.unparsed.push({
|
|
31019
31641
|
location: indexName ?? "index",
|
|
31020
|
-
reason: `blocked: CREATE INDEX targets unknown table '${
|
|
31642
|
+
reason: `blocked: CREATE INDEX targets unknown table '${tableName2}'`
|
|
31021
31643
|
});
|
|
31022
31644
|
return;
|
|
31023
31645
|
}
|
|
@@ -31050,7 +31672,7 @@ function consumeCreateIndex(node, statement, context) {
|
|
|
31050
31672
|
}
|
|
31051
31673
|
if (columns.value.length === 0) {
|
|
31052
31674
|
context.unparsed.push({
|
|
31053
|
-
location: indexName ?? `${
|
|
31675
|
+
location: indexName ?? `${tableName2} index`,
|
|
31054
31676
|
reason: "blocked: malformed CREATE INDEX without columns"
|
|
31055
31677
|
});
|
|
31056
31678
|
return;
|
|
@@ -31498,9 +32120,9 @@ function firstMeaningfulUnsupportedField(value, allowed) {
|
|
|
31498
32120
|
}
|
|
31499
32121
|
return null;
|
|
31500
32122
|
}
|
|
31501
|
-
function pushUnsupportedDefinition(
|
|
32123
|
+
function pushUnsupportedDefinition(tableName2, definition, unparsed) {
|
|
31502
32124
|
unparsed.push({
|
|
31503
|
-
location: `${
|
|
32125
|
+
location: `${tableName2} (${definition})`,
|
|
31504
32126
|
reason: `blocked: unsupported table definition '${definition}'`
|
|
31505
32127
|
});
|
|
31506
32128
|
}
|
|
@@ -31626,7 +32248,7 @@ function collectBlocks(text3, unparsed) {
|
|
|
31626
32248
|
function parseModel2(block, unparsed) {
|
|
31627
32249
|
const fields = [];
|
|
31628
32250
|
const pendingIndexes = [];
|
|
31629
|
-
let
|
|
32251
|
+
let tableName2 = block.name;
|
|
31630
32252
|
let hasTableMap = false;
|
|
31631
32253
|
for (const sourceLine of block.lines) {
|
|
31632
32254
|
const line = stripLineComment(sourceLine.text).trim();
|
|
@@ -31645,7 +32267,7 @@ function parseModel2(block, unparsed) {
|
|
|
31645
32267
|
addBlocked2(unparsed, block.name, `malformed or duplicate '@@map' attribute`);
|
|
31646
32268
|
continue;
|
|
31647
32269
|
}
|
|
31648
|
-
|
|
32270
|
+
tableName2 = mapped;
|
|
31649
32271
|
hasTableMap = true;
|
|
31650
32272
|
continue;
|
|
31651
32273
|
}
|
|
@@ -31671,12 +32293,12 @@ function parseModel2(block, unparsed) {
|
|
|
31671
32293
|
}
|
|
31672
32294
|
return {
|
|
31673
32295
|
name: block.name,
|
|
31674
|
-
tableName,
|
|
32296
|
+
tableName: tableName2,
|
|
31675
32297
|
fields,
|
|
31676
32298
|
pendingIndexes,
|
|
31677
32299
|
pendingRelations: [],
|
|
31678
32300
|
columnNames: new Map,
|
|
31679
|
-
table: { identity: { table:
|
|
32301
|
+
table: { identity: { table: tableName2 }, columns: [], indexes: [], foreignKeys: [] }
|
|
31680
32302
|
};
|
|
31681
32303
|
}
|
|
31682
32304
|
function parseField2(line, modelName, lineNumber, unparsed) {
|
|
@@ -32735,31 +33357,31 @@ function compareSnapshots(before, after) {
|
|
|
32735
33357
|
const removedColumns = [];
|
|
32736
33358
|
const modifiedColumns = [];
|
|
32737
33359
|
const indexChanges = [];
|
|
32738
|
-
for (const
|
|
32739
|
-
const added = after.tables[
|
|
33360
|
+
for (const tableName2 of addedTables) {
|
|
33361
|
+
const added = after.tables[tableName2];
|
|
32740
33362
|
if (!added)
|
|
32741
33363
|
continue;
|
|
32742
33364
|
for (const col of added.columns) {
|
|
32743
33365
|
addedColumns.push({
|
|
32744
|
-
table:
|
|
33366
|
+
table: tableName2,
|
|
32745
33367
|
column: col.name,
|
|
32746
33368
|
type: col.type,
|
|
32747
33369
|
nullable: col.nullable
|
|
32748
33370
|
});
|
|
32749
33371
|
}
|
|
32750
33372
|
}
|
|
32751
|
-
for (const
|
|
32752
|
-
const removed = before.tables[
|
|
33373
|
+
for (const tableName2 of removedTables) {
|
|
33374
|
+
const removed = before.tables[tableName2];
|
|
32753
33375
|
if (!removed)
|
|
32754
33376
|
continue;
|
|
32755
33377
|
for (const col of removed.columns) {
|
|
32756
|
-
removedColumns.push({ table:
|
|
33378
|
+
removedColumns.push({ table: tableName2, column: col.name, type: col.type });
|
|
32757
33379
|
}
|
|
32758
33380
|
}
|
|
32759
33381
|
const commonTables = Array.from(afterTables).filter((t2) => beforeTables.has(t2));
|
|
32760
|
-
for (const
|
|
32761
|
-
const beforeTable = before.tables[
|
|
32762
|
-
const afterTable = after.tables[
|
|
33382
|
+
for (const tableName2 of commonTables) {
|
|
33383
|
+
const beforeTable = before.tables[tableName2];
|
|
33384
|
+
const afterTable = after.tables[tableName2];
|
|
32763
33385
|
if (!beforeTable || !afterTable)
|
|
32764
33386
|
continue;
|
|
32765
33387
|
const beforeColMap = new Map(beforeTable.columns.map((c) => [c.name, c]));
|
|
@@ -32767,7 +33389,7 @@ function compareSnapshots(before, after) {
|
|
|
32767
33389
|
for (const [name, col] of afterColMap) {
|
|
32768
33390
|
if (!beforeColMap.has(name)) {
|
|
32769
33391
|
addedColumns.push({
|
|
32770
|
-
table:
|
|
33392
|
+
table: tableName2,
|
|
32771
33393
|
column: name,
|
|
32772
33394
|
type: col.type,
|
|
32773
33395
|
nullable: col.nullable
|
|
@@ -32776,7 +33398,7 @@ function compareSnapshots(before, after) {
|
|
|
32776
33398
|
}
|
|
32777
33399
|
for (const [name, col] of beforeColMap) {
|
|
32778
33400
|
if (!afterColMap.has(name)) {
|
|
32779
|
-
removedColumns.push({ table:
|
|
33401
|
+
removedColumns.push({ table: tableName2, column: name, type: col.type });
|
|
32780
33402
|
}
|
|
32781
33403
|
}
|
|
32782
33404
|
for (const [name, afterCol] of afterColMap) {
|
|
@@ -32785,7 +33407,7 @@ function compareSnapshots(before, after) {
|
|
|
32785
33407
|
continue;
|
|
32786
33408
|
if (beforeCol.type.toLowerCase() !== afterCol.type.toLowerCase() || beforeCol.nullable !== afterCol.nullable) {
|
|
32787
33409
|
modifiedColumns.push({
|
|
32788
|
-
table:
|
|
33410
|
+
table: tableName2,
|
|
32789
33411
|
column: name,
|
|
32790
33412
|
before: { type: beforeCol.type, nullable: beforeCol.nullable },
|
|
32791
33413
|
after: { type: afterCol.type, nullable: afterCol.nullable }
|
|
@@ -32796,12 +33418,12 @@ function compareSnapshots(before, after) {
|
|
|
32796
33418
|
const afterIndexes = new Map((afterTable.indexes || []).map((i) => [i.name, i]));
|
|
32797
33419
|
for (const name of afterIndexes.keys()) {
|
|
32798
33420
|
if (!beforeIndexes.has(name)) {
|
|
32799
|
-
indexChanges.push({ table:
|
|
33421
|
+
indexChanges.push({ table: tableName2, name, change: "added" });
|
|
32800
33422
|
}
|
|
32801
33423
|
}
|
|
32802
33424
|
for (const name of beforeIndexes.keys()) {
|
|
32803
33425
|
if (!afterIndexes.has(name)) {
|
|
32804
|
-
indexChanges.push({ table:
|
|
33426
|
+
indexChanges.push({ table: tableName2, name, change: "removed" });
|
|
32805
33427
|
}
|
|
32806
33428
|
}
|
|
32807
33429
|
}
|
|
@@ -38394,10 +39016,10 @@ async function loadSchemaContext(dbcliPath, connectionName) {
|
|
|
38394
39016
|
const loader3 = new SchemaLayeredLoader(dbcliPath, { connectionName });
|
|
38395
39017
|
const { cache, index } = await loader3.initialize();
|
|
38396
39018
|
const schema = {};
|
|
38397
|
-
for (const
|
|
38398
|
-
const table = await cache.getTableSchema(
|
|
39019
|
+
for (const tableName2 of Object.keys(index?.tables ?? {})) {
|
|
39020
|
+
const table = await cache.getTableSchema(tableName2);
|
|
38399
39021
|
if (table) {
|
|
38400
|
-
schema[
|
|
39022
|
+
schema[tableName2] = table;
|
|
38401
39023
|
}
|
|
38402
39024
|
}
|
|
38403
39025
|
return buildSchemaContext(schema);
|
|
@@ -39833,9 +40455,9 @@ var init_select_star = __esm(() => {
|
|
|
39833
40455
|
span: findingSpan(ctx.sql, "select *"),
|
|
39834
40456
|
schemaVerified: false
|
|
39835
40457
|
};
|
|
39836
|
-
const
|
|
39837
|
-
if (ctx.schema.available &&
|
|
39838
|
-
const table = ctx.schema.getTable(
|
|
40458
|
+
const tableName2 = singlePhysicalTable(ctx.ast);
|
|
40459
|
+
if (ctx.schema.available && tableName2 && isSoleUnqualifiedStar(ctx.ast)) {
|
|
40460
|
+
const table = ctx.schema.getTable(tableName2);
|
|
39839
40461
|
if (table && table.columns.length > 0) {
|
|
39840
40462
|
if (wildcardIndexes.length === 1) {
|
|
39841
40463
|
const wildcardIndex = wildcardIndexes[0];
|
|
@@ -40744,17 +41366,17 @@ function canonicalizeEvidenceReceiptCommand(command, operation = "assert") {
|
|
|
40744
41366
|
function sha2563(value) {
|
|
40745
41367
|
return `sha256:${createHash5("sha256").update(value).digest("hex")}`;
|
|
40746
41368
|
}
|
|
40747
|
-
function verdictFingerprint(
|
|
40748
|
-
if (!record3(
|
|
41369
|
+
function verdictFingerprint(verdict2) {
|
|
41370
|
+
if (!record3(verdict2) || typeof verdict2.pass !== "boolean" || !Array.isArray(verdict2.checks) || verdict2.checks.length > 1000) {
|
|
40749
41371
|
throw new EvidenceReceiptValidationError("verdict must contain bounded pass bits");
|
|
40750
41372
|
}
|
|
40751
|
-
const bits =
|
|
41373
|
+
const bits = verdict2.checks.map((check) => {
|
|
40752
41374
|
if (!record3(check) || Object.keys(check).some((key) => key !== "pass") || typeof check.pass !== "boolean") {
|
|
40753
41375
|
throw new EvidenceReceiptValidationError("verdict checks must contain only pass bits");
|
|
40754
41376
|
}
|
|
40755
41377
|
return check.pass;
|
|
40756
41378
|
});
|
|
40757
|
-
return sha2563(JSON.stringify({ pass:
|
|
41379
|
+
return sha2563(JSON.stringify({ pass: verdict2.pass, checks: bits }));
|
|
40758
41380
|
}
|
|
40759
41381
|
function verificationOutcomeFingerprint(status2, artifactPersisted) {
|
|
40760
41382
|
if (!isVerificationStatus(status2)) {
|
|
@@ -41052,7 +41674,7 @@ var init_assert = __esm(() => {
|
|
|
41052
41674
|
const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection11(config.connection));
|
|
41053
41675
|
await adapter.connect();
|
|
41054
41676
|
let auditRef = null;
|
|
41055
|
-
let
|
|
41677
|
+
let verdict2;
|
|
41056
41678
|
try {
|
|
41057
41679
|
const blacklistManager = new BlacklistManager(config);
|
|
41058
41680
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
@@ -41079,9 +41701,9 @@ var init_assert = __esm(() => {
|
|
|
41079
41701
|
});
|
|
41080
41702
|
checks.push(...compareAgainst(current, baseline, options.tolerance));
|
|
41081
41703
|
}
|
|
41082
|
-
|
|
41704
|
+
verdict2 = { pass: checks.every((c) => c.pass), checks };
|
|
41083
41705
|
auditRef = await writeAuditEntry(config, "assert", options, {
|
|
41084
|
-
success:
|
|
41706
|
+
success: verdict2.pass,
|
|
41085
41707
|
sql: query
|
|
41086
41708
|
});
|
|
41087
41709
|
} finally {
|
|
@@ -41091,7 +41713,7 @@ var init_assert = __esm(() => {
|
|
|
41091
41713
|
if (options.writeVerificationArtifact === true && verificationSubject) {
|
|
41092
41714
|
try {
|
|
41093
41715
|
const artifact2 = buildAssertVerificationArtifact({
|
|
41094
|
-
verdict,
|
|
41716
|
+
verdict: verdict2,
|
|
41095
41717
|
subject: verificationSubject,
|
|
41096
41718
|
summary: options.verificationSummary,
|
|
41097
41719
|
argv: process.argv,
|
|
@@ -41110,7 +41732,7 @@ var init_assert = __esm(() => {
|
|
|
41110
41732
|
context: await buildEvidenceReceiptContext(config, process.cwd()),
|
|
41111
41733
|
auditRef,
|
|
41112
41734
|
verificationArtifactRef: verificationArtifactPath ? basename7(verificationArtifactPath) : null,
|
|
41113
|
-
verdict
|
|
41735
|
+
verdict: verdict2
|
|
41114
41736
|
});
|
|
41115
41737
|
evidenceReceiptPath = await writeEvidenceReceipt(process.cwd(), options.evidenceReceipt, receipt);
|
|
41116
41738
|
} catch {
|
|
@@ -41120,23 +41742,23 @@ var init_assert = __esm(() => {
|
|
|
41120
41742
|
}
|
|
41121
41743
|
if (options.format === "json") {
|
|
41122
41744
|
console.log(JSON.stringify({
|
|
41123
|
-
...
|
|
41745
|
+
...verdict2,
|
|
41124
41746
|
...verificationArtifactPath ? { verificationArtifactPath } : {},
|
|
41125
41747
|
...evidenceReceiptPath ? { evidenceReceiptPath } : {}
|
|
41126
41748
|
}, null, 2));
|
|
41127
41749
|
} else {
|
|
41128
|
-
for (const c of
|
|
41750
|
+
for (const c of verdict2.checks) {
|
|
41129
41751
|
console.log(`${c.pass ? "PASS" : "FAIL"} ${c.name} expected=${c.expected} actual=${c.actual}`);
|
|
41130
41752
|
}
|
|
41131
41753
|
console.log(`
|
|
41132
|
-
Verdict: ${
|
|
41754
|
+
Verdict: ${verdict2.pass ? "PASS" : "FAIL"}`);
|
|
41133
41755
|
if (verificationArtifactPath) {
|
|
41134
41756
|
console.log(`Verification artifact: ${verificationArtifactPath}`);
|
|
41135
41757
|
}
|
|
41136
41758
|
if (evidenceReceiptPath)
|
|
41137
41759
|
console.log(`Evidence receipt: ${evidenceReceiptPath}`);
|
|
41138
41760
|
}
|
|
41139
|
-
process.exit(
|
|
41761
|
+
process.exit(verdict2.pass || options.fail === false ? 0 : 1);
|
|
41140
41762
|
} catch (error) {
|
|
41141
41763
|
if (error instanceof Error) {
|
|
41142
41764
|
console.error(error.message);
|
|
@@ -44132,6 +44754,7 @@ var init_repl_engine = __esm(() => {
|
|
|
44132
44754
|
init_meta_commands();
|
|
44133
44755
|
init_history_manager();
|
|
44134
44756
|
init_permission_guard();
|
|
44757
|
+
init_redis();
|
|
44135
44758
|
init_query_result_formatter();
|
|
44136
44759
|
init_message_loader();
|
|
44137
44760
|
init_sql_tables();
|
|
@@ -44169,9 +44792,9 @@ function createCompleter(ctx) {
|
|
|
44169
44792
|
const sqlHits = matchWithSuffix([...SQL_KEYWORDS_FOR_COMPLETION], lastWord);
|
|
44170
44793
|
const tableHits = matchWithSuffix(allTableNames, lastWord);
|
|
44171
44794
|
const colHits = isColumnPosition(upperLine) ? (() => {
|
|
44172
|
-
const
|
|
44173
|
-
if (
|
|
44174
|
-
return matchWithSuffix([...ctx.columnsByTable[
|
|
44795
|
+
const tableName2 = extractTableFromLine(trimmed, allTableNames);
|
|
44796
|
+
if (tableName2 && ctx.columnsByTable[tableName2]) {
|
|
44797
|
+
return matchWithSuffix([...ctx.columnsByTable[tableName2]], lastWord);
|
|
44175
44798
|
}
|
|
44176
44799
|
return matchWithSuffix(uniqueColumns, lastWord);
|
|
44177
44800
|
})() : [[], lastWord];
|
|
@@ -45260,13 +45883,16 @@ class DDLExecutor {
|
|
|
45260
45883
|
};
|
|
45261
45884
|
}
|
|
45262
45885
|
if (isDestructiveOperation2(operation) && !options.force) {
|
|
45263
|
-
|
|
45886
|
+
if (!options.confirm) {
|
|
45887
|
+
throw new DDLExecutionError(`Refusing to run ${operation.kind} without confirmation: no confirmation handler was ` + "supplied. Pass options.confirm to ask the user, or options.force to proceed unattended.");
|
|
45888
|
+
}
|
|
45889
|
+
const confirmed = await options.confirm({ operation: operation.kind, sql });
|
|
45264
45890
|
if (!confirmed) {
|
|
45265
45891
|
return {
|
|
45266
|
-
status: "
|
|
45892
|
+
status: "cancelled",
|
|
45267
45893
|
operation: operation.kind,
|
|
45268
45894
|
sql,
|
|
45269
|
-
warnings
|
|
45895
|
+
warnings,
|
|
45270
45896
|
timestamp: timestamp2,
|
|
45271
45897
|
dryRun: false
|
|
45272
45898
|
};
|
|
@@ -45341,8 +45967,15 @@ class DDLExecutor {
|
|
|
45341
45967
|
} catch {}
|
|
45342
45968
|
}
|
|
45343
45969
|
}
|
|
45970
|
+
var DDLExecutionError;
|
|
45344
45971
|
var init_ddl_executor = __esm(() => {
|
|
45345
|
-
|
|
45972
|
+
DDLExecutionError = class DDLExecutionError extends Error {
|
|
45973
|
+
constructor(message) {
|
|
45974
|
+
super(message);
|
|
45975
|
+
this.name = "DDLExecutionError";
|
|
45976
|
+
Object.setPrototypeOf(this, DDLExecutionError.prototype);
|
|
45977
|
+
}
|
|
45978
|
+
};
|
|
45346
45979
|
});
|
|
45347
45980
|
|
|
45348
45981
|
// src/commands/migrate.ts
|
|
@@ -45386,7 +46019,8 @@ async function runDDL(operation, opts, command) {
|
|
|
45386
46019
|
const executor3 = new DDLExecutor(adapter, generator, config.permission, blacklistManager);
|
|
45387
46020
|
const result = await executor3.execute(operation, {
|
|
45388
46021
|
execute: opts.execute,
|
|
45389
|
-
force: opts.force
|
|
46022
|
+
force: opts.force,
|
|
46023
|
+
confirm: confirmDdlInteractively
|
|
45390
46024
|
});
|
|
45391
46025
|
console.log(JSON.stringify({
|
|
45392
46026
|
status: result.status,
|
|
@@ -45428,6 +46062,7 @@ var init_migrate = __esm(() => {
|
|
|
45428
46062
|
init_adapters();
|
|
45429
46063
|
init_ddl2();
|
|
45430
46064
|
init_ddl_executor();
|
|
46065
|
+
init_mutation_confirm();
|
|
45431
46066
|
init_config_path();
|
|
45432
46067
|
migrateCommand = new Command("migrate").description(t("migrate.description"));
|
|
45433
46068
|
addExecOpts(migrateCommand.command("create <table>").description(t("migrate.create_description")).option("--column <spec...>", 'Column definitions (e.g., "id:serial:pk" "name:varchar(50):not-null")')).action(async (table, opts, command) => {
|