@askdb/core 1.0.0-beta.43 → 1.0.0-beta.45
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/README.md +15 -12
- package/dist/ask.d.ts +39 -38
- package/dist/ask.d.ts.map +1 -1
- package/dist/ask.js +135 -309
- package/dist/ask.js.map +1 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/enrichment/index.d.ts.map +1 -1
- package/dist/enrichment/prompt.d.ts +3 -3
- package/dist/enrichment/prompt.d.ts.map +1 -1
- package/dist/enrichment/prompt.js +41 -17
- package/dist/enrichment/prompt.js.map +1 -1
- package/dist/enrichment/suggest.d.ts +2 -2
- package/dist/enrichment/suggest.d.ts.map +1 -1
- package/dist/enrichment/suggest.js +6 -2
- package/dist/enrichment/suggest.js.map +1 -1
- package/dist/enrichment/types.d.ts +20 -1
- package/dist/enrichment/types.d.ts.map +1 -1
- package/dist/errors.d.ts +68 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +21 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/retrieval/synthesize-ddl.d.ts +11 -1
- package/dist/retrieval/synthesize-ddl.d.ts.map +1 -1
- package/dist/retrieval/synthesize-ddl.js +39 -34
- package/dist/retrieval/synthesize-ddl.js.map +1 -1
- package/dist/schema/normalize.d.ts +16 -0
- package/dist/schema/normalize.d.ts.map +1 -1
- package/dist/schema/normalize.js +1 -0
- package/dist/schema/normalize.js.map +1 -1
- package/dist/schema/v2/format.d.ts +35 -1
- package/dist/schema/v2/format.d.ts.map +1 -1
- package/dist/schema/v2/format.js +69 -22
- package/dist/schema/v2/format.js.map +1 -1
- package/dist/schema/v2/index.d.ts +2 -0
- package/dist/schema/v2/index.d.ts.map +1 -1
- package/dist/schema/v2/index.js +1 -0
- package/dist/schema/v2/index.js.map +1 -1
- package/dist/schema/v2/loader.d.ts +30 -4
- package/dist/schema/v2/loader.d.ts.map +1 -1
- package/dist/schema/v2/loader.js +90 -28
- package/dist/schema/v2/loader.js.map +1 -1
- package/dist/schema/v2/mentions.d.ts +43 -0
- package/dist/schema/v2/mentions.d.ts.map +1 -0
- package/dist/schema/v2/mentions.js +101 -0
- package/dist/schema/v2/mentions.js.map +1 -0
- package/dist/schema/v2/normalized.d.ts +5 -0
- package/dist/schema/v2/normalized.d.ts.map +1 -1
- package/dist/schema/v2/tenant-policy.d.ts +2 -2
- package/dist/sql/bind.d.ts +18 -5
- package/dist/sql/bind.d.ts.map +1 -1
- package/dist/sql/bind.js +36 -15
- package/dist/sql/bind.js.map +1 -1
- package/dist/sql/dialect-spec.d.ts +27 -1
- package/dist/sql/dialect-spec.d.ts.map +1 -1
- package/dist/sql/dialect-spec.js +20 -1
- package/dist/sql/dialect-spec.js.map +1 -1
- package/dist/sql/generate.d.ts +19 -11
- package/dist/sql/generate.d.ts.map +1 -1
- package/dist/sql/generate.js +72 -34
- package/dist/sql/generate.js.map +1 -1
- package/dist/sql/guardrail-decide.d.ts +56 -0
- package/dist/sql/guardrail-decide.d.ts.map +1 -0
- package/dist/sql/guardrail-decide.js +161 -0
- package/dist/sql/guardrail-decide.js.map +1 -0
- package/dist/sql/guardrails.d.ts +52 -0
- package/dist/sql/guardrails.d.ts.map +1 -0
- package/dist/sql/guardrails.js +100 -0
- package/dist/sql/guardrails.js.map +1 -0
- package/dist/sql/index.d.ts +4 -2
- package/dist/sql/index.d.ts.map +1 -1
- package/dist/sql/index.js +4 -2
- package/dist/sql/index.js.map +1 -1
- package/dist/sql/lexer.d.ts +13 -0
- package/dist/sql/lexer.d.ts.map +1 -1
- package/dist/sql/lexer.js +42 -7
- package/dist/sql/lexer.js.map +1 -1
- package/dist/sql/prompt-identifiers.d.ts +20 -0
- package/dist/sql/prompt-identifiers.d.ts.map +1 -0
- package/dist/sql/prompt-identifiers.js +64 -0
- package/dist/sql/prompt-identifiers.js.map +1 -0
- package/dist/sql/prompt.d.ts +7 -1
- package/dist/sql/prompt.d.ts.map +1 -1
- package/dist/sql/prompt.js +24 -3
- package/dist/sql/prompt.js.map +1 -1
- package/dist/sql/rebind.d.ts +89 -0
- package/dist/sql/rebind.d.ts.map +1 -0
- package/dist/sql/rebind.js +175 -0
- package/dist/sql/rebind.js.map +1 -0
- package/dist/sql/reserved-words.d.ts +15 -0
- package/dist/sql/reserved-words.d.ts.map +1 -0
- package/dist/sql/reserved-words.js +102 -0
- package/dist/sql/reserved-words.js.map +1 -0
- package/dist/sql/sensitive-guardrail.d.ts +7 -2
- package/dist/sql/sensitive-guardrail.d.ts.map +1 -1
- package/dist/sql/sensitive-guardrail.js +20 -37
- package/dist/sql/sensitive-guardrail.js.map +1 -1
- package/dist/sql/tenant-guardrail.d.ts +3 -14
- package/dist/sql/tenant-guardrail.d.ts.map +1 -1
- package/dist/sql/tenant-guardrail.js +16 -65
- package/dist/sql/tenant-guardrail.js.map +1 -1
- package/dist/sql/tenant-placeholders.d.ts +12 -23
- package/dist/sql/tenant-placeholders.d.ts.map +1 -1
- package/dist/sql/tenant-placeholders.js +46 -27
- package/dist/sql/tenant-placeholders.js.map +1 -1
- package/dist/sql/tenant-scope-expand.d.ts +46 -0
- package/dist/sql/tenant-scope-expand.d.ts.map +1 -0
- package/dist/sql/tenant-scope-expand.js +101 -0
- package/dist/sql/tenant-scope-expand.js.map +1 -0
- package/dist/sql/tsql-batch.d.ts +56 -0
- package/dist/sql/tsql-batch.d.ts.map +1 -0
- package/dist/sql/tsql-batch.js +246 -0
- package/dist/sql/tsql-batch.js.map +1 -0
- package/dist/sql/validate.d.ts +16 -3
- package/dist/sql/validate.d.ts.map +1 -1
- package/dist/sql/validate.js +43 -8
- package/dist/sql/validate.js.map +1 -1
- package/package.json +9 -6
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type GuardrailFinding, type GuardrailForm, type GuardrailVerdict, type SensitiveReference, type SensitiveReferenceRuleCode, type SensitiveScopeReport, type TenantGuardrailWarning } from "../errors.js";
|
|
2
|
+
/**
|
|
3
|
+
* The decision point (ADR 0010, decision 2): the only code that knows what `strict`,
|
|
4
|
+
* `warn` and `off` mean, at each enforcement point. A leaf module: the checks and the
|
|
5
|
+
* enforcement points import it, and it imports only the error vocabulary.
|
|
6
|
+
*/
|
|
7
|
+
/** `return`: `ask()` / `generateSelectSql()` handing SQL back. `rebind`: `bindPreparedQuery()`. */
|
|
8
|
+
export type GuardrailPoint = "return" | "rebind";
|
|
9
|
+
export type GuardrailModes = {
|
|
10
|
+
/** The tenant policy's `enforcement`; absent without a policy. */
|
|
11
|
+
tenant?: "warn" | "strict";
|
|
12
|
+
/** The caller's `sensitiveGuardrailMode`, default `"warn"`. With `"off"` the check doesn't run. */
|
|
13
|
+
sensitive: "off" | "warn" | "strict";
|
|
14
|
+
/** Rebind only: checks whose `warn` the caller accepts. */
|
|
15
|
+
acceptWarnings?: ReadonlyArray<"tenant">;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Map findings to a verdict. Read-only findings always deny. Tenant findings deny under
|
|
19
|
+
* `strict`; under `warn` they warn when returned, and deny at rebind unless the caller
|
|
20
|
+
* lists `"tenant"` in `acceptWarnings`. Sensitive findings deny under `strict` and warn
|
|
21
|
+
* otherwise, at both points. The outcome is the most severe finding's.
|
|
22
|
+
*/
|
|
23
|
+
export declare function decide(findings: GuardrailFinding[], modes: GuardrailModes, point: GuardrailPoint): GuardrailVerdict;
|
|
24
|
+
/**
|
|
25
|
+
* Throw the typed error for a `deny` verdict, with the verdict attached. When several
|
|
26
|
+
* checks deny, the precedence is read-only (`SqlValidationError`), then tenant
|
|
27
|
+
* (`TenantGuardrailError`), then sensitive (`SensitiveReferenceError`).
|
|
28
|
+
*/
|
|
29
|
+
export declare function throwIfDenied(verdict: GuardrailVerdict, modes: GuardrailModes, point: GuardrailPoint): void;
|
|
30
|
+
/** Tenant findings for one form, from the rule's warnings. */
|
|
31
|
+
export declare function tenantFindings(warnings: readonly TenantGuardrailWarning[], form: GuardrailForm | "generator"): GuardrailFinding[];
|
|
32
|
+
/** The tenant findings as warnings, once each across forms (same rule, table and message). */
|
|
33
|
+
export declare function tenantWarnings(findings: readonly GuardrailFinding[]): TenantGuardrailWarning[];
|
|
34
|
+
/** `result.tenantGuardrail`, derived from the verdict. */
|
|
35
|
+
export declare function tenantGuardrailResult(findings: readonly GuardrailFinding[]): {
|
|
36
|
+
passed: boolean;
|
|
37
|
+
warnings: TenantGuardrailWarning[];
|
|
38
|
+
};
|
|
39
|
+
type SensitiveScan = {
|
|
40
|
+
references: SensitiveReference[];
|
|
41
|
+
unresolvedScope?: SensitiveScopeReport;
|
|
42
|
+
};
|
|
43
|
+
/** Render a reference as `schema.table.column` (or `table.column` when the schema has no namespace). */
|
|
44
|
+
export declare function formatSensitiveReference(ref: SensitiveReference): string;
|
|
45
|
+
/** Sensitive findings for one form: one per reference, plus one when the scope wasn't resolved. */
|
|
46
|
+
export declare function sensitiveFindings(scan: SensitiveScan, form: GuardrailForm): GuardrailFinding[];
|
|
47
|
+
/** `result.sensitiveGuardrail`, derived from the verdict: references once each across forms. */
|
|
48
|
+
export declare function sensitiveGuardrailResult(findings: readonly GuardrailFinding[]): {
|
|
49
|
+
passed: boolean;
|
|
50
|
+
references: SensitiveReference[];
|
|
51
|
+
unresolvedScope?: SensitiveScopeReport;
|
|
52
|
+
};
|
|
53
|
+
export declare function sensitiveRuleFor(scan: SensitiveScan): SensitiveReferenceRuleCode;
|
|
54
|
+
export declare function sensitiveStrictMessage(scan: SensitiveScan): string;
|
|
55
|
+
export {};
|
|
56
|
+
//# sourceMappingURL=guardrail-decide.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guardrail-decide.d.ts","sourceRoot":"","sources":["../../src/sql/guardrail-decide.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAElB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,0BAA0B,EAC/B,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC5B,MAAM,cAAc,CAAC;AAEtB;;;;GAIG;AAEH,mGAAmG;AACnG,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEjD,MAAM,MAAM,cAAc,GAAG;IAC3B,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC3B,mGAAmG;IACnG,SAAS,EAAE,KAAK,GAAG,MAAM,GAAG,QAAQ,CAAC;IACrC,2DAA2D;IAC3D,cAAc,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;CAC1C,CAAC;AAIF;;;;;GAKG;AACH,wBAAgB,MAAM,CACpB,QAAQ,EAAE,gBAAgB,EAAE,EAC5B,KAAK,EAAE,cAAc,EACrB,KAAK,EAAE,cAAc,GACpB,gBAAgB,CAOlB;AAiBD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,cAAc,GAAG,IAAI,CAgC3G;AAMD,8DAA8D;AAC9D,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,SAAS,sBAAsB,EAAE,EAC3C,IAAI,EAAE,aAAa,GAAG,WAAW,GAChC,gBAAgB,EAAE,CAEpB;AAED,8FAA8F;AAC9F,wBAAgB,cAAc,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,GAAG,sBAAsB,EAAE,CAW9F;AAED,0DAA0D;AAC1D,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,GAAG;IAC5E,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,sBAAsB,EAAE,CAAC;CACpC,CAGA;AAMD,KAAK,aAAa,GAAG;IAAE,UAAU,EAAE,kBAAkB,EAAE,CAAC;IAAC,eAAe,CAAC,EAAE,oBAAoB,CAAA;CAAE,CAAC;AAElG,wGAAwG;AACxG,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,kBAAkB,GAAG,MAAM,CAGxE;AAED,mGAAmG;AACnG,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,aAAa,GAAG,gBAAgB,EAAE,CAuB9F;AAED,gGAAgG;AAChG,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,GAAG;IAC/E,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,kBAAkB,EAAE,CAAC;IACjC,eAAe,CAAC,EAAE,oBAAoB,CAAC;CACxC,CAiBA;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,aAAa,GAAG,0BAA0B,CAIhF;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAOlE"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { SensitiveReferenceError, SqlValidationError, TenantGuardrailError, } from "../errors.js";
|
|
2
|
+
const SEVERITY = { allow: 0, warn: 1, deny: 2 };
|
|
3
|
+
/**
|
|
4
|
+
* Map findings to a verdict. Read-only findings always deny. Tenant findings deny under
|
|
5
|
+
* `strict`; under `warn` they warn when returned, and deny at rebind unless the caller
|
|
6
|
+
* lists `"tenant"` in `acceptWarnings`. Sensitive findings deny under `strict` and warn
|
|
7
|
+
* otherwise, at both points. The outcome is the most severe finding's.
|
|
8
|
+
*/
|
|
9
|
+
export function decide(findings, modes, point) {
|
|
10
|
+
let outcome = "allow";
|
|
11
|
+
for (const finding of findings) {
|
|
12
|
+
const o = outcomeOf(finding.check, modes, point);
|
|
13
|
+
if (SEVERITY[o] > SEVERITY[outcome])
|
|
14
|
+
outcome = o;
|
|
15
|
+
}
|
|
16
|
+
return { outcome, findings };
|
|
17
|
+
}
|
|
18
|
+
function outcomeOf(check, modes, point) {
|
|
19
|
+
switch (check) {
|
|
20
|
+
case "read-only":
|
|
21
|
+
return "deny";
|
|
22
|
+
case "tenant":
|
|
23
|
+
// A tenant finding without a recognized mode (no policy, or an unknown value) fails closed.
|
|
24
|
+
if (modes.tenant !== "warn")
|
|
25
|
+
return "deny";
|
|
26
|
+
if (point === "rebind" && !modes.acceptWarnings?.includes("tenant"))
|
|
27
|
+
return "deny";
|
|
28
|
+
return "warn";
|
|
29
|
+
case "sensitive":
|
|
30
|
+
// Like the tenant case, a mode it doesn't recognize fails closed.
|
|
31
|
+
return modes.sensitive === "warn" ? "warn" : modes.sensitive === "off" ? "allow" : "deny";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Throw the typed error for a `deny` verdict, with the verdict attached. When several
|
|
36
|
+
* checks deny, the precedence is read-only (`SqlValidationError`), then tenant
|
|
37
|
+
* (`TenantGuardrailError`), then sensitive (`SensitiveReferenceError`).
|
|
38
|
+
*/
|
|
39
|
+
export function throwIfDenied(verdict, modes, point) {
|
|
40
|
+
if (verdict.outcome !== "deny")
|
|
41
|
+
return;
|
|
42
|
+
const denied = (check) => verdict.findings.filter((f) => f.check === check && outcomeOf(check, modes, point) === "deny");
|
|
43
|
+
let error;
|
|
44
|
+
const readOnly = denied("read-only");
|
|
45
|
+
const tenant = denied("tenant");
|
|
46
|
+
if (readOnly.length > 0) {
|
|
47
|
+
const first = readOnly[0];
|
|
48
|
+
error = new SqlValidationError(first.message, first.rule, first.hint);
|
|
49
|
+
}
|
|
50
|
+
else if (tenant.length > 0) {
|
|
51
|
+
const warnings = tenantWarnings(tenant);
|
|
52
|
+
const detail = warnings.map((w) => w.message).join("; ");
|
|
53
|
+
error = new TenantGuardrailError(modes.tenant === "warn"
|
|
54
|
+
? "Tenant guardrail found issues (policy enforcement: warn), and bindPreparedQuery() refuses a " +
|
|
55
|
+
`tenant warning unless acceptWarnings includes "tenant": ${detail}`
|
|
56
|
+
: `Tenant guardrail validation failed (strict mode): ${detail}`, warnings);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
const result = sensitiveGuardrailResult(denied("sensitive"));
|
|
60
|
+
error = new SensitiveReferenceError(sensitiveStrictMessage(result), sensitiveRuleFor(result), result.references, result.unresolvedScope);
|
|
61
|
+
}
|
|
62
|
+
error.verdict = verdict;
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Tenant findings ↔ the TenantGuardrailResult shape
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
/** Tenant findings for one form, from the rule's warnings. */
|
|
69
|
+
export function tenantFindings(warnings, form) {
|
|
70
|
+
return warnings.map((w) => ({ check: "tenant", form, rule: w.rule, message: w.message, tableId: w.tableId }));
|
|
71
|
+
}
|
|
72
|
+
/** The tenant findings as warnings, once each across forms (same rule, table and message). */
|
|
73
|
+
export function tenantWarnings(findings) {
|
|
74
|
+
const seen = new Set();
|
|
75
|
+
const out = [];
|
|
76
|
+
for (const f of findings) {
|
|
77
|
+
if (f.check !== "tenant")
|
|
78
|
+
continue;
|
|
79
|
+
const key = `${f.rule}\u0000${f.tableId}\u0000${f.message}`;
|
|
80
|
+
if (seen.has(key))
|
|
81
|
+
continue;
|
|
82
|
+
seen.add(key);
|
|
83
|
+
out.push({ rule: f.rule, tableId: f.tableId, message: f.message });
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
/** `result.tenantGuardrail`, derived from the verdict. */
|
|
88
|
+
export function tenantGuardrailResult(findings) {
|
|
89
|
+
const warnings = tenantWarnings(findings);
|
|
90
|
+
return { passed: warnings.length === 0, warnings };
|
|
91
|
+
}
|
|
92
|
+
/** Render a reference as `schema.table.column` (or `table.column` when the schema has no namespace). */
|
|
93
|
+
export function formatSensitiveReference(ref) {
|
|
94
|
+
const table = ref.schema ? `${ref.schema}.${ref.table}` : ref.table;
|
|
95
|
+
return `${table}.${ref.column}`;
|
|
96
|
+
}
|
|
97
|
+
/** Sensitive findings for one form: one per reference, plus one when the scope wasn't resolved. */
|
|
98
|
+
export function sensitiveFindings(scan, form) {
|
|
99
|
+
const findings = scan.references.map((reference) => {
|
|
100
|
+
const table = reference.matchKind === "table";
|
|
101
|
+
return {
|
|
102
|
+
check: "sensitive",
|
|
103
|
+
form,
|
|
104
|
+
rule: table ? "SENSITIVE_TABLE_REFERENCED" : "SENSITIVE_COLUMN_REFERENCED",
|
|
105
|
+
message: table
|
|
106
|
+
? `SQL references sensitive table ${formatSensitiveReference(reference).replace(/\.\*$/, "")}`
|
|
107
|
+
: `SQL references sensitive column ${formatSensitiveReference(reference)}`,
|
|
108
|
+
reference,
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
if (scan.unresolvedScope) {
|
|
112
|
+
findings.push({
|
|
113
|
+
check: "sensitive",
|
|
114
|
+
form,
|
|
115
|
+
rule: "UNRESOLVED_TABLE_SCOPE",
|
|
116
|
+
message: scan.unresolvedScope.message,
|
|
117
|
+
unresolvedScope: scan.unresolvedScope,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return findings;
|
|
121
|
+
}
|
|
122
|
+
/** `result.sensitiveGuardrail`, derived from the verdict: references once each across forms. */
|
|
123
|
+
export function sensitiveGuardrailResult(findings) {
|
|
124
|
+
const references = [];
|
|
125
|
+
const seen = new Set();
|
|
126
|
+
let unresolvedScope;
|
|
127
|
+
let any = false;
|
|
128
|
+
for (const f of findings) {
|
|
129
|
+
if (f.check !== "sensitive")
|
|
130
|
+
continue;
|
|
131
|
+
any = true;
|
|
132
|
+
if (f.unresolvedScope)
|
|
133
|
+
unresolvedScope ??= f.unresolvedScope;
|
|
134
|
+
if (!f.reference)
|
|
135
|
+
continue;
|
|
136
|
+
const r = f.reference;
|
|
137
|
+
const key = `${r.schema ?? ""}\u0000${r.table}\u0000${r.column}\u0000${r.matchKind}`;
|
|
138
|
+
if (seen.has(key))
|
|
139
|
+
continue;
|
|
140
|
+
seen.add(key);
|
|
141
|
+
references.push(r);
|
|
142
|
+
}
|
|
143
|
+
return { passed: !any, references, ...(unresolvedScope ? { unresolvedScope } : {}) };
|
|
144
|
+
}
|
|
145
|
+
export function sensitiveRuleFor(scan) {
|
|
146
|
+
if (scan.references.some((r) => r.matchKind === "table"))
|
|
147
|
+
return "SENSITIVE_TABLE_REFERENCED";
|
|
148
|
+
if (scan.references.length > 0)
|
|
149
|
+
return "SENSITIVE_COLUMN_REFERENCED";
|
|
150
|
+
return "UNRESOLVED_TABLE_SCOPE";
|
|
151
|
+
}
|
|
152
|
+
export function sensitiveStrictMessage(scan) {
|
|
153
|
+
const parts = [];
|
|
154
|
+
if (scan.references.length > 0) {
|
|
155
|
+
parts.push(`SQL references sensitive identifiers: ${scan.references.map(formatSensitiveReference).join(", ")}`);
|
|
156
|
+
}
|
|
157
|
+
if (scan.unresolvedScope)
|
|
158
|
+
parts.push(scan.unresolvedScope.message);
|
|
159
|
+
return `Sensitive-identifier guardrail failed (strict mode): ${parts.join("; ")}`;
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=guardrail-decide.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guardrail-decide.js","sourceRoot":"","sources":["../../src/sql/guardrail-decide.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type GuardrailCheckId, type GuardrailFinding, type GuardrailForm, type GuardrailVerdict } from "../errors.js";
|
|
2
|
+
import type { AskDbLogger } from "../logging/askdb-logger.js";
|
|
3
|
+
import type { AnyNormalizedSchema } from "../schema/types.js";
|
|
4
|
+
import type { NormalizedTenantPolicy, TenantScope } from "../schema/v2/tenant-policy.js";
|
|
5
|
+
import type { DialectSpec } from "./dialect-spec.js";
|
|
6
|
+
import { type GuardrailModes } from "./guardrail-decide.js";
|
|
7
|
+
import { type SensitiveGuardrailMode } from "./sensitive-guardrail.js";
|
|
8
|
+
/**
|
|
9
|
+
* The checks as pure rules (ADR 0010, decision 1): each takes a candidate and returns
|
|
10
|
+
* findings. They know nothing about modes and never throw for a finding; `decide`
|
|
11
|
+
* (`guardrail-decide.ts`) maps the findings to a verdict. Internal to `@askdb/core`.
|
|
12
|
+
*/
|
|
13
|
+
/** What every enforcement point checks: the untrusted forms, before AskDB renders them. */
|
|
14
|
+
export type Candidate = {
|
|
15
|
+
/** `sql`: the model's bound SQL. `template`: its `sql-unbound` block (`preparedQuery.namedSql`). */
|
|
16
|
+
forms: Partial<Record<GuardrailForm, string>>;
|
|
17
|
+
/** Undefined for a custom `AskDialect`. */
|
|
18
|
+
dialect: DialectSpec | undefined;
|
|
19
|
+
schema: AnyNormalizedSchema;
|
|
20
|
+
/** A v2 schema's tenant policy, with the enforcement point's (expanded) scope. */
|
|
21
|
+
tenant?: {
|
|
22
|
+
policy: NormalizedTenantPolicy;
|
|
23
|
+
scope: TenantScope;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* ADR 0010, decision 3: which checks each enforcement point runs, and the modes `decide`
|
|
28
|
+
* reads there. `ask()` with a custom `AskDialect` has no `dialect`, so no read-only check;
|
|
29
|
+
* `generateSelectSql()` takes no sensitive mode, so no sensitive check; `acceptWarnings`
|
|
30
|
+
* means something at rebind only.
|
|
31
|
+
*/
|
|
32
|
+
export declare function guardrailPlan(path: "ask" | "generate" | "rebind", input: {
|
|
33
|
+
dialect: DialectSpec | undefined;
|
|
34
|
+
schema: AnyNormalizedSchema;
|
|
35
|
+
tenantPolicy: NormalizedTenantPolicy | undefined;
|
|
36
|
+
sensitiveGuardrailMode?: SensitiveGuardrailMode | "off";
|
|
37
|
+
acceptWarnings?: ReadonlyArray<"tenant">;
|
|
38
|
+
}): {
|
|
39
|
+
checks: GuardrailCheckId[];
|
|
40
|
+
modes: GuardrailModes;
|
|
41
|
+
};
|
|
42
|
+
/** Run each listed check on every form in the candidate. */
|
|
43
|
+
export declare function evaluateGuardrails(candidate: Candidate, checks: readonly GuardrailCheckId[]): GuardrailFinding[];
|
|
44
|
+
/**
|
|
45
|
+
* Log a verdict the way `ask()` and `generateSelectSql()` always have: one tenant
|
|
46
|
+
* pass/fail event (with a tenant policy), and the sensitive references (when checked).
|
|
47
|
+
*/
|
|
48
|
+
export declare function logGuardrailVerdict(logger: AskDbLogger | undefined, verdict: GuardrailVerdict, context: {
|
|
49
|
+
tenantPolicy: NormalizedTenantPolicy | undefined;
|
|
50
|
+
sensitiveChecked: boolean;
|
|
51
|
+
}): void;
|
|
52
|
+
//# sourceMappingURL=guardrails.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guardrails.d.ts","sourceRoot":"","sources":["../../src/sql/guardrails.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACtB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAE9D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,KAAK,EAAE,sBAAsB,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AACzF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EACL,KAAK,cAAc,EAMpB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAGL,KAAK,sBAAsB,EAC5B,MAAM,0BAA0B,CAAC;AAIlC;;;;GAIG;AAEH,2FAA2F;AAC3F,MAAM,MAAM,SAAS,GAAG;IACtB,oGAAoG;IACpG,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9C,2CAA2C;IAC3C,OAAO,EAAE,WAAW,GAAG,SAAS,CAAC;IACjC,MAAM,EAAE,mBAAmB,CAAC;IAC5B,kFAAkF;IAClF,MAAM,CAAC,EAAE;QAAE,MAAM,EAAE,sBAAsB,CAAC;QAAC,KAAK,EAAE,WAAW,CAAA;KAAE,CAAC;CACjE,CAAC;AAgCF;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,KAAK,GAAG,UAAU,GAAG,QAAQ,EACnC,KAAK,EAAE;IACL,OAAO,EAAE,WAAW,GAAG,SAAS,CAAC;IACjC,MAAM,EAAE,mBAAmB,CAAC;IAC5B,YAAY,EAAE,sBAAsB,GAAG,SAAS,CAAC;IACjD,sBAAsB,CAAC,EAAE,sBAAsB,GAAG,KAAK,CAAC;IACxD,cAAc,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;CAC1C,GACA;IAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAAC,KAAK,EAAE,cAAc,CAAA;CAAE,CAcvD;AAED,4DAA4D;AAC5D,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,SAAS,gBAAgB,EAAE,GAClC,gBAAgB,EAAE,CASpB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,WAAW,GAAG,SAAS,EAC/B,OAAO,EAAE,gBAAgB,EACzB,OAAO,EAAE;IAAE,YAAY,EAAE,sBAAsB,GAAG,SAAS,CAAC;IAAC,gBAAgB,EAAE,OAAO,CAAA;CAAE,GACvF,IAAI,CA4BN"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { SqlValidationError, } from "../errors.js";
|
|
2
|
+
import { AskDbLogEvent } from "../logging/log-events.js";
|
|
3
|
+
import { formatSensitiveReference, sensitiveFindings, sensitiveGuardrailResult, tenantFindings, tenantWarnings, } from "./guardrail-decide.js";
|
|
4
|
+
import { scanSensitiveReferences, schemaHasSensitiveIdentifiers, } from "./sensitive-guardrail.js";
|
|
5
|
+
import { tenantRuleWarnings } from "./tenant-guardrail.js";
|
|
6
|
+
import { validateSelectSql } from "./validate.js";
|
|
7
|
+
const CHECKS = {
|
|
8
|
+
// Needs a DialectSpec: a custom AskDialect may target non-SELECT SQL (decision 3).
|
|
9
|
+
"read-only": ({ dialect }, form, sql) => {
|
|
10
|
+
if (!dialect)
|
|
11
|
+
return [];
|
|
12
|
+
try {
|
|
13
|
+
validateSelectSql(dialect, sql);
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
if (!(error instanceof SqlValidationError))
|
|
18
|
+
throw error;
|
|
19
|
+
return [
|
|
20
|
+
{
|
|
21
|
+
check: "read-only",
|
|
22
|
+
form,
|
|
23
|
+
rule: error.rule,
|
|
24
|
+
message: error.message,
|
|
25
|
+
...(error.hint !== undefined ? { hint: error.hint } : {}),
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
tenant: ({ tenant, dialect }, form, sql) => tenant ? tenantFindings(tenantRuleWarnings(sql, tenant.policy, tenant.scope, dialect), form) : [],
|
|
31
|
+
sensitive: ({ schema, dialect }, form, sql) => sensitiveFindings(scanSensitiveReferences(sql, schema, dialect), form),
|
|
32
|
+
};
|
|
33
|
+
const FORMS = ["sql", "template"];
|
|
34
|
+
/**
|
|
35
|
+
* ADR 0010, decision 3: which checks each enforcement point runs, and the modes `decide`
|
|
36
|
+
* reads there. `ask()` with a custom `AskDialect` has no `dialect`, so no read-only check;
|
|
37
|
+
* `generateSelectSql()` takes no sensitive mode, so no sensitive check; `acceptWarnings`
|
|
38
|
+
* means something at rebind only.
|
|
39
|
+
*/
|
|
40
|
+
export function guardrailPlan(path, input) {
|
|
41
|
+
const sensitive = path === "generate" ? "off" : (input.sensitiveGuardrailMode ?? "warn");
|
|
42
|
+
const checks = [];
|
|
43
|
+
if (input.dialect)
|
|
44
|
+
checks.push("read-only");
|
|
45
|
+
if (input.tenantPolicy)
|
|
46
|
+
checks.push("tenant");
|
|
47
|
+
if (sensitive !== "off" && schemaHasSensitiveIdentifiers(input.schema))
|
|
48
|
+
checks.push("sensitive");
|
|
49
|
+
return {
|
|
50
|
+
checks,
|
|
51
|
+
modes: {
|
|
52
|
+
...(input.tenantPolicy ? { tenant: input.tenantPolicy.enforcement } : {}),
|
|
53
|
+
sensitive,
|
|
54
|
+
...(path === "rebind" && input.acceptWarnings ? { acceptWarnings: input.acceptWarnings } : {}),
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** Run each listed check on every form in the candidate. */
|
|
59
|
+
export function evaluateGuardrails(candidate, checks) {
|
|
60
|
+
const findings = [];
|
|
61
|
+
for (const check of checks) {
|
|
62
|
+
for (const form of FORMS) {
|
|
63
|
+
const sql = candidate.forms[form];
|
|
64
|
+
if (sql !== undefined)
|
|
65
|
+
findings.push(...CHECKS[check](candidate, form, sql));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return findings;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Log a verdict the way `ask()` and `generateSelectSql()` always have: one tenant
|
|
72
|
+
* pass/fail event (with a tenant policy), and the sensitive references (when checked).
|
|
73
|
+
*/
|
|
74
|
+
export function logGuardrailVerdict(logger, verdict, context) {
|
|
75
|
+
if (context.tenantPolicy) {
|
|
76
|
+
const warnings = tenantWarnings(verdict.findings);
|
|
77
|
+
if (warnings.length === 0) {
|
|
78
|
+
logger?.info({ event: AskDbLogEvent.TenantGuardrailPassed }, "tenant guardrail validation passed");
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
logger?.info({
|
|
82
|
+
event: AskDbLogEvent.TenantGuardrailFailed,
|
|
83
|
+
warningCount: warnings.length,
|
|
84
|
+
enforcement: context.tenantPolicy.enforcement,
|
|
85
|
+
}, "tenant guardrail validation found issues");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (!context.sensitiveChecked)
|
|
89
|
+
return;
|
|
90
|
+
const references = sensitiveGuardrailResult(verdict.findings).references;
|
|
91
|
+
if (references.length === 0)
|
|
92
|
+
return;
|
|
93
|
+
const sensitiveColumns = references.map(formatSensitiveReference);
|
|
94
|
+
logger?.info({
|
|
95
|
+
event: AskDbLogEvent.PipelineSensitiveSqlWarning,
|
|
96
|
+
sensitiveColumnCount: sensitiveColumns.length,
|
|
97
|
+
sensitiveColumns,
|
|
98
|
+
}, "generated SQL references sensitive identifiers");
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=guardrails.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guardrails.js","sourceRoot":"","sources":["../../src/sql/guardrails.ts"],"names":[],"mappings":"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"}
|
package/dist/sql/index.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export { extractSqlFromModelText, extractUnboundSqlFromModelText } from "./extract-sql.js";
|
|
2
|
-
export {
|
|
2
|
+
export { escapeSqlLiteral, type QueryParameterType, type QueryParameterValue, type QueryParamSlot, type QueryParameterBinding, type PreparedQuery, } from "./bind.js";
|
|
3
|
+
export { bindPreparedQuery, type BindGuardrails, type BoundQuery } from "./rebind.js";
|
|
3
4
|
export { parseParameterManifest, type ParameterManifest, type ManifestParameter, } from "./parameter-manifest.js";
|
|
4
5
|
export { generateSelectSql, type GenerateSelectSqlResult, type GenerateSqlDeps, } from "./generate.js";
|
|
5
6
|
export { validateSelectSql, buildSelectGuardrailExplanation, type SelectGuardrailExplain, } from "./validate.js";
|
|
6
7
|
export { buildNlToSqlUserPrompt, buildNlToSqlSystemPrompt, } from "./prompt.js";
|
|
8
|
+
export { promptIdentifierQuoter } from "./prompt-identifiers.js";
|
|
7
9
|
export { assertNlToSqlInputs, nlToSqlAmbiguityNotes, } from "./schema-question-precheck.js";
|
|
8
|
-
export { type DialectId, type DialectSpec, type BuiltInDialectId, POSTGRES_DIALECT, COCKROACHDB_DIALECT, MYSQL_DIALECT, MARIADB_DIALECT, SQLITE_DIALECT, SQLSERVER_DIALECT, BUILT_IN_DIALECTS, SUPPORTED_DIALECT_IDS, isBuiltInDialectId, getDialectSpec, } from "./dialect-spec.js";
|
|
10
|
+
export { type DialectId, type DialectSpec, type BuiltInDialectId, POSTGRES_DIALECT, COCKROACHDB_DIALECT, MYSQL_DIALECT, MARIADB_DIALECT, SQLITE_DIALECT, SQLSERVER_DIALECT, SINGLE_NAMESPACE_LABEL, BUILT_IN_DIALECTS, SUPPORTED_DIALECT_IDS, isBuiltInDialectId, getDialectSpec, } from "./dialect-spec.js";
|
|
9
11
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/sql/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sql/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,8BAA8B,EAAE,MAAM,kBAAkB,CAAC;AAC3F,OAAO,EACL,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sql/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,8BAA8B,EAAE,MAAM,kBAAkB,CAAC;AAC3F,OAAO,EACL,gBAAgB,EAChB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,KAAK,aAAa,GACnB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,iBAAiB,EAAE,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACtF,OAAO,EACL,sBAAsB,EACtB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,GACvB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,iBAAiB,EACjB,KAAK,uBAAuB,EAC5B,KAAK,eAAe,GACrB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,iBAAiB,EACjB,+BAA+B,EAC/B,KAAK,sBAAsB,GAC5B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,EACL,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,gBAAgB,EAChB,mBAAmB,EACnB,aAAa,EACb,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,sBAAsB,EACtB,iBAAiB,EACjB,qBAAqB,EACrB,kBAAkB,EAClB,cAAc,GACf,MAAM,mBAAmB,CAAC"}
|
package/dist/sql/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export { extractSqlFromModelText, extractUnboundSqlFromModelText } from "./extract-sql.js";
|
|
2
|
-
export {
|
|
2
|
+
export { escapeSqlLiteral, } from "./bind.js";
|
|
3
|
+
export { bindPreparedQuery } from "./rebind.js";
|
|
3
4
|
export { parseParameterManifest, } from "./parameter-manifest.js";
|
|
4
5
|
export { generateSelectSql, } from "./generate.js";
|
|
5
6
|
export { validateSelectSql, buildSelectGuardrailExplanation, } from "./validate.js";
|
|
6
7
|
export { buildNlToSqlUserPrompt, buildNlToSqlSystemPrompt, } from "./prompt.js";
|
|
8
|
+
export { promptIdentifierQuoter } from "./prompt-identifiers.js";
|
|
7
9
|
export { assertNlToSqlInputs, nlToSqlAmbiguityNotes, } from "./schema-question-precheck.js";
|
|
8
|
-
export { POSTGRES_DIALECT, COCKROACHDB_DIALECT, MYSQL_DIALECT, MARIADB_DIALECT, SQLITE_DIALECT, SQLSERVER_DIALECT, BUILT_IN_DIALECTS, SUPPORTED_DIALECT_IDS, isBuiltInDialectId, getDialectSpec, } from "./dialect-spec.js";
|
|
10
|
+
export { POSTGRES_DIALECT, COCKROACHDB_DIALECT, MYSQL_DIALECT, MARIADB_DIALECT, SQLITE_DIALECT, SQLSERVER_DIALECT, SINGLE_NAMESPACE_LABEL, BUILT_IN_DIALECTS, SUPPORTED_DIALECT_IDS, isBuiltInDialectId, getDialectSpec, } from "./dialect-spec.js";
|
|
9
11
|
//# sourceMappingURL=index.js.map
|
package/dist/sql/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sql/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,8BAA8B,EAAE,MAAM,kBAAkB,CAAC;AAC3F,OAAO,EACL,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sql/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,8BAA8B,EAAE,MAAM,kBAAkB,CAAC;AAC3F,OAAO,EACL,gBAAgB,GAMjB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,iBAAiB,EAAwC,MAAM,aAAa,CAAC;AACtF,OAAO,EACL,sBAAsB,GAGvB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,iBAAiB,GAGlB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,iBAAiB,EACjB,+BAA+B,GAEhC,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,EACL,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAIL,gBAAgB,EAChB,mBAAmB,EACnB,aAAa,EACb,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,sBAAsB,EACtB,iBAAiB,EACjB,qBAAqB,EACrB,kBAAkB,EAClB,cAAc,GACf,MAAM,mBAAmB,CAAC"}
|
package/dist/sql/lexer.d.ts
CHANGED
|
@@ -19,6 +19,11 @@
|
|
|
19
19
|
* | `#` line comment | — | yes | — | — |
|
|
20
20
|
* | `--` line comment | yes | only before whitespace | yes | yes |
|
|
21
21
|
* | nested `/* … *\/` | yes | no (`/*! … *\/` runs) | yes | no |
|
|
22
|
+
* | `0x`, `1E`, `$`/`€` money | — | — | literal | — |
|
|
23
|
+
* | U+00A0 etc. end a name | — | — | yes | — |
|
|
24
|
+
* | `@` continues a name | — | — | yes | — |
|
|
25
|
+
*
|
|
26
|
+
* The last three rows are `tsqlLiterals`; U+FEFF does not end a SQL Server name.
|
|
22
27
|
*
|
|
23
28
|
* The lexer never throws: a string, quoted identifier, or comment that runs off the end
|
|
24
29
|
* of the input is returned with `unterminated: true` so callers can fail closed.
|
|
@@ -49,6 +54,14 @@ export type SqlLexerProfile = {
|
|
|
49
54
|
atVariables: boolean;
|
|
50
55
|
/** SQL Server `#temp` / `##temp` table names. */
|
|
51
56
|
hashIdentifiers: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* SQL Server's token ends: `0x` with no hex digits, an exponent with no digits (`1E`,
|
|
59
|
+
* `1E+`), and money literals that start with a currency symbol (`$`, `$1.5`, `€`) are
|
|
60
|
+
* complete literals; U+200B and U+0085 are whitespace; and a name or variable continues
|
|
61
|
+
* through `@` and ends at any whitespace character except U+FEFF. A keyword written right after one of these
|
|
62
|
+
* is its own token, as it is to SQL Server.
|
|
63
|
+
*/
|
|
64
|
+
tsqlLiterals: boolean;
|
|
52
65
|
};
|
|
53
66
|
export declare const POSTGRES_LEXER: SqlLexerProfile;
|
|
54
67
|
export declare const MYSQL_LEXER: SqlLexerProfile;
|
package/dist/sql/lexer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lexer.d.ts","sourceRoot":"","sources":["../../src/sql/lexer.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"lexer.d.ts","sourceRoot":"","sources":["../../src/sql/lexer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,OAAO,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE1F,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,iBAAiB,CAAC;IACtB,sEAAsE;IACtE,gBAAgB,EAAE,OAAO,CAAC;IAC1B,4FAA4F;IAC5F,WAAW,EAAE,YAAY,GAAG,QAAQ,CAAC;IACrC,mBAAmB,EAAE,OAAO,CAAC;IAC7B,yFAAyF;IACzF,kBAAkB,EAAE,KAAK,GAAG,QAAQ,GAAG,SAAS,CAAC;IACjD,wCAAwC;IACxC,YAAY,EAAE,OAAO,CAAC;IACtB,sDAAsD;IACtD,aAAa,EAAE,OAAO,CAAC;IACvB,+BAA+B;IAC/B,YAAY,EAAE,OAAO,CAAC;IACtB,6FAA6F;IAC7F,qBAAqB,EAAE,OAAO,CAAC;IAC/B,mBAAmB,EAAE,OAAO,CAAC;IAC7B,gGAAgG;IAChG,kBAAkB,EAAE,OAAO,CAAC;IAC5B,+FAA+F;IAC/F,WAAW,EAAE,OAAO,CAAC;IACrB,iDAAiD;IACjD,eAAe,EAAE,OAAO,CAAC;IACzB;;;;;;OAMG;IACH,YAAY,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,eAe5B,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,eAezB,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,eAe7B,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,eAe1B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,aAAa,EAAE,eAe3B,CAAC;AAEF,qGAAqG;AACrG,eAAO,MAAM,qBAAqB,EAAE,SAAS,eAAe,EAK3D,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,kBAAkB,CAAC,GACpD,eAAe,GAAG,SAAS,CAmB7B;AAED,MAAM,MAAM,YAAY;AACtB,sCAAsC;AACpC,MAAM,GACN,mBAAmB,GACnB,QAAQ,GACR,QAAQ;AACV,wCAAwC;GACtC,WAAW;AACb,+EAA+E;GAC7E,OAAO,GACP,SAAS,CAAC;AAEd,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,YAAY,CAAC;IACnB,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,8FAA8F;IAC9F,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,YAAY,CAAC,EAAE,IAAI,CAAC;CACrB,CAAC;AAgBF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,GAAG,OAAO,CAItF;AAED,yFAAyF;AACzF,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,QAAQ,EAAE,CA4RxE;AAED,wDAAwD;AACxD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,QAAQ,EAAE,GAAG,OAAO,CAEzE"}
|
package/dist/sql/lexer.js
CHANGED
|
@@ -12,6 +12,7 @@ export const POSTGRES_LEXER = {
|
|
|
12
12
|
executableComments: false,
|
|
13
13
|
atVariables: false,
|
|
14
14
|
hashIdentifiers: false,
|
|
15
|
+
tsqlLiterals: false,
|
|
15
16
|
};
|
|
16
17
|
export const MYSQL_LEXER = {
|
|
17
18
|
id: "mysql",
|
|
@@ -27,6 +28,7 @@ export const MYSQL_LEXER = {
|
|
|
27
28
|
executableComments: true,
|
|
28
29
|
atVariables: true,
|
|
29
30
|
hashIdentifiers: false,
|
|
31
|
+
tsqlLiterals: false,
|
|
30
32
|
};
|
|
31
33
|
export const SQLSERVER_LEXER = {
|
|
32
34
|
id: "sqlserver",
|
|
@@ -42,6 +44,7 @@ export const SQLSERVER_LEXER = {
|
|
|
42
44
|
executableComments: false,
|
|
43
45
|
atVariables: true,
|
|
44
46
|
hashIdentifiers: true,
|
|
47
|
+
tsqlLiterals: true,
|
|
45
48
|
};
|
|
46
49
|
export const SQLITE_LEXER = {
|
|
47
50
|
id: "sqlite",
|
|
@@ -57,6 +60,7 @@ export const SQLITE_LEXER = {
|
|
|
57
60
|
executableComments: false,
|
|
58
61
|
atVariables: true,
|
|
59
62
|
hashIdentifiers: false,
|
|
63
|
+
tsqlLiterals: false,
|
|
60
64
|
};
|
|
61
65
|
/**
|
|
62
66
|
* Dialect-agnostic profile for callers that have no dialect (the tenant placeholder
|
|
@@ -78,6 +82,7 @@ export const GENERIC_LEXER = {
|
|
|
78
82
|
executableComments: false,
|
|
79
83
|
atVariables: true,
|
|
80
84
|
hashIdentifiers: false,
|
|
85
|
+
tsqlLiterals: false,
|
|
81
86
|
};
|
|
82
87
|
/** Every engine-specific profile. Used when the dialect is unknown and each reading must be safe. */
|
|
83
88
|
export const ENGINE_LEXER_PROFILES = [
|
|
@@ -118,6 +123,14 @@ const WORD_START = /[A-Za-z_\u0080-
|
|
|
118
123
|
const WORD_CHAR = /[A-Za-z0-9_$\u0080-]/;
|
|
119
124
|
const DIGIT = /[0-9]/;
|
|
120
125
|
const HEX = /[0-9A-Fa-f]/;
|
|
126
|
+
/** Currency symbols. SQL Server starts a money literal with one (its list is a subset). */
|
|
127
|
+
const CURRENCY = /\p{Sc}/u;
|
|
128
|
+
/**
|
|
129
|
+
* Characters SQL Server reads as whitespace that `\s` does not match. With `\s` minus
|
|
130
|
+
* U+FEFF they are the only characters at or above U+0080 that end a T-SQL name or
|
|
131
|
+
* number (every BMP character probed on SQL Server 2022, #327); U+FEFF continues a name.
|
|
132
|
+
*/
|
|
133
|
+
const TSQL_EXTRA_SPACE = /[\u200b\u0085]/;
|
|
121
134
|
const DOLLAR_TAG = /^\$([A-Za-z_\u0080-][A-Za-z0-9_\u0080-]*)?\$/;
|
|
122
135
|
/**
|
|
123
136
|
* Whether the `--` at `i` starts a line comment. It always does, except on MySQL/MariaDB
|
|
@@ -137,6 +150,13 @@ export function lexSql(sql, profile) {
|
|
|
137
150
|
let i = 0;
|
|
138
151
|
/** Index in `tokens` of the open MySQL `/*! … *\/` executable-comment opener, or -1. */
|
|
139
152
|
let executableOpener = -1;
|
|
153
|
+
const isSpace = (c) => /\s/.test(c) || (profile.tsqlLiterals && TSQL_EXTRA_SPACE.test(c));
|
|
154
|
+
/**
|
|
155
|
+
* `WORD_CHAR`, except that a SQL Server name also continues through `@` (`a@b` is one
|
|
156
|
+
* name, as `a$b` and `a#b` are) and ends at Unicode whitespace other than U+FEFF.
|
|
157
|
+
*/
|
|
158
|
+
const isWordChar = (c) => (WORD_CHAR.test(c) || (profile.tsqlLiterals && c === "@")) &&
|
|
159
|
+
!(profile.tsqlLiterals && c !== "\ufeff" && isSpace(c));
|
|
140
160
|
const push = (kind, start, end, extra) => {
|
|
141
161
|
const text = sql.slice(start, end);
|
|
142
162
|
const value = extra?.value ?? text;
|
|
@@ -187,7 +207,7 @@ export function lexSql(sql, profile) {
|
|
|
187
207
|
while (i < n) {
|
|
188
208
|
const ch = sql[i];
|
|
189
209
|
const next = sql[i + 1];
|
|
190
|
-
if (
|
|
210
|
+
if (isSpace(ch)) {
|
|
191
211
|
i++;
|
|
192
212
|
continue;
|
|
193
213
|
}
|
|
@@ -285,6 +305,21 @@ export function lexSql(sql, profile) {
|
|
|
285
305
|
i = j;
|
|
286
306
|
continue;
|
|
287
307
|
}
|
|
308
|
+
// SQL Server money: a currency symbol, then optional digits (`$`, `$1.5`, `€2`). `$`
|
|
309
|
+
// before a letter is a pseudocolumn (`$action`), lexed below as a parameter.
|
|
310
|
+
if (profile.tsqlLiterals && CURRENCY.test(ch) && !(ch === "$" && next !== undefined && WORD_START.test(next))) {
|
|
311
|
+
let j = i + 1;
|
|
312
|
+
while (j < n && DIGIT.test(sql[j]))
|
|
313
|
+
j++;
|
|
314
|
+
if (sql[j] === ".") {
|
|
315
|
+
j++;
|
|
316
|
+
while (j < n && DIGIT.test(sql[j]))
|
|
317
|
+
j++;
|
|
318
|
+
}
|
|
319
|
+
push("number", i, j);
|
|
320
|
+
i = j;
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
288
323
|
if (ch === "$") {
|
|
289
324
|
if (profile.dollarQuotes) {
|
|
290
325
|
const tag = DOLLAR_TAG.exec(sql.slice(i));
|
|
@@ -299,7 +334,7 @@ export function lexSql(sql, profile) {
|
|
|
299
334
|
}
|
|
300
335
|
if (next !== undefined && (DIGIT.test(next) || (!profile.dollarQuotes && WORD_START.test(next)))) {
|
|
301
336
|
let j = i + 1;
|
|
302
|
-
while (j < n &&
|
|
337
|
+
while (j < n && isWordChar(sql[j]) && sql[j] !== "$")
|
|
303
338
|
j++;
|
|
304
339
|
push("parameter", i, j);
|
|
305
340
|
i = j;
|
|
@@ -309,7 +344,7 @@ export function lexSql(sql, profile) {
|
|
|
309
344
|
// ---- words (and prefixed string literals) ---------------------------
|
|
310
345
|
if (WORD_START.test(ch) || (ch === "#" && profile.hashIdentifiers && next !== undefined && (next === "#" || WORD_START.test(next)))) {
|
|
311
346
|
let j = i + 1;
|
|
312
|
-
while (j < n && (
|
|
347
|
+
while (j < n && (isWordChar(sql[j]) || (profile.hashIdentifiers && sql[j] === "#")))
|
|
313
348
|
j++;
|
|
314
349
|
const word = sql.slice(i, j);
|
|
315
350
|
if (sql[j] === "'" && j - i === 1) {
|
|
@@ -330,7 +365,7 @@ export function lexSql(sql, profile) {
|
|
|
330
365
|
// ---- numbers --------------------------------------------------------
|
|
331
366
|
if (DIGIT.test(ch) || (ch === "." && next !== undefined && DIGIT.test(next))) {
|
|
332
367
|
let j = i;
|
|
333
|
-
if (ch === "0" && (next === "x" || next === "X") && HEX.test(sql[i + 2] ?? "")) {
|
|
368
|
+
if (ch === "0" && (next === "x" || next === "X") && (profile.tsqlLiterals || HEX.test(sql[i + 2] ?? ""))) {
|
|
334
369
|
j = i + 2;
|
|
335
370
|
while (j < n && HEX.test(sql[j]))
|
|
336
371
|
j++;
|
|
@@ -343,7 +378,7 @@ export function lexSql(sql, profile) {
|
|
|
343
378
|
while (j < n && DIGIT.test(sql[j]))
|
|
344
379
|
j++;
|
|
345
380
|
}
|
|
346
|
-
if ((sql[j] === "e" || sql[j] === "E") && /^[eE][+-]?[0-9]/.test(sql.slice(j))) {
|
|
381
|
+
if ((sql[j] === "e" || sql[j] === "E") && (profile.tsqlLiterals || /^[eE][+-]?[0-9]/.test(sql.slice(j)))) {
|
|
347
382
|
j += sql[j + 1] === "+" || sql[j + 1] === "-" ? 2 : 1;
|
|
348
383
|
while (j < n && DIGIT.test(sql[j]))
|
|
349
384
|
j++;
|
|
@@ -363,7 +398,7 @@ export function lexSql(sql, profile) {
|
|
|
363
398
|
}
|
|
364
399
|
if (ch === ":" && next !== undefined && WORD_START.test(next)) {
|
|
365
400
|
let j = i + 1;
|
|
366
|
-
while (j < n &&
|
|
401
|
+
while (j < n && isWordChar(sql[j]))
|
|
367
402
|
j++;
|
|
368
403
|
push("parameter", i, j);
|
|
369
404
|
i = j;
|
|
@@ -374,7 +409,7 @@ export function lexSql(sql, profile) {
|
|
|
374
409
|
if (sql[j] === "@")
|
|
375
410
|
j++;
|
|
376
411
|
if (j < n && WORD_START.test(sql[j])) {
|
|
377
|
-
while (j < n &&
|
|
412
|
+
while (j < n && isWordChar(sql[j]))
|
|
378
413
|
j++;
|
|
379
414
|
push("parameter", i, j);
|
|
380
415
|
i = j;
|