@carllee1983/dbcli 1.37.1 → 1.39.1
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 +204 -282
- package/.cursor/skills/dbcli/reference.md +68 -0
- package/.github/skills/dbcli/SKILL.md +204 -282
- package/.github/skills/dbcli/reference.md +68 -0
- package/CHANGELOG.md +32 -0
- package/assets/SKILL.md +204 -282
- package/assets/SKILL.zh-TW.md +220 -331
- package/assets/reference.md +68 -0
- package/assets/ui-template.html +8 -8
- package/dist/cli.mjs +564 -72
- package/dist/core.mjs +14 -3
- package/dist/ui-style.css +1 -1
- package/package.json +1 -1
- package/plugins/dbcli-agent/skills/dbcli/SKILL.md +204 -282
- package/plugins/dbcli-agent/skills/dbcli/reference.md +68 -0
- package/skills/dbcli/SKILL.md +204 -282
- package/skills/dbcli/reference.md +68 -0
package/dist/cli.mjs
CHANGED
|
@@ -52,7 +52,7 @@ var package_default;
|
|
|
52
52
|
var init_package = __esm(() => {
|
|
53
53
|
package_default = {
|
|
54
54
|
name: "@carllee1983/dbcli",
|
|
55
|
-
version: "1.
|
|
55
|
+
version: "1.39.1",
|
|
56
56
|
description: "Database CLI for AI agents",
|
|
57
57
|
type: "module",
|
|
58
58
|
publishConfig: {
|
|
@@ -8809,8 +8809,9 @@ class BlacklistManager {
|
|
|
8809
8809
|
}
|
|
8810
8810
|
|
|
8811
8811
|
// src/core/saved-queries/types.ts
|
|
8812
|
-
var SavedQueryError;
|
|
8812
|
+
var SUPPORTED_CHART_TYPES, SavedQueryError;
|
|
8813
8813
|
var init_types2 = __esm(() => {
|
|
8814
|
+
SUPPORTED_CHART_TYPES = ["line", "bar", "area", "pie"];
|
|
8814
8815
|
SavedQueryError = class SavedQueryError extends Error {
|
|
8815
8816
|
code;
|
|
8816
8817
|
file;
|
|
@@ -9577,7 +9578,7 @@ function parseFrontmatter(yaml, input) {
|
|
|
9577
9578
|
const tags = Array.isArray(raw.tags) ? raw.tags.map(String) : [];
|
|
9578
9579
|
const index = typeof raw.index === "string" ? raw.index : undefined;
|
|
9579
9580
|
const intent = normaliseIntent(raw.intent, input);
|
|
9580
|
-
const visual = normaliseVisual(raw.visual);
|
|
9581
|
+
const visual = normaliseVisual(raw.visual, input);
|
|
9581
9582
|
const target = typeof raw.target === "string" ? raw.target : undefined;
|
|
9582
9583
|
const operation = raw.operation === "find" || raw.operation === "aggregate" ? raw.operation : undefined;
|
|
9583
9584
|
const verify = normaliseVerify(raw.verify, input);
|
|
@@ -9599,7 +9600,7 @@ function parseFrontmatter(yaml, input) {
|
|
|
9599
9600
|
warnings
|
|
9600
9601
|
};
|
|
9601
9602
|
}
|
|
9602
|
-
function normaliseVisual(value) {
|
|
9603
|
+
function normaliseVisual(value, input) {
|
|
9603
9604
|
if (value === undefined || value === null || typeof value !== "object")
|
|
9604
9605
|
return;
|
|
9605
9606
|
const raw = value;
|
|
@@ -9625,6 +9626,9 @@ function normaliseVisual(value) {
|
|
|
9625
9626
|
if (typeof item === "object" && item !== null) {
|
|
9626
9627
|
const c2 = item;
|
|
9627
9628
|
if (typeof c2.type === "string" && typeof c2.x === "string" && Array.isArray(c2.y)) {
|
|
9629
|
+
if (!SUPPORTED_CHART_TYPES.includes(c2.type)) {
|
|
9630
|
+
throw new SavedQueryError(`Snippet '${input.key}' has invalid chart type '${c2.type}' (supported: ${SUPPORTED_CHART_TYPES.join(", ")})`, "PARSE_ERROR", input.file);
|
|
9631
|
+
}
|
|
9628
9632
|
charts.push({
|
|
9629
9633
|
type: c2.type,
|
|
9630
9634
|
title: typeof c2.title === "string" ? c2.title : undefined,
|
|
@@ -23087,7 +23091,7 @@ var init_capabilities = __esm(() => {
|
|
|
23087
23091
|
queries: cap("limited", "local-write", "Snippet management works with Redis-specific saved-query limitations."),
|
|
23088
23092
|
insert: cap("unsupported", "none", "Dedicated write subcommand is not exposed."),
|
|
23089
23093
|
update: cap("unsupported", "none", "Dedicated write subcommand is not exposed."),
|
|
23090
|
-
delete: cap("
|
|
23094
|
+
delete: cap("limited", "db-write", "Basic delete via DEL/HDEL/LREM/SREM/ZREM (needs data-admin); supports --dry-run."),
|
|
23091
23095
|
export: cap("unsupported", "none", "Redis export is not supported."),
|
|
23092
23096
|
blacklist: cap("limited", "local-write", "Key-glob rejection (Redis-native pattern) plus value/hash-field masking ([REDACTED])."),
|
|
23093
23097
|
check: cap("unsupported", "none", "Data health check is SQL-only."),
|
|
@@ -24863,6 +24867,17 @@ class RedisAdapter {
|
|
|
24863
24867
|
const regexes = rules.map((p) => globToRegex(p));
|
|
24864
24868
|
return keys.filter((k) => !regexes.some((r) => r.test(k))).map((name2) => ({ name: name2 }));
|
|
24865
24869
|
}
|
|
24870
|
+
async sampleKeyNames(limit) {
|
|
24871
|
+
const client = this.requireClient();
|
|
24872
|
+
const keys = await scanAllKeys(client, "*", limit, limit);
|
|
24873
|
+
const truncated = keys.length >= limit;
|
|
24874
|
+
const rules = this.blacklistRules;
|
|
24875
|
+
if (rules.length === 0)
|
|
24876
|
+
return { names: keys, truncated };
|
|
24877
|
+
const regexes = rules.map((p) => globToRegex(p));
|
|
24878
|
+
const names = keys.filter((k) => !regexes.some((r) => r.test(k)));
|
|
24879
|
+
return { names, truncated };
|
|
24880
|
+
}
|
|
24866
24881
|
async getDbSize() {
|
|
24867
24882
|
const client = this.requireClient();
|
|
24868
24883
|
const reply = await client.send("DBSIZE", []);
|
|
@@ -25145,7 +25160,7 @@ function parseRedisCommand(input) {
|
|
|
25145
25160
|
push();
|
|
25146
25161
|
return tokens;
|
|
25147
25162
|
}
|
|
25148
|
-
async function scanAllKeys(client, pattern, count) {
|
|
25163
|
+
async function scanAllKeys(client, pattern, count, maxKeys = 1e5) {
|
|
25149
25164
|
const seen = new Set;
|
|
25150
25165
|
let cursor = "0";
|
|
25151
25166
|
do {
|
|
@@ -25160,7 +25175,7 @@ async function scanAllKeys(client, pattern, count) {
|
|
|
25160
25175
|
for (const k of batch)
|
|
25161
25176
|
seen.add(k);
|
|
25162
25177
|
cursor = next;
|
|
25163
|
-
if (seen.size >=
|
|
25178
|
+
if (seen.size >= maxKeys)
|
|
25164
25179
|
break;
|
|
25165
25180
|
} while (cursor !== "0");
|
|
25166
25181
|
return Array.from(seen);
|
|
@@ -28927,6 +28942,7 @@ var init_assert_artifact = __esm(() => {
|
|
|
28927
28942
|
"assertion",
|
|
28928
28943
|
"migration",
|
|
28929
28944
|
"backfill",
|
|
28945
|
+
"table",
|
|
28930
28946
|
"manual"
|
|
28931
28947
|
];
|
|
28932
28948
|
AssertArtifactError = class AssertArtifactError extends Error {
|
|
@@ -92930,12 +92946,277 @@ var init_rollback = __esm(() => {
|
|
|
92930
92946
|
note: "Preflight guards ran before post-rollback read-back verification."
|
|
92931
92947
|
};
|
|
92932
92948
|
});
|
|
92949
|
+
|
|
92950
|
+
// src/core/verify/constraint.ts
|
|
92951
|
+
function normalizeConstraintCheck(raw) {
|
|
92952
|
+
const check2 = raw ?? "";
|
|
92953
|
+
if (!ALLOWED_CONSTRAINT_CHECKS.includes(check2)) {
|
|
92954
|
+
throw new VerifyInputError(`Invalid --check '${check2}'. Allowed: ${ALLOWED_CONSTRAINT_CHECKS.join(", ")}`);
|
|
92955
|
+
}
|
|
92956
|
+
return check2;
|
|
92957
|
+
}
|
|
92958
|
+
function toColumns(raw) {
|
|
92959
|
+
if (raw === undefined || raw === null)
|
|
92960
|
+
return [];
|
|
92961
|
+
const arr = Array.isArray(raw) ? raw : [raw];
|
|
92962
|
+
return arr.map((c2) => requireNonEmpty(c2, "--column"));
|
|
92963
|
+
}
|
|
92964
|
+
function parseReferences(raw) {
|
|
92965
|
+
const ref = requireNonEmpty(raw, "--references");
|
|
92966
|
+
const parts = ref.split(".").map((p) => p.trim());
|
|
92967
|
+
if (parts.length < 2 || parts.some((p) => p.length === 0)) {
|
|
92968
|
+
throw new VerifyInputError(`--references must be '<table>.<column>' (got '${ref}')`);
|
|
92969
|
+
}
|
|
92970
|
+
const column = parts.pop();
|
|
92971
|
+
const table = parts.join(".");
|
|
92972
|
+
return { table, column };
|
|
92973
|
+
}
|
|
92974
|
+
function parseBaseline(raw) {
|
|
92975
|
+
if (raw === undefined || raw === null || raw === "")
|
|
92976
|
+
return 0;
|
|
92977
|
+
const n = Number(raw);
|
|
92978
|
+
if (!Number.isInteger(n) || n < 0) {
|
|
92979
|
+
throw new VerifyInputError(`--baseline must be a non-negative integer (got '${String(raw)}')`);
|
|
92980
|
+
}
|
|
92981
|
+
return n;
|
|
92982
|
+
}
|
|
92983
|
+
function normalizeConstraintInput(raw) {
|
|
92984
|
+
const check2 = normalizeConstraintCheck(raw.check);
|
|
92985
|
+
const table = requireNonEmpty(raw.table, "--table");
|
|
92986
|
+
const format = normalizeFormat(raw.format);
|
|
92987
|
+
const columns = toColumns(raw.column);
|
|
92988
|
+
const violationQueryRaw = raw.violationQuery;
|
|
92989
|
+
const hasViolationQuery = typeof violationQueryRaw === "string" && violationQueryRaw.trim().length > 0;
|
|
92990
|
+
let references;
|
|
92991
|
+
if (check2 === "fk") {
|
|
92992
|
+
if (columns.length !== 1) {
|
|
92993
|
+
throw new VerifyInputError("--check fk requires exactly one --column (the child FK column).");
|
|
92994
|
+
}
|
|
92995
|
+
references = parseReferences(raw.references);
|
|
92996
|
+
if (hasViolationQuery) {
|
|
92997
|
+
throw new VerifyInputError("--violation-query is only valid with --check custom.");
|
|
92998
|
+
}
|
|
92999
|
+
} else if (check2 === "not-null" || check2 === "unique") {
|
|
93000
|
+
if (columns.length < 1) {
|
|
93001
|
+
throw new VerifyInputError(`--check ${check2} requires at least one --column.`);
|
|
93002
|
+
}
|
|
93003
|
+
if (raw.references !== undefined) {
|
|
93004
|
+
throw new VerifyInputError("--references is only valid with --check fk.");
|
|
93005
|
+
}
|
|
93006
|
+
if (hasViolationQuery) {
|
|
93007
|
+
throw new VerifyInputError("--violation-query is only valid with --check custom.");
|
|
93008
|
+
}
|
|
93009
|
+
} else {
|
|
93010
|
+
if (!hasViolationQuery) {
|
|
93011
|
+
throw new VerifyInputError("--check custom requires --violation-query.");
|
|
93012
|
+
}
|
|
93013
|
+
if (columns.length > 0) {
|
|
93014
|
+
throw new VerifyInputError("--column is not valid with --check custom.");
|
|
93015
|
+
}
|
|
93016
|
+
if (raw.references !== undefined) {
|
|
93017
|
+
throw new VerifyInputError("--references is only valid with --check fk.");
|
|
93018
|
+
}
|
|
93019
|
+
}
|
|
93020
|
+
const subjectNameRaw = raw.subjectName;
|
|
93021
|
+
const summaryRaw = raw.summary;
|
|
93022
|
+
return {
|
|
93023
|
+
table,
|
|
93024
|
+
check: check2,
|
|
93025
|
+
columns,
|
|
93026
|
+
...references ? { references } : {},
|
|
93027
|
+
...check2 === "custom" ? { violationQuery: violationQueryRaw.trim() } : {},
|
|
93028
|
+
allowPreexisting: raw.allowPreexisting === true,
|
|
93029
|
+
baseline: parseBaseline(raw.baseline),
|
|
93030
|
+
afterWrite: raw.afterWrite === true,
|
|
93031
|
+
format,
|
|
93032
|
+
...subjectNameRaw && subjectNameRaw.trim().length > 0 ? { subjectName: subjectNameRaw.trim() } : {},
|
|
93033
|
+
...summaryRaw && summaryRaw.trim().length > 0 ? { summary: summaryRaw.trim() } : {}
|
|
93034
|
+
};
|
|
93035
|
+
}
|
|
93036
|
+
function buildConstraintSubject(input) {
|
|
93037
|
+
return {
|
|
93038
|
+
kind: "table",
|
|
93039
|
+
name: input.subjectName ?? input.table,
|
|
93040
|
+
command: "verify constraint"
|
|
93041
|
+
};
|
|
93042
|
+
}
|
|
93043
|
+
function buildConstraintAfterWriteCommand(input, baseline) {
|
|
93044
|
+
const flags = [`--check ${input.check}`, `--table ${shellQuote2(input.table)}`];
|
|
93045
|
+
if (input.check === "fk") {
|
|
93046
|
+
flags.push(`--column ${shellQuote2(input.columns[0])}`);
|
|
93047
|
+
flags.push(`--references ${shellQuote2(`${input.references.table}.${input.references.column}`)}`);
|
|
93048
|
+
} else if (input.check === "custom") {
|
|
93049
|
+
flags.push(`--violation-query ${shellQuote2(input.violationQuery)}`);
|
|
93050
|
+
} else {
|
|
93051
|
+
for (const c2 of input.columns)
|
|
93052
|
+
flags.push(`--column ${shellQuote2(c2)}`);
|
|
93053
|
+
}
|
|
93054
|
+
if (input.subjectName)
|
|
93055
|
+
flags.push(`--subject-name ${shellQuote2(input.subjectName)}`);
|
|
93056
|
+
if (input.summary)
|
|
93057
|
+
flags.push(`--summary ${shellQuote2(input.summary)}`);
|
|
93058
|
+
if (input.format !== "table")
|
|
93059
|
+
flags.push(`--format ${input.format}`);
|
|
93060
|
+
if (baseline !== undefined && baseline > 0) {
|
|
93061
|
+
flags.push("--allow-preexisting", `--baseline ${baseline}`);
|
|
93062
|
+
}
|
|
93063
|
+
return renderAfterWriteCommand("constraint", flags);
|
|
93064
|
+
}
|
|
93065
|
+
async function runConstraintGuards(runners) {
|
|
93066
|
+
return runGuardSequence([
|
|
93067
|
+
["blacklist", () => runners.blacklistGuard()],
|
|
93068
|
+
["schema", () => runners.schemaGuard()],
|
|
93069
|
+
["violation-query-readonly", () => runners.violationReadonlyGuard()]
|
|
93070
|
+
]);
|
|
93071
|
+
}
|
|
93072
|
+
function constraintDefaultSummary(status2, check2, table) {
|
|
93073
|
+
switch (status2) {
|
|
93074
|
+
case "verified":
|
|
93075
|
+
return `Constraint '${check2}' holds on ${table} (violation count within threshold).`;
|
|
93076
|
+
case "not_verified":
|
|
93077
|
+
return `Constraint '${check2}' is violated on ${table} (violation count exceeds threshold).`;
|
|
93078
|
+
case "blocked":
|
|
93079
|
+
return `Constraint '${check2}' verification was blocked before the violation count on ${table}.`;
|
|
93080
|
+
default:
|
|
93081
|
+
return `Constraint '${check2}' verification could not produce a trustworthy verdict on ${table}.`;
|
|
93082
|
+
}
|
|
93083
|
+
}
|
|
93084
|
+
async function runConstraintPreflight(input, runners) {
|
|
93085
|
+
const guards = await runConstraintGuards(runners);
|
|
93086
|
+
const ready = allGuardsPassed(guards, 3);
|
|
93087
|
+
let baseline;
|
|
93088
|
+
if (ready) {
|
|
93089
|
+
const outcome = await runners.runViolationCount();
|
|
93090
|
+
if (outcome.ran && typeof outcome.count === "number")
|
|
93091
|
+
baseline = outcome.count;
|
|
93092
|
+
}
|
|
93093
|
+
return {
|
|
93094
|
+
scenario: "constraint",
|
|
93095
|
+
mode: "preflight",
|
|
93096
|
+
status: ready ? "ready" : "blocked",
|
|
93097
|
+
check: input.check,
|
|
93098
|
+
table: input.table,
|
|
93099
|
+
violationSql: runners.violationSql,
|
|
93100
|
+
...baseline !== undefined ? { baseline } : {},
|
|
93101
|
+
guards,
|
|
93102
|
+
afterWriteCommand: buildConstraintAfterWriteCommand(input, baseline)
|
|
93103
|
+
};
|
|
93104
|
+
}
|
|
93105
|
+
async function runConstraintAfterWrite(input, runners, clock = {}) {
|
|
93106
|
+
const subject = buildConstraintSubject(input);
|
|
93107
|
+
const guards = await runConstraintGuards(runners);
|
|
93108
|
+
if (!allGuardsPassed(guards, 3)) {
|
|
93109
|
+
const failed = guards.find((g) => g.status === "failed");
|
|
93110
|
+
const blockedReason = failed?.reason ?? "A required guard failed before the violation count.";
|
|
93111
|
+
const artifact3 = buildVerificationArtifact({
|
|
93112
|
+
status: "blocked",
|
|
93113
|
+
subject,
|
|
93114
|
+
summary: input.summary ?? constraintDefaultSummary("blocked", input.check, input.table),
|
|
93115
|
+
evidence: [CONSTRAINT_TASK_PACK_EVIDENCE],
|
|
93116
|
+
blockedReason,
|
|
93117
|
+
now: clock.now,
|
|
93118
|
+
idFactory: clock.idFactory
|
|
93119
|
+
});
|
|
93120
|
+
return {
|
|
93121
|
+
scenario: "constraint",
|
|
93122
|
+
mode: "after-write",
|
|
93123
|
+
status: "blocked",
|
|
93124
|
+
check: input.check,
|
|
93125
|
+
table: input.table,
|
|
93126
|
+
guards,
|
|
93127
|
+
artifact: artifact3,
|
|
93128
|
+
blockedReason
|
|
93129
|
+
};
|
|
93130
|
+
}
|
|
93131
|
+
const outcome = await runners.runViolationCount();
|
|
93132
|
+
const threshold = input.allowPreexisting ? input.baseline : 0;
|
|
93133
|
+
const assertionOutcome = outcome.ran && typeof outcome.count === "number" ? { ran: true, pass: outcome.count <= threshold, auditRef: outcome.auditRef } : { ran: false, reason: outcome.reason, auditRef: outcome.auditRef };
|
|
93134
|
+
const status2 = mapAssertionToStatus(assertionOutcome);
|
|
93135
|
+
const evidence2 = {
|
|
93136
|
+
kind: "assert",
|
|
93137
|
+
command: `constraint:${input.check} <${redactSqlForEvidence(runners.violationSql)}> threshold <=${threshold}`,
|
|
93138
|
+
exitCode: status2 === "verified" ? 0 : 1,
|
|
93139
|
+
...outcome.auditRef ? { auditRef: outcome.auditRef } : {},
|
|
93140
|
+
...status2 === "indeterminate" && outcome.reason ? { note: outcome.reason } : {}
|
|
93141
|
+
};
|
|
93142
|
+
const artifact2 = buildVerificationArtifact({
|
|
93143
|
+
status: status2,
|
|
93144
|
+
subject,
|
|
93145
|
+
summary: input.summary ?? constraintDefaultSummary(status2, input.check, input.table),
|
|
93146
|
+
evidence: [CONSTRAINT_TASK_PACK_EVIDENCE, evidence2],
|
|
93147
|
+
...status2 === "indeterminate" && outcome.reason ? { blockedReason: outcome.reason } : {},
|
|
93148
|
+
now: clock.now,
|
|
93149
|
+
idFactory: clock.idFactory
|
|
93150
|
+
});
|
|
93151
|
+
return {
|
|
93152
|
+
scenario: "constraint",
|
|
93153
|
+
mode: "after-write",
|
|
93154
|
+
status: status2,
|
|
93155
|
+
check: input.check,
|
|
93156
|
+
table: input.table,
|
|
93157
|
+
guards,
|
|
93158
|
+
...outcome.ran && typeof outcome.count === "number" ? { assertion: { violations: outcome.count, threshold, passed: status2 === "verified" } } : {},
|
|
93159
|
+
artifact: artifact2
|
|
93160
|
+
};
|
|
93161
|
+
}
|
|
93162
|
+
var ALLOWED_CONSTRAINT_CHECKS, CONSTRAINT_TASK_PACK_EVIDENCE;
|
|
93163
|
+
var init_constraint = __esm(() => {
|
|
93164
|
+
init_scenario();
|
|
93165
|
+
init_verification();
|
|
93166
|
+
init_scenario();
|
|
93167
|
+
ALLOWED_CONSTRAINT_CHECKS = ["fk", "not-null", "unique", "custom"];
|
|
93168
|
+
CONSTRAINT_TASK_PACK_EVIDENCE = {
|
|
93169
|
+
kind: "task-pack-plan",
|
|
93170
|
+
taskName: "constraint-verify",
|
|
93171
|
+
note: "Preflight guards ran before the read-only violation count."
|
|
93172
|
+
};
|
|
93173
|
+
});
|
|
93174
|
+
|
|
93175
|
+
// src/core/verify/constraint-query.ts
|
|
93176
|
+
function quoteIdent(name2, engine) {
|
|
93177
|
+
const useBacktick = engine === "mysql" || engine === "mariadb";
|
|
93178
|
+
const q = useBacktick ? "`" : '"';
|
|
93179
|
+
const esc = (seg) => `${q}${seg.split(q).join(q + q)}${q}`;
|
|
93180
|
+
return name2.split(".").map((seg) => esc(seg.trim())).join(".");
|
|
93181
|
+
}
|
|
93182
|
+
function buildNotNullViolationQuery(a) {
|
|
93183
|
+
const where = a.columns.map((c2) => `${quoteIdent(c2, a.engine)} IS NULL`).join(" OR ");
|
|
93184
|
+
return `SELECT COUNT(*) AS violation_count FROM ${quoteIdent(a.table, a.engine)} WHERE ${where}`;
|
|
93185
|
+
}
|
|
93186
|
+
function buildUniqueViolationQuery(a) {
|
|
93187
|
+
const cols = a.columns.map((c2) => quoteIdent(c2, a.engine)).join(", ");
|
|
93188
|
+
return `SELECT COUNT(*) AS violation_count FROM (SELECT 1 FROM ${quoteIdent(a.table, a.engine)} GROUP BY ${cols} HAVING COUNT(*) > 1) AS dups`;
|
|
93189
|
+
}
|
|
93190
|
+
function buildFkViolationQuery(a) {
|
|
93191
|
+
const col = quoteIdent(a.column, a.engine);
|
|
93192
|
+
const ref = quoteIdent(a.refColumn, a.engine);
|
|
93193
|
+
return `SELECT COUNT(*) AS violation_count FROM ${quoteIdent(a.table, a.engine)} AS c LEFT JOIN ${quoteIdent(a.refTable, a.engine)} AS p ON c.${col} = p.${ref} WHERE c.${col} IS NOT NULL AND p.${ref} IS NULL`;
|
|
93194
|
+
}
|
|
93195
|
+
function buildViolationQuery(input, engine) {
|
|
93196
|
+
switch (input.check) {
|
|
93197
|
+
case "custom":
|
|
93198
|
+
return input.violationQuery;
|
|
93199
|
+
case "not-null":
|
|
93200
|
+
return buildNotNullViolationQuery({ engine, table: input.table, columns: input.columns });
|
|
93201
|
+
case "unique":
|
|
93202
|
+
return buildUniqueViolationQuery({ engine, table: input.table, columns: input.columns });
|
|
93203
|
+
case "fk":
|
|
93204
|
+
return buildFkViolationQuery({
|
|
93205
|
+
engine,
|
|
93206
|
+
table: input.table,
|
|
93207
|
+
column: input.columns[0],
|
|
93208
|
+
refTable: input.references.table,
|
|
93209
|
+
refColumn: input.references.column
|
|
93210
|
+
});
|
|
93211
|
+
}
|
|
93212
|
+
}
|
|
92933
93213
|
// src/core/verify/index.ts
|
|
92934
93214
|
var init_verify = __esm(() => {
|
|
92935
93215
|
init_scenario();
|
|
92936
93216
|
init_safe_backfill();
|
|
92937
93217
|
init_migration();
|
|
92938
93218
|
init_rollback();
|
|
93219
|
+
init_constraint();
|
|
92939
93220
|
});
|
|
92940
93221
|
|
|
92941
93222
|
// src/commands/verify.ts
|
|
@@ -93197,6 +93478,96 @@ function buildRollbackRunners(ctx, input) {
|
|
|
93197
93478
|
}
|
|
93198
93479
|
};
|
|
93199
93480
|
}
|
|
93481
|
+
function constraintEngineOf(system) {
|
|
93482
|
+
return system;
|
|
93483
|
+
}
|
|
93484
|
+
function buildConstraintRunners(ctx, input) {
|
|
93485
|
+
const { adapter, config } = ctx;
|
|
93486
|
+
const blacklist = config.blacklist ?? { tables: [], columns: {} };
|
|
93487
|
+
const schema = config.schema ?? {};
|
|
93488
|
+
const schemaLookup = { tables: schema, cacheAvailable: Object.keys(schema).length > 0 };
|
|
93489
|
+
const analyze = (sql) => analyzeQueryRisk({ sql: sql.trim(), permission: config.permission, blacklist, schemaLookup });
|
|
93490
|
+
const engine = constraintEngineOf(config.connection.system);
|
|
93491
|
+
const violationSql = buildViolationQuery(input, engine);
|
|
93492
|
+
const columnsExist = async (table, cols) => {
|
|
93493
|
+
const ts = await adapter.getTableSchema(table);
|
|
93494
|
+
const present = new Set(ts.columns.map((c2) => c2.name.toLowerCase()));
|
|
93495
|
+
const missing = cols.filter((c2) => !present.has(c2.trim().toLowerCase()));
|
|
93496
|
+
return missing.length > 0 ? `unknown column(s) on ${table}: ${missing.join(", ")}` : null;
|
|
93497
|
+
};
|
|
93498
|
+
return {
|
|
93499
|
+
violationSql,
|
|
93500
|
+
blacklistGuard: async () => {
|
|
93501
|
+
const bm = new BlacklistManager(config);
|
|
93502
|
+
const targets = [input.table, ...input.references ? [input.references.table] : []];
|
|
93503
|
+
for (const t2 of targets) {
|
|
93504
|
+
if (bm.isTableBlacklisted(t2) && !bm.canOverrideBlacklist()) {
|
|
93505
|
+
return { ok: false, reason: boundedReason(`Target table '${t2}' is blacklisted.`) };
|
|
93506
|
+
}
|
|
93507
|
+
}
|
|
93508
|
+
return { ok: true };
|
|
93509
|
+
},
|
|
93510
|
+
schemaGuard: async () => {
|
|
93511
|
+
try {
|
|
93512
|
+
if (input.check !== "custom") {
|
|
93513
|
+
const miss = await columnsExist(input.table, input.columns);
|
|
93514
|
+
if (miss)
|
|
93515
|
+
return { ok: false, reason: boundedReason(miss) };
|
|
93516
|
+
} else {
|
|
93517
|
+
await adapter.getTableSchema(input.table);
|
|
93518
|
+
}
|
|
93519
|
+
if (input.references) {
|
|
93520
|
+
const refMiss = await columnsExist(input.references.table, [input.references.column]);
|
|
93521
|
+
if (refMiss)
|
|
93522
|
+
return { ok: false, reason: boundedReason(refMiss) };
|
|
93523
|
+
}
|
|
93524
|
+
return { ok: true };
|
|
93525
|
+
} catch (e) {
|
|
93526
|
+
return { ok: false, reason: boundedReason(`schema check failed: ${e.message}`) };
|
|
93527
|
+
}
|
|
93528
|
+
},
|
|
93529
|
+
violationReadonlyGuard: async () => {
|
|
93530
|
+
const r = analyze(violationSql);
|
|
93531
|
+
if (!isPlainSelectVerifyQuery(r.operation, violationSql)) {
|
|
93532
|
+
return {
|
|
93533
|
+
ok: false,
|
|
93534
|
+
reason: boundedReason(`violation query must be a read-only plain SELECT (got ${r.operation}).`)
|
|
93535
|
+
};
|
|
93536
|
+
}
|
|
93537
|
+
return { ok: true };
|
|
93538
|
+
},
|
|
93539
|
+
runViolationCount: async () => {
|
|
93540
|
+
try {
|
|
93541
|
+
const blacklistValidator = new BlacklistValidator(new BlacklistManager(config));
|
|
93542
|
+
const executor3 = new QueryExecutor(adapter, config.permission, blacklistValidator, config, ctx.options);
|
|
93543
|
+
const result = await executor3.execute(violationSql, { autoLimit: true });
|
|
93544
|
+
const scalar = firstScalar(result);
|
|
93545
|
+
const auditRef = await writeAuditEntry(config, "verify", ctx.options, {
|
|
93546
|
+
success: true,
|
|
93547
|
+
sql: violationSql
|
|
93548
|
+
});
|
|
93549
|
+
if (scalar === null) {
|
|
93550
|
+
return {
|
|
93551
|
+
ran: false,
|
|
93552
|
+
reason: boundedReason("violation query returned no count"),
|
|
93553
|
+
auditRef
|
|
93554
|
+
};
|
|
93555
|
+
}
|
|
93556
|
+
const count = typeof scalar === "number" ? scalar : Number(scalar);
|
|
93557
|
+
if (!Number.isFinite(count)) {
|
|
93558
|
+
return {
|
|
93559
|
+
ran: false,
|
|
93560
|
+
reason: boundedReason(`violation count not numeric: ${scalar}`),
|
|
93561
|
+
auditRef
|
|
93562
|
+
};
|
|
93563
|
+
}
|
|
93564
|
+
return { ran: true, count, auditRef };
|
|
93565
|
+
} catch (e) {
|
|
93566
|
+
return { ran: false, reason: boundedReason(e.message) };
|
|
93567
|
+
}
|
|
93568
|
+
}
|
|
93569
|
+
};
|
|
93570
|
+
}
|
|
93200
93571
|
function formatPreflightTable(r) {
|
|
93201
93572
|
const lines = [
|
|
93202
93573
|
`Scenario: ${r.scenario}`,
|
|
@@ -93405,6 +93776,81 @@ function buildRollbackAfterWriteJson(r, artifactPath) {
|
|
|
93405
93776
|
...r.blockedReason ? { blockedReason: r.blockedReason } : {}
|
|
93406
93777
|
};
|
|
93407
93778
|
}
|
|
93779
|
+
function formatConstraintPreflightTable(r) {
|
|
93780
|
+
const lines = [
|
|
93781
|
+
`Scenario: ${r.scenario}`,
|
|
93782
|
+
`Mode: preflight`,
|
|
93783
|
+
`Check: ${r.check}`,
|
|
93784
|
+
`Table: ${r.table}`,
|
|
93785
|
+
`Status: ${r.status}`,
|
|
93786
|
+
"Guards:"
|
|
93787
|
+
];
|
|
93788
|
+
for (const g of r.guards) {
|
|
93789
|
+
lines.push(` - ${g.name}: ${g.status}${g.reason ? ` (${g.reason})` : ""}`);
|
|
93790
|
+
}
|
|
93791
|
+
if (r.baseline !== undefined)
|
|
93792
|
+
lines.push("", `Baseline violations: ${r.baseline}`);
|
|
93793
|
+
lines.push("", "Violation query (read-only; this command never executes a write):");
|
|
93794
|
+
lines.push(` ${r.violationSql}`);
|
|
93795
|
+
lines.push("", "After-write command (run AFTER you apply your change externally):");
|
|
93796
|
+
lines.push(` ${r.afterWriteCommand}`);
|
|
93797
|
+
lines.push("", "Note: default verdict requires 0 violations; add --allow-preexisting --baseline <N> to tolerate pre-existing ones.");
|
|
93798
|
+
return lines.join(`
|
|
93799
|
+
`);
|
|
93800
|
+
}
|
|
93801
|
+
function formatConstraintAfterWriteTable(r, artifactPath) {
|
|
93802
|
+
const lines = [
|
|
93803
|
+
`Scenario: ${r.scenario}`,
|
|
93804
|
+
`Mode: after-write`,
|
|
93805
|
+
`Check: ${r.check}`,
|
|
93806
|
+
`Table: ${r.table}`,
|
|
93807
|
+
`Status: ${r.status}`
|
|
93808
|
+
];
|
|
93809
|
+
if (r.assertion)
|
|
93810
|
+
lines.push(`Violations: ${r.assertion.violations} (threshold <= ${r.assertion.threshold}) -> ${r.assertion.passed ? "PASS" : "FAIL"}`);
|
|
93811
|
+
if (r.blockedReason)
|
|
93812
|
+
lines.push(`Reason: ${r.blockedReason}`);
|
|
93813
|
+
lines.push(`Summary: ${r.artifact.summary}`);
|
|
93814
|
+
lines.push(`Artifact id: ${r.artifact.id}`);
|
|
93815
|
+
if (artifactPath)
|
|
93816
|
+
lines.push(`Artifact: ${artifactPath}`);
|
|
93817
|
+
lines.push("", `Next: dbcli verification show ${r.artifact.id}`);
|
|
93818
|
+
return lines.join(`
|
|
93819
|
+
`);
|
|
93820
|
+
}
|
|
93821
|
+
function buildConstraintPreflightJson(r) {
|
|
93822
|
+
return {
|
|
93823
|
+
scenario: r.scenario,
|
|
93824
|
+
mode: r.mode,
|
|
93825
|
+
status: r.status,
|
|
93826
|
+
check: r.check,
|
|
93827
|
+
table: r.table,
|
|
93828
|
+
violationSql: r.violationSql,
|
|
93829
|
+
...r.baseline !== undefined ? { baseline: r.baseline } : {},
|
|
93830
|
+
guards: r.guards.map((g) => ({
|
|
93831
|
+
name: g.name,
|
|
93832
|
+
status: g.status,
|
|
93833
|
+
...g.reason ? { reason: g.reason } : {}
|
|
93834
|
+
})),
|
|
93835
|
+
afterWriteCommand: r.afterWriteCommand
|
|
93836
|
+
};
|
|
93837
|
+
}
|
|
93838
|
+
function buildConstraintAfterWriteJson(r, artifactPath) {
|
|
93839
|
+
return {
|
|
93840
|
+
scenario: r.scenario,
|
|
93841
|
+
mode: r.mode,
|
|
93842
|
+
status: r.status,
|
|
93843
|
+
check: r.check,
|
|
93844
|
+
table: r.table,
|
|
93845
|
+
artifact: {
|
|
93846
|
+
id: r.artifact.id,
|
|
93847
|
+
...artifactPath ? { path: artifactPath } : {},
|
|
93848
|
+
subject: r.artifact.subject
|
|
93849
|
+
},
|
|
93850
|
+
...r.assertion ? { assertion: r.assertion } : {},
|
|
93851
|
+
...r.blockedReason ? { blockedReason: r.blockedReason } : {}
|
|
93852
|
+
};
|
|
93853
|
+
}
|
|
93408
93854
|
async function executeScenario(def, options, command) {
|
|
93409
93855
|
let input;
|
|
93410
93856
|
try {
|
|
@@ -93471,7 +93917,7 @@ function registerScenario(parent, def) {
|
|
|
93471
93917
|
def.configureOptions(command);
|
|
93472
93918
|
command.action((options, cmd) => executeScenario(def, options, cmd));
|
|
93473
93919
|
}
|
|
93474
|
-
var SQL_SYSTEMS7, safeBackfillScenario, migrationScenario, rollbackScenario, BUILTIN_VERIFY_SCENARIOS, verifyCommand;
|
|
93920
|
+
var SQL_SYSTEMS7, safeBackfillScenario, migrationScenario, rollbackScenario, constraintScenario, BUILTIN_VERIFY_SCENARIOS, verifyCommand;
|
|
93475
93921
|
var init_verify2 = __esm(() => {
|
|
93476
93922
|
init_esm();
|
|
93477
93923
|
init_adapters();
|
|
@@ -93627,10 +94073,61 @@ var init_verify2 = __esm(() => {
|
|
|
93627
94073
|
return result.status === "verified" && !artifactError;
|
|
93628
94074
|
}
|
|
93629
94075
|
};
|
|
94076
|
+
constraintScenario = {
|
|
94077
|
+
name: "constraint",
|
|
94078
|
+
description: "Preflight or after-write verification that a data-integrity invariant holds across your change (--check fk|not-null|unique|custom); never executes a write",
|
|
94079
|
+
subjectKind: "table",
|
|
94080
|
+
configureOptions(command) {
|
|
94081
|
+
return command.requiredOption("--table <table>", "Table the invariant is checked on").requiredOption("--check <kind>", "Constraint kind: fk | not-null | unique | custom").option("--column <name>", "Column to check (repeatable for not-null/unique; the child FK column for fk)", (val, prev = []) => [...prev, val]).option("--references <table.column>", "Referenced <table>.<column> (required for --check fk)").option("--violation-query <sql>", "Read-only SELECT counting violations (required for --check custom)").option("--allow-preexisting", "Tolerate pre-existing violations: verified when count <= --baseline", false).option("--baseline <n>", "Baseline violation count measured at preflight (use with --allow-preexisting)").option("--after-write", "Re-run the violation count and write a verification artifact", false).option("--format <format>", "Output format: table (default) or json", "table").option("--subject-name <name>", "Optional artifact subject name (default: table)").option("--summary <text>", "Optional artifact summary override (after-write mode)");
|
|
94082
|
+
},
|
|
94083
|
+
normalize(options) {
|
|
94084
|
+
return normalizeConstraintInput({
|
|
94085
|
+
table: options.table,
|
|
94086
|
+
check: options.check,
|
|
94087
|
+
column: options.column,
|
|
94088
|
+
references: options.references,
|
|
94089
|
+
violationQuery: options.violationQuery,
|
|
94090
|
+
allowPreexisting: options.allowPreexisting === true,
|
|
94091
|
+
baseline: options.baseline,
|
|
94092
|
+
afterWrite: options.afterWrite === true,
|
|
94093
|
+
format: options.format,
|
|
94094
|
+
subjectName: options.subjectName,
|
|
94095
|
+
summary: options.summary
|
|
94096
|
+
});
|
|
94097
|
+
},
|
|
94098
|
+
createRunners(context, input) {
|
|
94099
|
+
return buildConstraintRunners(context, input);
|
|
94100
|
+
},
|
|
94101
|
+
runPreflight(input, runners) {
|
|
94102
|
+
return runConstraintPreflight(input, runners);
|
|
94103
|
+
},
|
|
94104
|
+
runAfterWrite(input, runners) {
|
|
94105
|
+
return runConstraintAfterWrite(input, runners);
|
|
94106
|
+
},
|
|
94107
|
+
renderPreflight(result, format) {
|
|
94108
|
+
return format === "json" ? JSON.stringify(buildConstraintPreflightJson(result), null, 2) : formatConstraintPreflightTable(result);
|
|
94109
|
+
},
|
|
94110
|
+
artifactOf(result) {
|
|
94111
|
+
return result.artifact;
|
|
94112
|
+
},
|
|
94113
|
+
afterWriteJson(result, artifactPath) {
|
|
94114
|
+
return buildConstraintAfterWriteJson(result, artifactPath);
|
|
94115
|
+
},
|
|
94116
|
+
renderAfterWriteTable(result, artifactPath) {
|
|
94117
|
+
return formatConstraintAfterWriteTable(result, artifactPath);
|
|
94118
|
+
},
|
|
94119
|
+
isPreflightReady(result) {
|
|
94120
|
+
return result.status === "ready";
|
|
94121
|
+
},
|
|
94122
|
+
isAfterWriteVerified(result, artifactError) {
|
|
94123
|
+
return result.status === "verified" && !artifactError;
|
|
94124
|
+
}
|
|
94125
|
+
};
|
|
93630
94126
|
BUILTIN_VERIFY_SCENARIOS = [
|
|
93631
94127
|
safeBackfillScenario,
|
|
93632
94128
|
migrationScenario,
|
|
93633
|
-
rollbackScenario
|
|
94129
|
+
rollbackScenario,
|
|
94130
|
+
constraintScenario
|
|
93634
94131
|
];
|
|
93635
94132
|
verifyCommand = new Command("verify").description("Run verification scenarios (preflight or after-write). Never executes writes.");
|
|
93636
94133
|
for (const scenario2 of BUILTIN_VERIFY_SCENARIOS) {
|
|
@@ -95813,32 +96310,6 @@ var init_meta_commands = __esm(() => {
|
|
|
95813
96310
|
VALID_FORMATS = ["table", "json", "csv"];
|
|
95814
96311
|
});
|
|
95815
96312
|
|
|
95816
|
-
// src/core/repl/command-registry.ts
|
|
95817
|
-
var exports_command_registry = {};
|
|
95818
|
-
__export(exports_command_registry, {
|
|
95819
|
-
setReplCommandNames: () => setReplCommandNames,
|
|
95820
|
-
isReplCommandKnown: () => isReplCommandKnown,
|
|
95821
|
-
getReplCommandNames: () => getReplCommandNames,
|
|
95822
|
-
REPL_DENYLIST: () => REPL_DENYLIST
|
|
95823
|
-
});
|
|
95824
|
-
function setReplCommandNames(input) {
|
|
95825
|
-
const allowed = input.filter((n) => !REPL_DENYLIST.has(n));
|
|
95826
|
-
names = allowed;
|
|
95827
|
-
known = new Set(allowed);
|
|
95828
|
-
}
|
|
95829
|
-
function getReplCommandNames() {
|
|
95830
|
-
return names;
|
|
95831
|
-
}
|
|
95832
|
-
function isReplCommandKnown(name2) {
|
|
95833
|
-
return known.has(name2);
|
|
95834
|
-
}
|
|
95835
|
-
var REPL_DENYLIST, names, known;
|
|
95836
|
-
var init_command_registry = __esm(() => {
|
|
95837
|
-
REPL_DENYLIST = new Set(["shell"]);
|
|
95838
|
-
names = [];
|
|
95839
|
-
known = new Set;
|
|
95840
|
-
});
|
|
95841
|
-
|
|
95842
96313
|
// src/core/repl/command-dispatcher.ts
|
|
95843
96314
|
function parseCommandLine(input) {
|
|
95844
96315
|
const trimmed = input.trim();
|
|
@@ -95874,12 +96345,9 @@ function splitRespectingQuotes(input) {
|
|
|
95874
96345
|
}
|
|
95875
96346
|
return tokens;
|
|
95876
96347
|
}
|
|
95877
|
-
function isKnownCommand(name2) {
|
|
95878
|
-
return
|
|
96348
|
+
function isKnownCommand(name2, commandNames) {
|
|
96349
|
+
return commandNames.includes(name2);
|
|
95879
96350
|
}
|
|
95880
|
-
var init_command_dispatcher = __esm(() => {
|
|
95881
|
-
init_command_registry();
|
|
95882
|
-
});
|
|
95883
96351
|
|
|
95884
96352
|
// src/core/repl/history-manager.ts
|
|
95885
96353
|
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
@@ -96006,7 +96474,7 @@ class ReplEngine {
|
|
|
96006
96474
|
}
|
|
96007
96475
|
async handleCommand(input) {
|
|
96008
96476
|
const parsed = parseCommandLine(input);
|
|
96009
|
-
if (!isKnownCommand(parsed.command)) {
|
|
96477
|
+
if (!isKnownCommand(parsed.command, this.context.commandNames)) {
|
|
96010
96478
|
if (this.context.system === "redis") {
|
|
96011
96479
|
this.history.add(input);
|
|
96012
96480
|
return this.executeSql(input);
|
|
@@ -96149,7 +96617,6 @@ var import_picocolors3;
|
|
|
96149
96617
|
var init_repl_engine = __esm(() => {
|
|
96150
96618
|
init_input_classifier();
|
|
96151
96619
|
init_meta_commands();
|
|
96152
|
-
init_command_dispatcher();
|
|
96153
96620
|
init_history_manager();
|
|
96154
96621
|
init_permission_guard();
|
|
96155
96622
|
init_query_result_formatter();
|
|
@@ -96197,7 +96664,7 @@ function createCompleter(ctx) {
|
|
|
96197
96664
|
}
|
|
96198
96665
|
const words = trimmed.split(/\s+/);
|
|
96199
96666
|
if (words.length === 1) {
|
|
96200
|
-
const cmdHits = matchWithSuffix([...
|
|
96667
|
+
const cmdHits = matchWithSuffix([...ctx.commandNames], lastWord);
|
|
96201
96668
|
const sqlHits = matchWithSuffix([...SQL_KEYWORDS_FOR_COMPLETION], lastWord);
|
|
96202
96669
|
return [[...cmdHits[0], ...sqlHits[0]], lastWord];
|
|
96203
96670
|
}
|
|
@@ -96250,7 +96717,6 @@ function matchWithSuffix(candidates, prefix) {
|
|
|
96250
96717
|
var REDIS_COMMANDS, TABLE_POSITION_KEYWORDS, COMMANDS_TAKING_TABLE_ARG;
|
|
96251
96718
|
var init_completer = __esm(() => {
|
|
96252
96719
|
init_types10();
|
|
96253
|
-
init_command_registry();
|
|
96254
96720
|
init_command_metadata();
|
|
96255
96721
|
REDIS_COMMANDS = Object.keys(REDIS_COMMAND_TABLE);
|
|
96256
96722
|
TABLE_POSITION_KEYWORDS = new Set([
|
|
@@ -96439,6 +96905,20 @@ var init_es_shell = __esm(() => {
|
|
|
96439
96905
|
import_picocolors4 = __toESM(require_picocolors(), 1);
|
|
96440
96906
|
});
|
|
96441
96907
|
|
|
96908
|
+
// src/core/repl/command-registry.ts
|
|
96909
|
+
var exports_command_registry = {};
|
|
96910
|
+
__export(exports_command_registry, {
|
|
96911
|
+
deriveReplCommandNames: () => deriveReplCommandNames,
|
|
96912
|
+
REPL_DENYLIST: () => REPL_DENYLIST
|
|
96913
|
+
});
|
|
96914
|
+
function deriveReplCommandNames(topLevel) {
|
|
96915
|
+
return topLevel.filter((n) => !REPL_DENYLIST.has(n));
|
|
96916
|
+
}
|
|
96917
|
+
var REPL_DENYLIST;
|
|
96918
|
+
var init_command_registry = __esm(() => {
|
|
96919
|
+
REPL_DENYLIST = new Set(["shell"]);
|
|
96920
|
+
});
|
|
96921
|
+
|
|
96442
96922
|
// src/commands/shell.ts
|
|
96443
96923
|
import { createInterface as createInterface3 } from "readline";
|
|
96444
96924
|
import { join as join32 } from "path";
|
|
@@ -96463,6 +96943,14 @@ async function populateMongoColumns(mongoAdapter, collectionNames, threshold = M
|
|
|
96463
96943
|
}
|
|
96464
96944
|
return columnsByTable;
|
|
96465
96945
|
}
|
|
96946
|
+
async function populateRedisKeyCompletion(adapter, limit = REDIS_COMPLETION_KEY_LIMIT) {
|
|
96947
|
+
try {
|
|
96948
|
+
const { names, truncated } = await adapter.sampleKeyNames(limit);
|
|
96949
|
+
return { tableNames: names, truncated };
|
|
96950
|
+
} catch {
|
|
96951
|
+
return { tableNames: [], truncated: false };
|
|
96952
|
+
}
|
|
96953
|
+
}
|
|
96466
96954
|
async function runShell(options, configPath) {
|
|
96467
96955
|
let config;
|
|
96468
96956
|
try {
|
|
@@ -96490,9 +96978,12 @@ async function runShell(options, configPath) {
|
|
|
96490
96978
|
let tableNames = [];
|
|
96491
96979
|
let columnsByTable = {};
|
|
96492
96980
|
if (isRedis) {
|
|
96493
|
-
const
|
|
96494
|
-
tableNames =
|
|
96981
|
+
const completion = await populateRedisKeyCompletion(redisInner);
|
|
96982
|
+
tableNames = completion.tableNames;
|
|
96495
96983
|
columnsByTable = {};
|
|
96984
|
+
if (completion.truncated) {
|
|
96985
|
+
console.error(import_picocolors5.default.dim(`Redis shell: large keyspace; tab completion limited to the first ${REDIS_COMPLETION_KEY_LIMIT} keys.`));
|
|
96986
|
+
}
|
|
96496
96987
|
} else if (isMongoDB) {
|
|
96497
96988
|
const collections = await adapter.listTables();
|
|
96498
96989
|
tableNames = collections.map((collection) => collection.name);
|
|
@@ -96512,17 +97003,18 @@ async function runShell(options, configPath) {
|
|
|
96512
97003
|
}
|
|
96513
97004
|
}
|
|
96514
97005
|
}
|
|
97006
|
+
const { buildProgram } = await Promise.resolve().then(() => (init_program(), exports_program));
|
|
97007
|
+
const { buildCompletionTree: buildCompletionTree2, listTopLevelCommandNames: listTopLevelCommandNames2 } = await Promise.resolve().then(() => exports_command_tree);
|
|
97008
|
+
const { deriveReplCommandNames: deriveReplCommandNames2 } = await Promise.resolve().then(() => (init_command_registry(), exports_command_registry));
|
|
97009
|
+
const commandNames = deriveReplCommandNames2(listTopLevelCommandNames2(buildCompletionTree2(buildProgram())));
|
|
96515
97010
|
const context = {
|
|
96516
97011
|
configPath,
|
|
96517
97012
|
permission: config.permission,
|
|
96518
97013
|
system: config.connection.system,
|
|
96519
97014
|
tableNames,
|
|
96520
|
-
columnsByTable
|
|
97015
|
+
columnsByTable,
|
|
97016
|
+
commandNames
|
|
96521
97017
|
};
|
|
96522
|
-
const { buildProgram } = await Promise.resolve().then(() => (init_program(), exports_program));
|
|
96523
|
-
const { buildCompletionTree: buildCompletionTree2, listTopLevelCommandNames: listTopLevelCommandNames2 } = await Promise.resolve().then(() => exports_command_tree);
|
|
96524
|
-
const { setReplCommandNames: setReplCommandNames2 } = await Promise.resolve().then(() => (init_command_registry(), exports_command_registry));
|
|
96525
|
-
setReplCommandNames2(listTopLevelCommandNames2(buildCompletionTree2(buildProgram())));
|
|
96526
97018
|
const engine = new ReplEngine(adapter, context, HISTORY_PATH, config);
|
|
96527
97019
|
const complete = createCompleter(context);
|
|
96528
97020
|
console.error(import_picocolors5.default.bold(t_vars("shell.welcome", {
|
|
@@ -96612,7 +97104,7 @@ async function runBatchSession(engine, input) {
|
|
|
96612
97104
|
}
|
|
96613
97105
|
}
|
|
96614
97106
|
}
|
|
96615
|
-
var import_picocolors5, HISTORY_PATH, MONGO_COMPLETION_EAGER_THRESHOLD = 20, shellCommand;
|
|
97107
|
+
var import_picocolors5, HISTORY_PATH, MONGO_COMPLETION_EAGER_THRESHOLD = 20, REDIS_COMPLETION_KEY_LIMIT = 1000, shellCommand;
|
|
96616
97108
|
var init_shell3 = __esm(() => {
|
|
96617
97109
|
init_esm();
|
|
96618
97110
|
init_config();
|
|
@@ -96730,30 +97222,30 @@ class PostgreSQLDDLGenerator {
|
|
|
96730
97222
|
dropIndex(indexName, _table) {
|
|
96731
97223
|
return { sql: `DROP INDEX ${q(indexName)};`, warnings: [] };
|
|
96732
97224
|
}
|
|
96733
|
-
addConstraint(
|
|
96734
|
-
const t2 = q(
|
|
97225
|
+
addConstraint(constraint2) {
|
|
97226
|
+
const t2 = q(constraint2.table);
|
|
96735
97227
|
const warnings = [];
|
|
96736
|
-
switch (
|
|
97228
|
+
switch (constraint2.type) {
|
|
96737
97229
|
case "foreign_key": {
|
|
96738
|
-
const name2 =
|
|
96739
|
-
const onDelete =
|
|
97230
|
+
const name2 = constraint2.name || `fk_${constraint2.table}_${constraint2.column}`;
|
|
97231
|
+
const onDelete = constraint2.onDelete ? ` ON DELETE ${constraint2.onDelete.toUpperCase().replace("_", " ")}` : "";
|
|
96740
97232
|
return {
|
|
96741
|
-
sql: `ALTER TABLE ${t2} ADD CONSTRAINT ${q(name2)} FOREIGN KEY (${q(
|
|
97233
|
+
sql: `ALTER TABLE ${t2} ADD CONSTRAINT ${q(name2)} FOREIGN KEY (${q(constraint2.column)}) REFERENCES ${q(constraint2.references.table)}(${q(constraint2.references.column)})${onDelete};`,
|
|
96742
97234
|
warnings
|
|
96743
97235
|
};
|
|
96744
97236
|
}
|
|
96745
97237
|
case "unique": {
|
|
96746
|
-
const name2 =
|
|
96747
|
-
const cols =
|
|
97238
|
+
const name2 = constraint2.name || `uq_${constraint2.table}_${constraint2.columns.join("_")}`;
|
|
97239
|
+
const cols = constraint2.columns.map(q).join(", ");
|
|
96748
97240
|
return {
|
|
96749
97241
|
sql: `ALTER TABLE ${t2} ADD CONSTRAINT ${q(name2)} UNIQUE (${cols});`,
|
|
96750
97242
|
warnings
|
|
96751
97243
|
};
|
|
96752
97244
|
}
|
|
96753
97245
|
case "check": {
|
|
96754
|
-
const name2 =
|
|
97246
|
+
const name2 = constraint2.name || `ck_${constraint2.table}`;
|
|
96755
97247
|
return {
|
|
96756
|
-
sql: `ALTER TABLE ${t2} ADD CONSTRAINT ${q(name2)} CHECK (${
|
|
97248
|
+
sql: `ALTER TABLE ${t2} ADD CONSTRAINT ${q(name2)} CHECK (${constraint2.expression});`,
|
|
96757
97249
|
warnings
|
|
96758
97250
|
};
|
|
96759
97251
|
}
|
|
@@ -96903,30 +97395,30 @@ class MySQLDDLGenerator {
|
|
|
96903
97395
|
warnings: table ? [] : ["MySQL requires ON <table> \u2014 specify table for this operation"]
|
|
96904
97396
|
};
|
|
96905
97397
|
}
|
|
96906
|
-
addConstraint(
|
|
96907
|
-
const t2 = q2(
|
|
97398
|
+
addConstraint(constraint2) {
|
|
97399
|
+
const t2 = q2(constraint2.table);
|
|
96908
97400
|
const warnings = [];
|
|
96909
|
-
switch (
|
|
97401
|
+
switch (constraint2.type) {
|
|
96910
97402
|
case "foreign_key": {
|
|
96911
|
-
const name2 =
|
|
96912
|
-
const onDelete =
|
|
97403
|
+
const name2 = constraint2.name || `fk_${constraint2.table}_${constraint2.column}`;
|
|
97404
|
+
const onDelete = constraint2.onDelete ? ` ON DELETE ${constraint2.onDelete.toUpperCase().replace("_", " ")}` : "";
|
|
96913
97405
|
return {
|
|
96914
|
-
sql: `ALTER TABLE ${t2} ADD CONSTRAINT ${q2(name2)} FOREIGN KEY (${q2(
|
|
97406
|
+
sql: `ALTER TABLE ${t2} ADD CONSTRAINT ${q2(name2)} FOREIGN KEY (${q2(constraint2.column)}) REFERENCES ${q2(constraint2.references.table)}(${q2(constraint2.references.column)})${onDelete};`,
|
|
96915
97407
|
warnings
|
|
96916
97408
|
};
|
|
96917
97409
|
}
|
|
96918
97410
|
case "unique": {
|
|
96919
|
-
const name2 =
|
|
96920
|
-
const cols =
|
|
97411
|
+
const name2 = constraint2.name || `uq_${constraint2.table}_${constraint2.columns.join("_")}`;
|
|
97412
|
+
const cols = constraint2.columns.map(q2).join(", ");
|
|
96921
97413
|
return {
|
|
96922
97414
|
sql: `ALTER TABLE ${t2} ADD CONSTRAINT ${q2(name2)} UNIQUE (${cols});`,
|
|
96923
97415
|
warnings
|
|
96924
97416
|
};
|
|
96925
97417
|
}
|
|
96926
97418
|
case "check": {
|
|
96927
|
-
const name2 =
|
|
97419
|
+
const name2 = constraint2.name || `ck_${constraint2.table}`;
|
|
96928
97420
|
return {
|
|
96929
|
-
sql: `ALTER TABLE ${t2} ADD CONSTRAINT ${q2(name2)} CHECK (${
|
|
97421
|
+
sql: `ALTER TABLE ${t2} ADD CONSTRAINT ${q2(name2)} CHECK (${constraint2.expression});`,
|
|
96930
97422
|
warnings: ["CHECK constraints enforced in MySQL 8.0.16+ and MariaDB 10.2.1+ only"]
|
|
96931
97423
|
};
|
|
96932
97424
|
}
|