@carllee1983/dbcli 1.58.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/rules/dbcli.mdc +12 -0
- package/.cursor/skills/dbcli/reference.md +43 -1
- package/.github/skills/dbcli/SKILL.md +12 -0
- package/.github/skills/dbcli/reference.md +43 -1
- package/CHANGELOG.md +31 -0
- package/assets/SKILL.md +12 -0
- package/assets/SKILL.zh-TW.md +11 -0
- package/assets/reference.md +43 -1
- package/dist/cli-runtime.mjs +590 -197
- package/dist/cli.mjs +1 -1
- package/dist/core.mjs +34 -0
- package/package.json +1 -1
- package/plugins/dbcli-agent/skills/dbcli/SKILL.md +12 -0
- package/plugins/dbcli-agent/skills/dbcli/reference.md +43 -1
- package/skills/dbcli/SKILL.md +12 -0
- package/skills/dbcli/reference.md +43 -1
package/dist/cli-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: {
|
|
@@ -9473,6 +9473,23 @@ var init_ceremony = __esm(() => {
|
|
|
9473
9473
|
confirm_destructive_warning_ddl: "\u26A0\uFE0F Warning: this schema change is destructive and cannot be undone!",
|
|
9474
9474
|
confirm_ddl_prompt: "Are you sure you want to run this {operation}? This cannot be undone.",
|
|
9475
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.",
|
|
9476
9493
|
recovery_none: "This cannot be undone automatically \u2014 restore from a backup to get the previous values back.",
|
|
9477
9494
|
recovery_retry: "Re-run with --recovery to write a recovery plan, then read it with: dbcli recover",
|
|
9478
9495
|
blacklist_hint: "Run `dbcli blacklist list` to see what is protected"
|
|
@@ -9500,6 +9517,23 @@ var init_ceremony2 = __esm(() => {
|
|
|
9500
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",
|
|
9501
9518
|
confirm_ddl_prompt: "\u78BA\u5B9A\u8981\u57F7\u884C\u9019\u500B {operation} \u55CE\uFF1F\u6B64\u64CD\u4F5C\u7121\u6CD5\u5FA9\u539F\u3002",
|
|
9502
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",
|
|
9503
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",
|
|
9504
9538
|
recovery_retry: "\u52A0\u4E0A --recovery \u91CD\u8DD1\u53EF\u7522\u751F\u56DE\u5FA9\u8A08\u756B\uFF0C\u518D\u7528 dbcli recover \u8B80\u53D6",
|
|
9505
9539
|
blacklist_hint: "\u57F7\u884C `dbcli blacklist list` \u67E5\u770B\u53D7\u4FDD\u8B77\u7684\u9805\u76EE"
|
|
@@ -9730,7 +9764,8 @@ async function readLineFromStdin(prompt = "") {
|
|
|
9730
9764
|
}
|
|
9731
9765
|
async function textFallback(message, defaultValue) {
|
|
9732
9766
|
const displayMessage = defaultValue ? `${message} [${defaultValue}]: ` : `${message}: `;
|
|
9733
|
-
|
|
9767
|
+
process.stderr.write(displayMessage);
|
|
9768
|
+
const answer = await readLineFromStdin();
|
|
9734
9769
|
return answer.trim() || defaultValue || "";
|
|
9735
9770
|
}
|
|
9736
9771
|
async function selectFallback(message, choices) {
|
|
@@ -9756,7 +9791,7 @@ async function text(message, defaultValue) {
|
|
|
9756
9791
|
const inquirer = await loadInquirer();
|
|
9757
9792
|
if (!inquirer)
|
|
9758
9793
|
return textFallback(message, defaultValue);
|
|
9759
|
-
return await inquirer.input({ message, default: defaultValue });
|
|
9794
|
+
return await inquirer.input({ message, default: defaultValue }, { output: process.stderr });
|
|
9760
9795
|
}
|
|
9761
9796
|
async function select(message, choices) {
|
|
9762
9797
|
if (!process.stdin.isTTY)
|
|
@@ -22202,6 +22237,360 @@ var init_elasticsearch = __esm(() => {
|
|
|
22202
22237
|
ES_DOCUMENT_PATH = /\/(?:_doc|_source)\/[^/?]+/;
|
|
22203
22238
|
});
|
|
22204
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
|
+
|
|
22205
22594
|
// src/core/permission/redis.ts
|
|
22206
22595
|
var exports_redis = {};
|
|
22207
22596
|
__export(exports_redis, {
|
|
@@ -22528,10 +22917,24 @@ async function executeQueryCommand(sql, options, command, runtimeOverrides = {})
|
|
|
22528
22917
|
connectionName = connectionNames[0];
|
|
22529
22918
|
config = await runtime.loadConfig(configPath, connectionName);
|
|
22530
22919
|
const context = { config, configPath, connectionName };
|
|
22920
|
+
if (!await runtime.enforceGate(resolvedSql, options, context))
|
|
22921
|
+
return;
|
|
22531
22922
|
await runtime.preflight(resolvedSql, options, context, fieldSelection, false);
|
|
22532
22923
|
const execution = await runtime.executeConnection(resolvedSql, options, context, fieldSelection);
|
|
22533
22924
|
await runtime.presentResult(resolvedSql, options, execution, tableCellLimit);
|
|
22534
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
|
+
}
|
|
22535
22938
|
let auditId = null;
|
|
22536
22939
|
let envelopeId;
|
|
22537
22940
|
if (options.recovery === true && !multiConnectionRequest) {
|
|
@@ -22618,6 +23021,48 @@ function scopeMultiConnectionNotices(execution) {
|
|
|
22618
23021
|
notices: []
|
|
22619
23022
|
};
|
|
22620
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
|
+
}
|
|
22621
23066
|
async function preflightQuery(query, options, context, fieldSelection, multiConnection) {
|
|
22622
23067
|
const { config } = context;
|
|
22623
23068
|
if (!config.connection)
|
|
@@ -23061,6 +23506,7 @@ var init_query = __esm(() => {
|
|
|
23061
23506
|
executeConnection: executeConnectionQuery,
|
|
23062
23507
|
presentResult: presentSingleResult,
|
|
23063
23508
|
writeAudit: writeAuditEntry,
|
|
23509
|
+
enforceGate: enforceQueryWriteGate,
|
|
23064
23510
|
randomUUID: () => crypto3.randomUUID(),
|
|
23065
23511
|
emitRecovery: async (error, context, options) => {
|
|
23066
23512
|
const { emitRecoveryEnvelope: emitRecoveryEnvelope2 } = await Promise.resolve().then(() => (init_recovery(), exports_recovery));
|
|
@@ -23085,7 +23531,7 @@ __export(exports_inline_registrars, {
|
|
|
23085
23531
|
registerDeleteCommand: () => registerDeleteCommand
|
|
23086
23532
|
});
|
|
23087
23533
|
function registerQueryCommand(program2, run) {
|
|
23088
|
-
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) => {
|
|
23089
23535
|
const rawArgs = command.parent?.rawArgs ?? [];
|
|
23090
23536
|
const rawTruncate = findLongOptionValue(rawArgs, "--truncate");
|
|
23091
23537
|
const rootUse = command.parent?.opts().use;
|
|
@@ -23401,8 +23847,8 @@ function applyBlacklistRules(facts, blacklist, factors) {
|
|
|
23401
23847
|
}
|
|
23402
23848
|
function findBlacklistedColumnsForTable(blacklist, table) {
|
|
23403
23849
|
const columns = blacklist.columns ?? {};
|
|
23404
|
-
for (const [
|
|
23405
|
-
if (
|
|
23850
|
+
for (const [tableName2, columnNames] of Object.entries(columns)) {
|
|
23851
|
+
if (tableName2.toLowerCase() === table.toLowerCase()) {
|
|
23406
23852
|
return new Set(columnNames);
|
|
23407
23853
|
}
|
|
23408
23854
|
}
|
|
@@ -25366,12 +25812,12 @@ class DataExecutor {
|
|
|
25366
25812
|
this.dbSystem = dbSystem;
|
|
25367
25813
|
this.blacklistValidator = blacklistValidator;
|
|
25368
25814
|
}
|
|
25369
|
-
buildInsertSql(
|
|
25815
|
+
buildInsertSql(tableName2, data, schema) {
|
|
25370
25816
|
const columnNames = schema.columns.map((col) => col.name);
|
|
25371
25817
|
const dataKeys = Object.keys(data);
|
|
25372
25818
|
for (const key of dataKeys) {
|
|
25373
25819
|
if (!columnNames.includes(key)) {
|
|
25374
|
-
throw new Error(`Column "${key}" not found in table "${
|
|
25820
|
+
throw new Error(`Column "${key}" not found in table "${tableName2}". Valid columns: ${columnNames.join(", ")}`);
|
|
25375
25821
|
}
|
|
25376
25822
|
}
|
|
25377
25823
|
const columns = dataKeys;
|
|
@@ -25379,38 +25825,38 @@ class DataExecutor {
|
|
|
25379
25825
|
const systemType = this.getSystemType();
|
|
25380
25826
|
const placeholders = systemType === "postgresql" ? dataKeys.map((_, index) => `$${index + 1}`).join(", ") : dataKeys.map(() => "?").join(", ");
|
|
25381
25827
|
const quote = this.getQuoteChar();
|
|
25382
|
-
const sql = `INSERT INTO ${quote}${
|
|
25828
|
+
const sql = `INSERT INTO ${quote}${tableName2}${quote} (${columns.map((col) => `${quote}${col}${quote}`).join(", ")}) VALUES (${placeholders})`;
|
|
25383
25829
|
return {
|
|
25384
25830
|
sql,
|
|
25385
25831
|
params: values
|
|
25386
25832
|
};
|
|
25387
25833
|
}
|
|
25388
|
-
async executeInsert(
|
|
25834
|
+
async executeInsert(tableName2, data, schema, options) {
|
|
25389
25835
|
const timestamp = new Date().toISOString();
|
|
25390
25836
|
try {
|
|
25391
|
-
this.checkBlacklist("INSERT",
|
|
25837
|
+
this.checkBlacklist("INSERT", tableName2);
|
|
25392
25838
|
enforcePermissionForType("INSERT", this.permission);
|
|
25393
|
-
return await this.executeMutation("insert", this.buildInsertSql(
|
|
25839
|
+
return await this.executeMutation("insert", this.buildInsertSql(tableName2, data, schema), timestamp, options, { destructive: false });
|
|
25394
25840
|
} catch (error) {
|
|
25395
25841
|
return this.handleMutationError("insert", timestamp, error);
|
|
25396
25842
|
}
|
|
25397
25843
|
}
|
|
25398
|
-
async executeUpdate(
|
|
25844
|
+
async executeUpdate(tableName2, data, where, schema, options) {
|
|
25399
25845
|
const timestamp = new Date().toISOString();
|
|
25400
25846
|
try {
|
|
25401
|
-
this.checkBlacklist("UPDATE",
|
|
25847
|
+
this.checkBlacklist("UPDATE", tableName2);
|
|
25402
25848
|
enforcePermissionForType("UPDATE", this.permission);
|
|
25403
|
-
return await this.executeMutation("update", this.buildUpdateSql(
|
|
25849
|
+
return await this.executeMutation("update", this.buildUpdateSql(tableName2, data, where, schema), timestamp, options, { destructive: false });
|
|
25404
25850
|
} catch (error) {
|
|
25405
25851
|
return this.handleMutationError("update", timestamp, error);
|
|
25406
25852
|
}
|
|
25407
25853
|
}
|
|
25408
|
-
async executeDelete(
|
|
25854
|
+
async executeDelete(tableName2, where, schema, options) {
|
|
25409
25855
|
const timestamp = new Date().toISOString();
|
|
25410
25856
|
try {
|
|
25411
|
-
this.checkBlacklist("DELETE",
|
|
25857
|
+
this.checkBlacklist("DELETE", tableName2);
|
|
25412
25858
|
enforcePermissionForType("DELETE", this.permission);
|
|
25413
|
-
return await this.executeMutation("delete", this.buildDeleteSql(
|
|
25859
|
+
return await this.executeMutation("delete", this.buildDeleteSql(tableName2, where, schema), timestamp, options, { destructive: true });
|
|
25414
25860
|
} catch (error) {
|
|
25415
25861
|
return this.handleMutationError("delete", timestamp, error);
|
|
25416
25862
|
}
|
|
@@ -25421,8 +25867,8 @@ class DataExecutor {
|
|
|
25421
25867
|
getQuoteChar() {
|
|
25422
25868
|
return this.dbSystem === "mysql" ? "`" : '"';
|
|
25423
25869
|
}
|
|
25424
|
-
checkBlacklist(operation,
|
|
25425
|
-
this.blacklistValidator?.checkTableBlacklist(operation,
|
|
25870
|
+
checkBlacklist(operation, tableName2) {
|
|
25871
|
+
this.blacklistValidator?.checkTableBlacklist(operation, tableName2, []);
|
|
25426
25872
|
}
|
|
25427
25873
|
async executeMutation(operation, statement, timestamp, options, { destructive }) {
|
|
25428
25874
|
if (options?.dryRun) {
|
|
@@ -25477,13 +25923,13 @@ class DataExecutor {
|
|
|
25477
25923
|
error: `${operation.toUpperCase()} failed: ${errorMessage2}`
|
|
25478
25924
|
};
|
|
25479
25925
|
}
|
|
25480
|
-
buildUpdateSql(
|
|
25926
|
+
buildUpdateSql(tableName2, data, where, schema) {
|
|
25481
25927
|
const dataKeys = Object.keys(data);
|
|
25482
25928
|
const whereKeys = Object.keys(where);
|
|
25483
25929
|
const columnNames = schema.columns.map((col) => col.name);
|
|
25484
25930
|
for (const key of [...dataKeys, ...whereKeys]) {
|
|
25485
25931
|
if (!columnNames.includes(key)) {
|
|
25486
|
-
throw new Error(`Column "${key}" not found in table "${
|
|
25932
|
+
throw new Error(`Column "${key}" not found in table "${tableName2}"`);
|
|
25487
25933
|
}
|
|
25488
25934
|
}
|
|
25489
25935
|
const systemType = this.getSystemType();
|
|
@@ -25497,16 +25943,16 @@ class DataExecutor {
|
|
|
25497
25943
|
const placeholder = systemType === "postgresql" ? `$${paramIndex++}` : "?";
|
|
25498
25944
|
return `${quote}${key}${quote} = ${placeholder}`;
|
|
25499
25945
|
}).join(" AND ");
|
|
25500
|
-
const sql = `UPDATE ${quote}${
|
|
25946
|
+
const sql = `UPDATE ${quote}${tableName2}${quote} SET ${setClause} WHERE ${whereClause}`;
|
|
25501
25947
|
const params = [...dataKeys.map((key) => data[key]), ...whereKeys.map((key) => where[key])];
|
|
25502
25948
|
return { sql, params };
|
|
25503
25949
|
}
|
|
25504
|
-
buildDeleteSql(
|
|
25950
|
+
buildDeleteSql(tableName2, where, schema) {
|
|
25505
25951
|
const whereKeys = Object.keys(where);
|
|
25506
25952
|
const columnNames = schema.columns.map((col) => col.name);
|
|
25507
25953
|
for (const key of whereKeys) {
|
|
25508
25954
|
if (!columnNames.includes(key)) {
|
|
25509
|
-
throw new Error(`Column "${key}" not found in table "${
|
|
25955
|
+
throw new Error(`Column "${key}" not found in table "${tableName2}"`);
|
|
25510
25956
|
}
|
|
25511
25957
|
}
|
|
25512
25958
|
const systemType = this.getSystemType();
|
|
@@ -25516,7 +25962,7 @@ class DataExecutor {
|
|
|
25516
25962
|
const placeholder = systemType === "postgresql" ? `$${paramIndex++}` : "?";
|
|
25517
25963
|
return `${quote}${key}${quote} = ${placeholder}`;
|
|
25518
25964
|
}).join(" AND ");
|
|
25519
|
-
const sql = `DELETE FROM ${quote}${
|
|
25965
|
+
const sql = `DELETE FROM ${quote}${tableName2}${quote} WHERE ${whereClause}`;
|
|
25520
25966
|
const params = whereKeys.map((key) => where[key]);
|
|
25521
25967
|
return { sql, params };
|
|
25522
25968
|
}
|
|
@@ -25590,87 +26036,6 @@ function auditOutcomeForMutation(result, table, recoveryRef) {
|
|
|
25590
26036
|
};
|
|
25591
26037
|
}
|
|
25592
26038
|
|
|
25593
|
-
// src/commands/mutation-outcome.ts
|
|
25594
|
-
function shouldRenderForHuman(context) {
|
|
25595
|
-
if (context.format === "json")
|
|
25596
|
-
return false;
|
|
25597
|
-
return context.isTTY;
|
|
25598
|
-
}
|
|
25599
|
-
function humanOutputContext(options) {
|
|
25600
|
-
return { isTTY: process.stdout.isTTY === true, ...options.format && { format: options.format } };
|
|
25601
|
-
}
|
|
25602
|
-
function renderMutationOutcome(result, table, elapsedMs) {
|
|
25603
|
-
if (result.status === "error") {
|
|
25604
|
-
return [
|
|
25605
|
-
t_vars("ceremony.failed", { reason: result.error ?? t("ceremony.unknown_error") }),
|
|
25606
|
-
t("ceremony.recovery_retry")
|
|
25607
|
-
];
|
|
25608
|
-
}
|
|
25609
|
-
if (result.status === "cancelled") {
|
|
25610
|
-
return [t_vars("ceremony.cancelled", { table })];
|
|
25611
|
-
}
|
|
25612
|
-
if (result.status === "dry_run") {
|
|
25613
|
-
return [t_vars("ceremony.dry_run", { table })];
|
|
25614
|
-
}
|
|
25615
|
-
const elapsed = t_vars("ceremony.elapsed", { seconds: (elapsedMs / 1000).toFixed(2) });
|
|
25616
|
-
if (result.rows_affected === 0) {
|
|
25617
|
-
return [t_vars("ceremony.matched_nothing", { table }), elapsed];
|
|
25618
|
-
}
|
|
25619
|
-
return [
|
|
25620
|
-
t_vars(VERB_KEY[result.operation], { count: result.rows_affected, table }),
|
|
25621
|
-
elapsed,
|
|
25622
|
-
t("ceremony.recovery_none")
|
|
25623
|
-
];
|
|
25624
|
-
}
|
|
25625
|
-
function printMutationOutcome(result, table, elapsedMs, context, envelopeExtras = {}) {
|
|
25626
|
-
if (shouldRenderForHuman(context)) {
|
|
25627
|
-
const writeLine = result.status === "error" ? console.error : console.log;
|
|
25628
|
-
for (const line of renderMutationOutcome(result, table, elapsedMs))
|
|
25629
|
-
writeLine(line);
|
|
25630
|
-
return;
|
|
25631
|
-
}
|
|
25632
|
-
console.log(JSON.stringify({ ...mutationEnvelope(result), ...envelopeExtras }, null, 2));
|
|
25633
|
-
}
|
|
25634
|
-
function printMutationFailure(error, operation, context) {
|
|
25635
|
-
if (shouldRenderForHuman(context)) {
|
|
25636
|
-
console.error(t_vars("ceremony.failed", { reason: error.message }));
|
|
25637
|
-
if (error instanceof BlacklistError) {
|
|
25638
|
-
console.error(t("ceremony.blacklist_hint"));
|
|
25639
|
-
}
|
|
25640
|
-
return;
|
|
25641
|
-
}
|
|
25642
|
-
console.log(JSON.stringify({ status: "error", operation, rows_affected: 0, error: error.message }, null, 2));
|
|
25643
|
-
}
|
|
25644
|
-
function cancelledOutcome(operation, preview) {
|
|
25645
|
-
return {
|
|
25646
|
-
status: "cancelled",
|
|
25647
|
-
operation,
|
|
25648
|
-
rows_affected: 0,
|
|
25649
|
-
sql: preview,
|
|
25650
|
-
timestamp: new Date().toISOString()
|
|
25651
|
-
};
|
|
25652
|
-
}
|
|
25653
|
-
function mutationEnvelope(result) {
|
|
25654
|
-
return {
|
|
25655
|
-
status: result.status,
|
|
25656
|
-
operation: result.operation,
|
|
25657
|
-
rows_affected: result.rows_affected,
|
|
25658
|
-
...result.timestamp && { timestamp: result.timestamp },
|
|
25659
|
-
...result.sql && { sql: result.sql },
|
|
25660
|
-
...result.error && { error: result.error }
|
|
25661
|
-
};
|
|
25662
|
-
}
|
|
25663
|
-
var VERB_KEY;
|
|
25664
|
-
var init_mutation_outcome = __esm(() => {
|
|
25665
|
-
init_message_loader();
|
|
25666
|
-
init_blacklist();
|
|
25667
|
-
VERB_KEY = {
|
|
25668
|
-
insert: "ceremony.inserted",
|
|
25669
|
-
update: "ceremony.updated",
|
|
25670
|
-
delete: "ceremony.deleted"
|
|
25671
|
-
};
|
|
25672
|
-
});
|
|
25673
|
-
|
|
25674
26039
|
// src/core/mongo/dry-run-formatter.ts
|
|
25675
26040
|
function pretty(value) {
|
|
25676
26041
|
return JSON.stringify(value, null, INDENT);
|
|
@@ -26974,6 +27339,20 @@ async function updateCommand(table, options, command) {
|
|
|
26974
27339
|
await adapter.connect();
|
|
26975
27340
|
try {
|
|
26976
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
|
+
}
|
|
26977
27356
|
const dbSystem = config.connection.system === "postgresql" ? "postgresql" : "mysql";
|
|
26978
27357
|
const blacklistManager = new BlacklistManager(config);
|
|
26979
27358
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
@@ -26999,6 +27378,11 @@ async function updateCommand(table, options, command) {
|
|
|
26999
27378
|
await adapter.disconnect();
|
|
27000
27379
|
}
|
|
27001
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
|
+
}
|
|
27002
27386
|
let auditId = null;
|
|
27003
27387
|
let envelopeId;
|
|
27004
27388
|
if (options.recovery === true) {
|
|
@@ -27254,6 +27638,20 @@ async function deleteCommand(table, options, command) {
|
|
|
27254
27638
|
await adapter.connect();
|
|
27255
27639
|
try {
|
|
27256
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
|
+
}
|
|
27257
27655
|
const dbSystem = config.connection.system === "postgresql" ? "postgresql" : "mysql";
|
|
27258
27656
|
const blacklistManager = new BlacklistManager(config);
|
|
27259
27657
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
@@ -27279,6 +27677,11 @@ async function deleteCommand(table, options, command) {
|
|
|
27279
27677
|
await adapter.disconnect();
|
|
27280
27678
|
}
|
|
27281
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
|
+
}
|
|
27282
27685
|
let auditId = null;
|
|
27283
27686
|
let envelopeId;
|
|
27284
27687
|
if (options.recovery === true) {
|
|
@@ -27777,16 +28180,6 @@ var init_export = __esm(() => {
|
|
|
27777
28180
|
SQL_PATTERN = /^\s*(SELECT|INSERT|UPDATE|DELETE|CREATE|DROP|ALTER|SHOW|DESCRIBE)\b/i;
|
|
27778
28181
|
});
|
|
27779
28182
|
|
|
27780
|
-
// src/core/sql-parser.ts
|
|
27781
|
-
function sqlParser() {
|
|
27782
|
-
if (!cached2) {
|
|
27783
|
-
const { Parser } = __require("node-sql-parser");
|
|
27784
|
-
cached2 = new Parser;
|
|
27785
|
-
}
|
|
27786
|
-
return cached2;
|
|
27787
|
-
}
|
|
27788
|
-
var cached2 = null;
|
|
27789
|
-
|
|
27790
28183
|
// src/core/explain/read-only.ts
|
|
27791
28184
|
function containsAnalyzeUnsafeNode(node) {
|
|
27792
28185
|
if (!node || typeof node !== "object")
|
|
@@ -28891,12 +29284,12 @@ function compactVisibleSchema(config) {
|
|
|
28891
29284
|
const blacklistManager = new BlacklistManager(config);
|
|
28892
29285
|
const compactSchema = {};
|
|
28893
29286
|
const rawSchema = config.schema ?? {};
|
|
28894
|
-
for (const [
|
|
28895
|
-
if (blacklistManager.isTableBlacklisted(
|
|
29287
|
+
for (const [tableName2, tableSchema] of Object.entries(rawSchema)) {
|
|
29288
|
+
if (blacklistManager.isTableBlacklisted(tableName2))
|
|
28896
29289
|
continue;
|
|
28897
29290
|
const columns = [];
|
|
28898
29291
|
for (const col of tableSchema.columns || []) {
|
|
28899
|
-
if (blacklistManager.isColumnBlacklisted(
|
|
29292
|
+
if (blacklistManager.isColumnBlacklisted(tableName2, col.name))
|
|
28900
29293
|
continue;
|
|
28901
29294
|
const compactCol = {
|
|
28902
29295
|
name: col.name,
|
|
@@ -28916,7 +29309,7 @@ function compactVisibleSchema(config) {
|
|
|
28916
29309
|
columns.push(compactCol);
|
|
28917
29310
|
}
|
|
28918
29311
|
const compactTable = {
|
|
28919
|
-
name: tableSchema.name ||
|
|
29312
|
+
name: tableSchema.name || tableName2,
|
|
28920
29313
|
columns
|
|
28921
29314
|
};
|
|
28922
29315
|
const rowCount = tableSchema.estimatedRowCount ?? tableSchema.rowCount;
|
|
@@ -28932,7 +29325,7 @@ function compactVisibleSchema(config) {
|
|
|
28932
29325
|
refColumns: fk.refColumns
|
|
28933
29326
|
}));
|
|
28934
29327
|
}
|
|
28935
|
-
compactSchema[
|
|
29328
|
+
compactSchema[tableName2] = compactTable;
|
|
28936
29329
|
}
|
|
28937
29330
|
return compactSchema;
|
|
28938
29331
|
}
|
|
@@ -29040,10 +29433,10 @@ function serializeXml(payload) {
|
|
|
29040
29433
|
}
|
|
29041
29434
|
if (Object.keys(payload.schema).length > 0) {
|
|
29042
29435
|
parts.push(" <tables>");
|
|
29043
|
-
for (const [
|
|
29436
|
+
for (const [tableName2, table] of Object.entries(payload.schema)) {
|
|
29044
29437
|
const rowsAttr = table.rowCount !== undefined ? ` rows="${table.rowCount}"` : "";
|
|
29045
29438
|
const pkAttr = table.primaryKey ? ` primary_key="${table.primaryKey.join(",")}"` : "";
|
|
29046
|
-
parts.push(` <table name="${escapeXml(
|
|
29439
|
+
parts.push(` <table name="${escapeXml(tableName2)}"${rowsAttr}${pkAttr}>`);
|
|
29047
29440
|
for (const col of table.columns) {
|
|
29048
29441
|
const pk = col.primaryKey ? ' primary_key="true"' : "";
|
|
29049
29442
|
const nulls = col.nullable ? ' nullable="true"' : ' nullable="false"';
|
|
@@ -29154,9 +29547,9 @@ function serializeMarkdown(payload) {
|
|
|
29154
29547
|
parts.push(`*No table schemas cached or active.*`);
|
|
29155
29548
|
parts.push(``);
|
|
29156
29549
|
} else {
|
|
29157
|
-
for (const [
|
|
29550
|
+
for (const [tableName2, table] of Object.entries(payload.schema)) {
|
|
29158
29551
|
const rows = table.rowCount !== undefined ? ` (~${table.rowCount.toLocaleString()} rows)` : "";
|
|
29159
|
-
parts.push(`### \`${
|
|
29552
|
+
parts.push(`### \`${tableName2}\`${rows}`);
|
|
29160
29553
|
parts.push(``);
|
|
29161
29554
|
parts.push(`| Column | Type | Nullable | Primary Key | Default | References |`);
|
|
29162
29555
|
parts.push(`| :--- | :--- | :---: | :---: | :--- | :--- |`);
|
|
@@ -30289,37 +30682,37 @@ async function blacklistList(configPath, format = "text") {
|
|
|
30289
30682
|
console.error(`\u26A0 blacklist.columns["${w2.collection}"]: '${w2.raw}' is ignored on mongo connections (${w2.reason}).`);
|
|
30290
30683
|
}
|
|
30291
30684
|
}
|
|
30292
|
-
async function blacklistTableAdd(
|
|
30293
|
-
if (!isValidTableName(
|
|
30294
|
-
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 }));
|
|
30295
30688
|
}
|
|
30296
30689
|
const config = await configModule.read(configPath);
|
|
30297
30690
|
const blacklist = getOrInitBlacklist(config);
|
|
30298
|
-
if (blacklist.tables.includes(
|
|
30299
|
-
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 }));
|
|
30300
30693
|
}
|
|
30301
30694
|
const newBlacklist = {
|
|
30302
30695
|
...blacklist,
|
|
30303
|
-
tables: [...blacklist.tables,
|
|
30696
|
+
tables: [...blacklist.tables, tableName2]
|
|
30304
30697
|
};
|
|
30305
30698
|
await configModule.write(configPath, { ...config, blacklist: newBlacklist });
|
|
30306
|
-
console.log(t_vars("blacklist.table_added", { table:
|
|
30699
|
+
console.log(t_vars("blacklist.table_added", { table: tableName2 }));
|
|
30307
30700
|
}
|
|
30308
|
-
async function blacklistTableRemove(
|
|
30309
|
-
if (!isValidTableName(
|
|
30310
|
-
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 }));
|
|
30311
30704
|
}
|
|
30312
30705
|
const config = await configModule.read(configPath);
|
|
30313
30706
|
const blacklist = getOrInitBlacklist(config);
|
|
30314
|
-
if (!blacklist.tables.includes(
|
|
30315
|
-
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 }));
|
|
30316
30709
|
}
|
|
30317
30710
|
const newBlacklist = {
|
|
30318
30711
|
...blacklist,
|
|
30319
|
-
tables: blacklist.tables.filter((t2) => t2 !==
|
|
30712
|
+
tables: blacklist.tables.filter((t2) => t2 !== tableName2)
|
|
30320
30713
|
};
|
|
30321
30714
|
await configModule.write(configPath, { ...config, blacklist: newBlacklist });
|
|
30322
|
-
console.log(t_vars("blacklist.table_removed", { table:
|
|
30715
|
+
console.log(t_vars("blacklist.table_removed", { table: tableName2 }));
|
|
30323
30716
|
}
|
|
30324
30717
|
async function blacklistColumnAdd(identifier, configPath) {
|
|
30325
30718
|
const parsed = parseColumnIdentifier(identifier);
|
|
@@ -30389,17 +30782,17 @@ var init_blacklist2 = __esm(() => {
|
|
|
30389
30782
|
}
|
|
30390
30783
|
});
|
|
30391
30784
|
tableCmd = blacklistCommand.command("table").description(t("blacklist.tables_label"));
|
|
30392
|
-
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) => {
|
|
30393
30786
|
try {
|
|
30394
|
-
await blacklistTableAdd(
|
|
30787
|
+
await blacklistTableAdd(tableName2, options.config || DEFAULT_CONFIG_PATH);
|
|
30395
30788
|
} catch (error) {
|
|
30396
30789
|
console.error(error.message);
|
|
30397
30790
|
process.exit(1);
|
|
30398
30791
|
}
|
|
30399
30792
|
});
|
|
30400
|
-
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) => {
|
|
30401
30794
|
try {
|
|
30402
|
-
await blacklistTableRemove(
|
|
30795
|
+
await blacklistTableRemove(tableName2, options.config || DEFAULT_CONFIG_PATH);
|
|
30403
30796
|
} catch (error) {
|
|
30404
30797
|
console.error(error.message);
|
|
30405
30798
|
process.exit(1);
|
|
@@ -31033,7 +31426,7 @@ function consumeCreateTable(node, statement, context) {
|
|
|
31033
31426
|
return;
|
|
31034
31427
|
}
|
|
31035
31428
|
const identity = resolveTableIdentifier(parsedIdentifier);
|
|
31036
|
-
const
|
|
31429
|
+
const tableName2 = qualifiedTableName(identity);
|
|
31037
31430
|
const unsupportedTableField = firstMeaningfulUnsupportedField(node, [
|
|
31038
31431
|
"type",
|
|
31039
31432
|
"keyword",
|
|
@@ -31043,14 +31436,14 @@ function consumeCreateTable(node, statement, context) {
|
|
|
31043
31436
|
]);
|
|
31044
31437
|
if (unsupportedTableField) {
|
|
31045
31438
|
context.unparsed.push({
|
|
31046
|
-
location:
|
|
31439
|
+
location: tableName2,
|
|
31047
31440
|
reason: `blocked: unsupported CREATE TABLE ${unsupportedTableField}`
|
|
31048
31441
|
});
|
|
31049
31442
|
return;
|
|
31050
31443
|
}
|
|
31051
31444
|
if (!Array.isArray(node.create_definitions)) {
|
|
31052
31445
|
context.unparsed.push({
|
|
31053
|
-
location:
|
|
31446
|
+
location: tableName2,
|
|
31054
31447
|
reason: "blocked: unsupported CREATE TABLE without column definitions"
|
|
31055
31448
|
});
|
|
31056
31449
|
return;
|
|
@@ -31065,7 +31458,7 @@ function consumeCreateTable(node, statement, context) {
|
|
|
31065
31458
|
const definitions = [];
|
|
31066
31459
|
for (const value of node.create_definitions) {
|
|
31067
31460
|
if (!isAst(value)) {
|
|
31068
|
-
pushUnsupportedDefinition(
|
|
31461
|
+
pushUnsupportedDefinition(tableName2, "malformed", context.unparsed);
|
|
31069
31462
|
continue;
|
|
31070
31463
|
}
|
|
31071
31464
|
definitions.push(value);
|
|
@@ -31091,14 +31484,14 @@ function consumeCreateTable(node, statement, context) {
|
|
|
31091
31484
|
consumeTablePrimaryKey(definition, table, context.unparsed);
|
|
31092
31485
|
continue;
|
|
31093
31486
|
}
|
|
31094
|
-
pushUnsupportedDefinition(
|
|
31487
|
+
pushUnsupportedDefinition(tableName2, stringValue2(definition.constraint_type) ?? stringValue2(definition.resource) ?? "malformed", context.unparsed);
|
|
31095
31488
|
}
|
|
31096
31489
|
const key = tableIdentityKey(identity);
|
|
31097
31490
|
if (context.tablesByIdentity.has(key)) {
|
|
31098
|
-
context.duplicateTableIdentity ??=
|
|
31491
|
+
context.duplicateTableIdentity ??= tableName2;
|
|
31099
31492
|
context.unparsed.push({
|
|
31100
|
-
location:
|
|
31101
|
-
reason: `blocked: duplicate table identity '${
|
|
31493
|
+
location: tableName2,
|
|
31494
|
+
reason: `blocked: duplicate table identity '${tableName2}'`
|
|
31102
31495
|
});
|
|
31103
31496
|
return;
|
|
31104
31497
|
}
|
|
@@ -31241,12 +31634,12 @@ function consumeCreateIndex(node, statement, context) {
|
|
|
31241
31634
|
return;
|
|
31242
31635
|
}
|
|
31243
31636
|
const identity = resolveTableIdentifier(parsedIdentifier);
|
|
31244
|
-
const
|
|
31637
|
+
const tableName2 = qualifiedTableName(identity);
|
|
31245
31638
|
const target = context.tablesByIdentity.get(tableIdentityKey(identity));
|
|
31246
31639
|
if (!target) {
|
|
31247
31640
|
context.unparsed.push({
|
|
31248
31641
|
location: indexName ?? "index",
|
|
31249
|
-
reason: `blocked: CREATE INDEX targets unknown table '${
|
|
31642
|
+
reason: `blocked: CREATE INDEX targets unknown table '${tableName2}'`
|
|
31250
31643
|
});
|
|
31251
31644
|
return;
|
|
31252
31645
|
}
|
|
@@ -31279,7 +31672,7 @@ function consumeCreateIndex(node, statement, context) {
|
|
|
31279
31672
|
}
|
|
31280
31673
|
if (columns.value.length === 0) {
|
|
31281
31674
|
context.unparsed.push({
|
|
31282
|
-
location: indexName ?? `${
|
|
31675
|
+
location: indexName ?? `${tableName2} index`,
|
|
31283
31676
|
reason: "blocked: malformed CREATE INDEX without columns"
|
|
31284
31677
|
});
|
|
31285
31678
|
return;
|
|
@@ -31727,9 +32120,9 @@ function firstMeaningfulUnsupportedField(value, allowed) {
|
|
|
31727
32120
|
}
|
|
31728
32121
|
return null;
|
|
31729
32122
|
}
|
|
31730
|
-
function pushUnsupportedDefinition(
|
|
32123
|
+
function pushUnsupportedDefinition(tableName2, definition, unparsed) {
|
|
31731
32124
|
unparsed.push({
|
|
31732
|
-
location: `${
|
|
32125
|
+
location: `${tableName2} (${definition})`,
|
|
31733
32126
|
reason: `blocked: unsupported table definition '${definition}'`
|
|
31734
32127
|
});
|
|
31735
32128
|
}
|
|
@@ -31855,7 +32248,7 @@ function collectBlocks(text3, unparsed) {
|
|
|
31855
32248
|
function parseModel2(block, unparsed) {
|
|
31856
32249
|
const fields = [];
|
|
31857
32250
|
const pendingIndexes = [];
|
|
31858
|
-
let
|
|
32251
|
+
let tableName2 = block.name;
|
|
31859
32252
|
let hasTableMap = false;
|
|
31860
32253
|
for (const sourceLine of block.lines) {
|
|
31861
32254
|
const line = stripLineComment(sourceLine.text).trim();
|
|
@@ -31874,7 +32267,7 @@ function parseModel2(block, unparsed) {
|
|
|
31874
32267
|
addBlocked2(unparsed, block.name, `malformed or duplicate '@@map' attribute`);
|
|
31875
32268
|
continue;
|
|
31876
32269
|
}
|
|
31877
|
-
|
|
32270
|
+
tableName2 = mapped;
|
|
31878
32271
|
hasTableMap = true;
|
|
31879
32272
|
continue;
|
|
31880
32273
|
}
|
|
@@ -31900,12 +32293,12 @@ function parseModel2(block, unparsed) {
|
|
|
31900
32293
|
}
|
|
31901
32294
|
return {
|
|
31902
32295
|
name: block.name,
|
|
31903
|
-
tableName,
|
|
32296
|
+
tableName: tableName2,
|
|
31904
32297
|
fields,
|
|
31905
32298
|
pendingIndexes,
|
|
31906
32299
|
pendingRelations: [],
|
|
31907
32300
|
columnNames: new Map,
|
|
31908
|
-
table: { identity: { table:
|
|
32301
|
+
table: { identity: { table: tableName2 }, columns: [], indexes: [], foreignKeys: [] }
|
|
31909
32302
|
};
|
|
31910
32303
|
}
|
|
31911
32304
|
function parseField2(line, modelName, lineNumber, unparsed) {
|
|
@@ -32964,31 +33357,31 @@ function compareSnapshots(before, after) {
|
|
|
32964
33357
|
const removedColumns = [];
|
|
32965
33358
|
const modifiedColumns = [];
|
|
32966
33359
|
const indexChanges = [];
|
|
32967
|
-
for (const
|
|
32968
|
-
const added = after.tables[
|
|
33360
|
+
for (const tableName2 of addedTables) {
|
|
33361
|
+
const added = after.tables[tableName2];
|
|
32969
33362
|
if (!added)
|
|
32970
33363
|
continue;
|
|
32971
33364
|
for (const col of added.columns) {
|
|
32972
33365
|
addedColumns.push({
|
|
32973
|
-
table:
|
|
33366
|
+
table: tableName2,
|
|
32974
33367
|
column: col.name,
|
|
32975
33368
|
type: col.type,
|
|
32976
33369
|
nullable: col.nullable
|
|
32977
33370
|
});
|
|
32978
33371
|
}
|
|
32979
33372
|
}
|
|
32980
|
-
for (const
|
|
32981
|
-
const removed = before.tables[
|
|
33373
|
+
for (const tableName2 of removedTables) {
|
|
33374
|
+
const removed = before.tables[tableName2];
|
|
32982
33375
|
if (!removed)
|
|
32983
33376
|
continue;
|
|
32984
33377
|
for (const col of removed.columns) {
|
|
32985
|
-
removedColumns.push({ table:
|
|
33378
|
+
removedColumns.push({ table: tableName2, column: col.name, type: col.type });
|
|
32986
33379
|
}
|
|
32987
33380
|
}
|
|
32988
33381
|
const commonTables = Array.from(afterTables).filter((t2) => beforeTables.has(t2));
|
|
32989
|
-
for (const
|
|
32990
|
-
const beforeTable = before.tables[
|
|
32991
|
-
const afterTable = after.tables[
|
|
33382
|
+
for (const tableName2 of commonTables) {
|
|
33383
|
+
const beforeTable = before.tables[tableName2];
|
|
33384
|
+
const afterTable = after.tables[tableName2];
|
|
32992
33385
|
if (!beforeTable || !afterTable)
|
|
32993
33386
|
continue;
|
|
32994
33387
|
const beforeColMap = new Map(beforeTable.columns.map((c) => [c.name, c]));
|
|
@@ -32996,7 +33389,7 @@ function compareSnapshots(before, after) {
|
|
|
32996
33389
|
for (const [name, col] of afterColMap) {
|
|
32997
33390
|
if (!beforeColMap.has(name)) {
|
|
32998
33391
|
addedColumns.push({
|
|
32999
|
-
table:
|
|
33392
|
+
table: tableName2,
|
|
33000
33393
|
column: name,
|
|
33001
33394
|
type: col.type,
|
|
33002
33395
|
nullable: col.nullable
|
|
@@ -33005,7 +33398,7 @@ function compareSnapshots(before, after) {
|
|
|
33005
33398
|
}
|
|
33006
33399
|
for (const [name, col] of beforeColMap) {
|
|
33007
33400
|
if (!afterColMap.has(name)) {
|
|
33008
|
-
removedColumns.push({ table:
|
|
33401
|
+
removedColumns.push({ table: tableName2, column: name, type: col.type });
|
|
33009
33402
|
}
|
|
33010
33403
|
}
|
|
33011
33404
|
for (const [name, afterCol] of afterColMap) {
|
|
@@ -33014,7 +33407,7 @@ function compareSnapshots(before, after) {
|
|
|
33014
33407
|
continue;
|
|
33015
33408
|
if (beforeCol.type.toLowerCase() !== afterCol.type.toLowerCase() || beforeCol.nullable !== afterCol.nullable) {
|
|
33016
33409
|
modifiedColumns.push({
|
|
33017
|
-
table:
|
|
33410
|
+
table: tableName2,
|
|
33018
33411
|
column: name,
|
|
33019
33412
|
before: { type: beforeCol.type, nullable: beforeCol.nullable },
|
|
33020
33413
|
after: { type: afterCol.type, nullable: afterCol.nullable }
|
|
@@ -33025,12 +33418,12 @@ function compareSnapshots(before, after) {
|
|
|
33025
33418
|
const afterIndexes = new Map((afterTable.indexes || []).map((i) => [i.name, i]));
|
|
33026
33419
|
for (const name of afterIndexes.keys()) {
|
|
33027
33420
|
if (!beforeIndexes.has(name)) {
|
|
33028
|
-
indexChanges.push({ table:
|
|
33421
|
+
indexChanges.push({ table: tableName2, name, change: "added" });
|
|
33029
33422
|
}
|
|
33030
33423
|
}
|
|
33031
33424
|
for (const name of beforeIndexes.keys()) {
|
|
33032
33425
|
if (!afterIndexes.has(name)) {
|
|
33033
|
-
indexChanges.push({ table:
|
|
33426
|
+
indexChanges.push({ table: tableName2, name, change: "removed" });
|
|
33034
33427
|
}
|
|
33035
33428
|
}
|
|
33036
33429
|
}
|
|
@@ -38623,10 +39016,10 @@ async function loadSchemaContext(dbcliPath, connectionName) {
|
|
|
38623
39016
|
const loader3 = new SchemaLayeredLoader(dbcliPath, { connectionName });
|
|
38624
39017
|
const { cache, index } = await loader3.initialize();
|
|
38625
39018
|
const schema = {};
|
|
38626
|
-
for (const
|
|
38627
|
-
const table = await cache.getTableSchema(
|
|
39019
|
+
for (const tableName2 of Object.keys(index?.tables ?? {})) {
|
|
39020
|
+
const table = await cache.getTableSchema(tableName2);
|
|
38628
39021
|
if (table) {
|
|
38629
|
-
schema[
|
|
39022
|
+
schema[tableName2] = table;
|
|
38630
39023
|
}
|
|
38631
39024
|
}
|
|
38632
39025
|
return buildSchemaContext(schema);
|
|
@@ -40062,9 +40455,9 @@ var init_select_star = __esm(() => {
|
|
|
40062
40455
|
span: findingSpan(ctx.sql, "select *"),
|
|
40063
40456
|
schemaVerified: false
|
|
40064
40457
|
};
|
|
40065
|
-
const
|
|
40066
|
-
if (ctx.schema.available &&
|
|
40067
|
-
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);
|
|
40068
40461
|
if (table && table.columns.length > 0) {
|
|
40069
40462
|
if (wildcardIndexes.length === 1) {
|
|
40070
40463
|
const wildcardIndex = wildcardIndexes[0];
|
|
@@ -40973,17 +41366,17 @@ function canonicalizeEvidenceReceiptCommand(command, operation = "assert") {
|
|
|
40973
41366
|
function sha2563(value) {
|
|
40974
41367
|
return `sha256:${createHash5("sha256").update(value).digest("hex")}`;
|
|
40975
41368
|
}
|
|
40976
|
-
function verdictFingerprint(
|
|
40977
|
-
if (!record3(
|
|
41369
|
+
function verdictFingerprint(verdict2) {
|
|
41370
|
+
if (!record3(verdict2) || typeof verdict2.pass !== "boolean" || !Array.isArray(verdict2.checks) || verdict2.checks.length > 1000) {
|
|
40978
41371
|
throw new EvidenceReceiptValidationError("verdict must contain bounded pass bits");
|
|
40979
41372
|
}
|
|
40980
|
-
const bits =
|
|
41373
|
+
const bits = verdict2.checks.map((check) => {
|
|
40981
41374
|
if (!record3(check) || Object.keys(check).some((key) => key !== "pass") || typeof check.pass !== "boolean") {
|
|
40982
41375
|
throw new EvidenceReceiptValidationError("verdict checks must contain only pass bits");
|
|
40983
41376
|
}
|
|
40984
41377
|
return check.pass;
|
|
40985
41378
|
});
|
|
40986
|
-
return sha2563(JSON.stringify({ pass:
|
|
41379
|
+
return sha2563(JSON.stringify({ pass: verdict2.pass, checks: bits }));
|
|
40987
41380
|
}
|
|
40988
41381
|
function verificationOutcomeFingerprint(status2, artifactPersisted) {
|
|
40989
41382
|
if (!isVerificationStatus(status2)) {
|
|
@@ -41281,7 +41674,7 @@ var init_assert = __esm(() => {
|
|
|
41281
41674
|
const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection11(config.connection));
|
|
41282
41675
|
await adapter.connect();
|
|
41283
41676
|
let auditRef = null;
|
|
41284
|
-
let
|
|
41677
|
+
let verdict2;
|
|
41285
41678
|
try {
|
|
41286
41679
|
const blacklistManager = new BlacklistManager(config);
|
|
41287
41680
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
@@ -41308,9 +41701,9 @@ var init_assert = __esm(() => {
|
|
|
41308
41701
|
});
|
|
41309
41702
|
checks.push(...compareAgainst(current, baseline, options.tolerance));
|
|
41310
41703
|
}
|
|
41311
|
-
|
|
41704
|
+
verdict2 = { pass: checks.every((c) => c.pass), checks };
|
|
41312
41705
|
auditRef = await writeAuditEntry(config, "assert", options, {
|
|
41313
|
-
success:
|
|
41706
|
+
success: verdict2.pass,
|
|
41314
41707
|
sql: query
|
|
41315
41708
|
});
|
|
41316
41709
|
} finally {
|
|
@@ -41320,7 +41713,7 @@ var init_assert = __esm(() => {
|
|
|
41320
41713
|
if (options.writeVerificationArtifact === true && verificationSubject) {
|
|
41321
41714
|
try {
|
|
41322
41715
|
const artifact2 = buildAssertVerificationArtifact({
|
|
41323
|
-
verdict,
|
|
41716
|
+
verdict: verdict2,
|
|
41324
41717
|
subject: verificationSubject,
|
|
41325
41718
|
summary: options.verificationSummary,
|
|
41326
41719
|
argv: process.argv,
|
|
@@ -41339,7 +41732,7 @@ var init_assert = __esm(() => {
|
|
|
41339
41732
|
context: await buildEvidenceReceiptContext(config, process.cwd()),
|
|
41340
41733
|
auditRef,
|
|
41341
41734
|
verificationArtifactRef: verificationArtifactPath ? basename7(verificationArtifactPath) : null,
|
|
41342
|
-
verdict
|
|
41735
|
+
verdict: verdict2
|
|
41343
41736
|
});
|
|
41344
41737
|
evidenceReceiptPath = await writeEvidenceReceipt(process.cwd(), options.evidenceReceipt, receipt);
|
|
41345
41738
|
} catch {
|
|
@@ -41349,23 +41742,23 @@ var init_assert = __esm(() => {
|
|
|
41349
41742
|
}
|
|
41350
41743
|
if (options.format === "json") {
|
|
41351
41744
|
console.log(JSON.stringify({
|
|
41352
|
-
...
|
|
41745
|
+
...verdict2,
|
|
41353
41746
|
...verificationArtifactPath ? { verificationArtifactPath } : {},
|
|
41354
41747
|
...evidenceReceiptPath ? { evidenceReceiptPath } : {}
|
|
41355
41748
|
}, null, 2));
|
|
41356
41749
|
} else {
|
|
41357
|
-
for (const c of
|
|
41750
|
+
for (const c of verdict2.checks) {
|
|
41358
41751
|
console.log(`${c.pass ? "PASS" : "FAIL"} ${c.name} expected=${c.expected} actual=${c.actual}`);
|
|
41359
41752
|
}
|
|
41360
41753
|
console.log(`
|
|
41361
|
-
Verdict: ${
|
|
41754
|
+
Verdict: ${verdict2.pass ? "PASS" : "FAIL"}`);
|
|
41362
41755
|
if (verificationArtifactPath) {
|
|
41363
41756
|
console.log(`Verification artifact: ${verificationArtifactPath}`);
|
|
41364
41757
|
}
|
|
41365
41758
|
if (evidenceReceiptPath)
|
|
41366
41759
|
console.log(`Evidence receipt: ${evidenceReceiptPath}`);
|
|
41367
41760
|
}
|
|
41368
|
-
process.exit(
|
|
41761
|
+
process.exit(verdict2.pass || options.fail === false ? 0 : 1);
|
|
41369
41762
|
} catch (error) {
|
|
41370
41763
|
if (error instanceof Error) {
|
|
41371
41764
|
console.error(error.message);
|
|
@@ -44399,9 +44792,9 @@ function createCompleter(ctx) {
|
|
|
44399
44792
|
const sqlHits = matchWithSuffix([...SQL_KEYWORDS_FOR_COMPLETION], lastWord);
|
|
44400
44793
|
const tableHits = matchWithSuffix(allTableNames, lastWord);
|
|
44401
44794
|
const colHits = isColumnPosition(upperLine) ? (() => {
|
|
44402
|
-
const
|
|
44403
|
-
if (
|
|
44404
|
-
return matchWithSuffix([...ctx.columnsByTable[
|
|
44795
|
+
const tableName2 = extractTableFromLine(trimmed, allTableNames);
|
|
44796
|
+
if (tableName2 && ctx.columnsByTable[tableName2]) {
|
|
44797
|
+
return matchWithSuffix([...ctx.columnsByTable[tableName2]], lastWord);
|
|
44405
44798
|
}
|
|
44406
44799
|
return matchWithSuffix(uniqueColumns, lastWord);
|
|
44407
44800
|
})() : [[], lastWord];
|