@askdb/core 1.0.0-beta.44 → 1.0.0-beta.46
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 +20 -19
- package/dist/ask.d.ts +45 -45
- package/dist/ask.d.ts.map +1 -1
- package/dist/ask.js +153 -310
- 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 +43 -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 +3 -1
- package/dist/enrichment/suggest.js.map +1 -1
- package/dist/enrichment/types.d.ts +29 -9
- package/dist/enrichment/types.d.ts.map +1 -1
- package/dist/errors.d.ts +97 -6
- 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 +7 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/logging/askdb-logger.d.ts +2 -0
- package/dist/logging/askdb-logger.d.ts.map +1 -1
- package/dist/logging/create-askdb-logger.d.ts.map +1 -1
- package/dist/logging/create-askdb-logger.js +3 -0
- package/dist/logging/create-askdb-logger.js.map +1 -1
- package/dist/logging/log-events.d.ts +8 -0
- package/dist/logging/log-events.d.ts.map +1 -1
- package/dist/logging/log-events.js +8 -0
- package/dist/logging/log-events.js.map +1 -1
- package/dist/retrieval/synthesize-ddl.d.ts +7 -1
- package/dist/retrieval/synthesize-ddl.d.ts.map +1 -1
- package/dist/retrieval/synthesize-ddl.js +36 -33
- package/dist/retrieval/synthesize-ddl.js.map +1 -1
- package/dist/schema/normalize.d.ts +6 -2
- package/dist/schema/normalize.d.ts.map +1 -1
- package/dist/schema/normalize.js +1 -4
- package/dist/schema/normalize.js.map +1 -1
- package/dist/schema/v2/format.d.ts +28 -1
- package/dist/schema/v2/format.d.ts.map +1 -1
- package/dist/schema/v2/format.js +53 -19
- package/dist/schema/v2/format.js.map +1 -1
- package/dist/schema/v2/index.d.ts +4 -3
- package/dist/schema/v2/index.d.ts.map +1 -1
- package/dist/schema/v2/index.js +4 -3
- package/dist/schema/v2/index.js.map +1 -1
- package/dist/schema/v2/loader.d.ts +28 -7
- package/dist/schema/v2/loader.d.ts.map +1 -1
- package/dist/schema/v2/loader.js +220 -96
- package/dist/schema/v2/loader.js.map +1 -1
- package/dist/schema/v2/mentions.d.ts +12 -0
- package/dist/schema/v2/mentions.d.ts.map +1 -1
- package/dist/schema/v2/mentions.js +65 -11
- package/dist/schema/v2/mentions.js.map +1 -1
- package/dist/schema/v2/save-file.d.ts +56 -0
- package/dist/schema/v2/save-file.d.ts.map +1 -0
- package/dist/schema/v2/save-file.js +138 -0
- package/dist/schema/v2/save-file.js.map +1 -0
- package/dist/schema/v2/tenant-policy-loader.d.ts +1 -10
- package/dist/schema/v2/tenant-policy-loader.d.ts.map +1 -1
- package/dist/schema/v2/tenant-policy-loader.js +65 -51
- package/dist/schema/v2/tenant-policy-loader.js.map +1 -1
- package/dist/schema/v2/tenant-policy.d.ts +7 -2
- package/dist/schema/v2/tenant-policy.d.ts.map +1 -1
- package/dist/schema/v2/tenant-policy.js +26 -0
- package/dist/schema/v2/tenant-policy.js.map +1 -1
- package/dist/sql/bind.d.ts +27 -7
- package/dist/sql/bind.d.ts.map +1 -1
- package/dist/sql/bind.js +33 -18
- package/dist/sql/bind.js.map +1 -1
- package/dist/sql/dialect-spec.d.ts +1 -0
- package/dist/sql/dialect-spec.d.ts.map +1 -1
- package/dist/sql/dialect-spec.js +7 -2
- package/dist/sql/dialect-spec.js.map +1 -1
- package/dist/sql/generate.d.ts +17 -11
- package/dist/sql/generate.d.ts.map +1 -1
- package/dist/sql/generate.js +65 -33
- package/dist/sql/generate.js.map +1 -1
- package/dist/sql/guardrail-decide.d.ts +58 -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 +2 -1
- package/dist/sql/index.d.ts.map +1 -1
- package/dist/sql/index.js +2 -1
- 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 +9 -0
- package/dist/sql/prompt-identifiers.d.ts.map +1 -1
- package/dist/sql/prompt-identifiers.js +22 -3
- package/dist/sql/prompt-identifiers.js.map +1 -1
- package/dist/sql/prompt.d.ts.map +1 -1
- package/dist/sql/prompt.js +5 -2
- package/dist/sql/prompt.js.map +1 -1
- package/dist/sql/rebind.d.ts +116 -0
- package/dist/sql/rebind.d.ts.map +1 -0
- package/dist/sql/rebind.js +215 -0
- package/dist/sql/rebind.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 +14 -33
- 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 +40 -14
- package/dist/sql/tenant-placeholders.d.ts.map +1 -1
- package/dist/sql/tenant-placeholders.js +92 -48
- package/dist/sql/tenant-placeholders.js.map +1 -1
- package/dist/sql/tenant-policy-warnings.d.ts +8 -0
- package/dist/sql/tenant-policy-warnings.d.ts.map +1 -0
- package/dist/sql/tenant-policy-warnings.js +21 -0
- package/dist/sql/tenant-policy-warnings.js.map +1 -0
- 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 +18 -3
- package/dist/sql/validate.d.ts.map +1 -1
- package/dist/sql/validate.js +127 -8
- package/dist/sql/validate.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { TenantScopeError } from "../errors.js";
|
|
2
|
+
import { subtreeRootIds } from "./tenant-hierarchy.js";
|
|
3
|
+
import { validateTenantScope } from "./tenant-scope-validate.js";
|
|
4
|
+
/**
|
|
5
|
+
* Replace a `subtree` access with the per-root access it expands to: `multi_root`
|
|
6
|
+
* with one entry per root the subtree covers (the scope's root first, then its
|
|
7
|
+
* descendant roots breadth-first), including a level with no IDs (`ids: []`), or `ids`
|
|
8
|
+
* when the subtree is the scope's root alone. Other access kinds pass through untouched.
|
|
9
|
+
*
|
|
10
|
+
* Each root's IDs stay under that root, so they bind only to its own placeholder:
|
|
11
|
+
* root tables have separate ID spaces, and folding a client ID into the agency
|
|
12
|
+
* placeholder would match another agency (#338). This relies on every root deriving a distinct
|
|
13
|
+
* placeholder, which `validateTenantScope()` and the policy loader enforce. The seeds are unioned into the
|
|
14
|
+
* `tenantRoot` entry here, not trusted to the resolver, so an ancestor never loses
|
|
15
|
+
* its own rows when a host returns strict descendants only.
|
|
16
|
+
*
|
|
17
|
+
* `ask()` runs this before generation, and a host runs it before a synchronous
|
|
18
|
+
* `bindPreparedQuery()` (`askdb.bind()` does it for you), so there is one expansion (ADR 0014).
|
|
19
|
+
* The scope is validated first (`validateTenantScope`), so an unknown root fails as in `ask()`.
|
|
20
|
+
*
|
|
21
|
+
* Fails closed with `SUBTREE_NOT_RESOLVABLE`: no resolver; a result that is an
|
|
22
|
+
* array (the old flat shape) or not a plain object; a key that isn't a root in
|
|
23
|
+
* this subtree; a value that isn't an array of non-empty strings; or no IDs at all.
|
|
24
|
+
*/
|
|
25
|
+
export async function expandTenantScope(policy, scope, resolve) {
|
|
26
|
+
validateTenantScope(policy, scope);
|
|
27
|
+
const access = scope.access;
|
|
28
|
+
if (access.kind !== "subtree")
|
|
29
|
+
return scope;
|
|
30
|
+
const { tenantRoot, rootIds } = access;
|
|
31
|
+
const levels = subtreeRootIds(policy, tenantRoot);
|
|
32
|
+
const shape = `{ ${levels.map((root) => `"${root}": [...]`).join(", ")} }`;
|
|
33
|
+
const fail = (message) => new TenantScopeError(message, "SUBTREE_NOT_RESOLVABLE");
|
|
34
|
+
if (!resolve) {
|
|
35
|
+
throw fail(`tenantScope.access is a 'subtree' of '${tenantRoot}', but no ` +
|
|
36
|
+
"resolveTenantDescendants was passed. AskDB does not query your database " +
|
|
37
|
+
"to find descendants: pass resolveTenantDescendants to expand the seed IDs, or pass " +
|
|
38
|
+
"an 'ids' or 'multi_root' access with each root's IDs already expanded.");
|
|
39
|
+
}
|
|
40
|
+
const result = await resolve(tenantRoot, rootIds);
|
|
41
|
+
if (Array.isArray(result)) {
|
|
42
|
+
throw fail(`resolveTenantDescendants for '${tenantRoot}' returned an array. It must return IDs per ` +
|
|
43
|
+
`tenant root, keyed by root table id: ${shape}. Root tables have separate ID spaces, so ` +
|
|
44
|
+
"a flat list can't say which root each ID belongs to, and binding them all to " +
|
|
45
|
+
`'${tenantRoot}' would match other tenants. Put the seeds and any same-table ` +
|
|
46
|
+
`descendants under '${tenantRoot}', and each descendant root's IDs under that root.`);
|
|
47
|
+
}
|
|
48
|
+
if (!isPlainObject(result)) {
|
|
49
|
+
throw fail(`resolveTenantDescendants for '${tenantRoot}' must return an object mapping each ` +
|
|
50
|
+
`tenant root in the subtree to its IDs: ${shape}.`);
|
|
51
|
+
}
|
|
52
|
+
// Read the result exactly once: validate and build from this snapshot, so a getter
|
|
53
|
+
// can't return one value to validation and another to the scope, and a property
|
|
54
|
+
// validation can't see (non-enumerable) is never read at all.
|
|
55
|
+
const idsByRoot = new Map();
|
|
56
|
+
const knownRoots = new Set(policy.roots.map((root) => root.id));
|
|
57
|
+
let returned = 0;
|
|
58
|
+
for (const [root, value] of Object.entries(result)) {
|
|
59
|
+
const ids = Array.isArray(value) ? [...value] : value;
|
|
60
|
+
if (!knownRoots.has(root)) {
|
|
61
|
+
throw fail(`resolveTenantDescendants returned IDs under a key the subtree can't have: '${root}' is ` +
|
|
62
|
+
`not a tenant root in the policy. Allowed keys for a subtree of '${tenantRoot}': ` +
|
|
63
|
+
`${levels.join(", ")}.`);
|
|
64
|
+
}
|
|
65
|
+
if (!levels.includes(root)) {
|
|
66
|
+
throw fail(`resolveTenantDescendants returned IDs under a key the subtree can't have: '${root}' is ` +
|
|
67
|
+
`not in the subtree of '${tenantRoot}' (the policy's hierarchy doesn't reach it from ` +
|
|
68
|
+
`there). Allowed keys: ${levels.join(", ")}.`);
|
|
69
|
+
}
|
|
70
|
+
if (!isTenantIdArray(ids)) {
|
|
71
|
+
throw fail(`resolveTenantDescendants: the IDs for '${root}' must be an array of non-empty strings.`);
|
|
72
|
+
}
|
|
73
|
+
idsByRoot.set(root, ids);
|
|
74
|
+
returned += ids.length;
|
|
75
|
+
}
|
|
76
|
+
if (returned === 0) {
|
|
77
|
+
throw fail(`resolveTenantDescendants returned no IDs for '${tenantRoot}' (seeds: ${rootIds.join(", ")}). ` +
|
|
78
|
+
`Return at least the seeds under '${tenantRoot}'; refusing to build an empty tenant scope.`);
|
|
79
|
+
}
|
|
80
|
+
// Every level the subtree covers stays in the scope, even one with no IDs. The
|
|
81
|
+
// guardrail then checks a read of that root on its own placeholder, and binding that
|
|
82
|
+
// placeholder fails closed (UNRESOLVED_TENANT_PLACEHOLDER). Dropping the level would
|
|
83
|
+
// leave its rows readable through a parent's foreign key or an ancestor, which is
|
|
84
|
+
// less restricted than a level the resolver narrowed to some IDs.
|
|
85
|
+
const scopes = levels.map((root) => {
|
|
86
|
+
const own = idsByRoot.get(root) ?? [];
|
|
87
|
+
return { tenantRoot: root, ids: [...new Set(root === tenantRoot ? [...rootIds, ...own] : own)] };
|
|
88
|
+
});
|
|
89
|
+
const expanded = scopes.length === 1 ? { kind: "ids", ...scopes[0] } : { kind: "multi_root", scopes };
|
|
90
|
+
return { ...scope, access: expanded };
|
|
91
|
+
}
|
|
92
|
+
function isTenantIdArray(value) {
|
|
93
|
+
return Array.isArray(value) && value.every((id) => typeof id === "string" && id.length > 0);
|
|
94
|
+
}
|
|
95
|
+
function isPlainObject(value) {
|
|
96
|
+
if (typeof value !== "object" || value === null)
|
|
97
|
+
return false;
|
|
98
|
+
const proto = Object.getPrototypeOf(value);
|
|
99
|
+
return proto === Object.prototype || proto === null;
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=tenant-scope-expand.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant-scope-expand.js","sourceRoot":"","sources":["../../src/sql/tenant-scope-expand.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a T-SQL batch starts a second statement without a `;`.
|
|
3
|
+
*
|
|
4
|
+
* SQL Server needs no separator between statements: `SELECT 1 SELECT 2` is two
|
|
5
|
+
* statements, and Studio's Execute runs the validated SQL inside a batch. This check
|
|
6
|
+
* finds the second statement in SQL that is otherwise a single SELECT. Only the top
|
|
7
|
+
* level (outside every bracket) is checked: a second statement inside brackets is a
|
|
8
|
+
* syntax error, and SQL Server runs nothing from a batch that does not compile.
|
|
9
|
+
*
|
|
10
|
+
* Verbs that start a statement (`DECLARE`, `WHILE`, `EXEC`, …) are the keyword
|
|
11
|
+
* denylist's job (`SQLSERVER_DIALECT.extraForbiddenKeywords`). This module covers the
|
|
12
|
+
* statements no verb announces:
|
|
13
|
+
*
|
|
14
|
+
* - a top-level `SELECT` that follows no set operator and is not the main query
|
|
15
|
+
* after a leading `WITH` list;
|
|
16
|
+
* - a top-level `(SELECT …` right after a complete expression, such as
|
|
17
|
+
* `SELECT 1 (SELECT 2)`. After a keyword, an operator, a comma or a function name
|
|
18
|
+
* the bracket opens a subquery instead;
|
|
19
|
+
* - a label: a `:` written directly against anything but `(`, a comma or an operator.
|
|
20
|
+
*
|
|
21
|
+
* It also rejects code SQL Server may split into tokens differently from the lexer
|
|
22
|
+
* ({@link findAmbiguousTsqlText}): rather than copy every corner of SQL Server's
|
|
23
|
+
* tokenizer, the check accepts plain code only.
|
|
24
|
+
*/
|
|
25
|
+
import type { SqlToken } from "./lexer.js";
|
|
26
|
+
/** The token where a second statement starts. */
|
|
27
|
+
export type TsqlStatementBoundary = {
|
|
28
|
+
token: SqlToken;
|
|
29
|
+
/**
|
|
30
|
+
* `select`: a top-level SELECT that continues nothing. `query`: a top-level
|
|
31
|
+
* `(SELECT …` after a complete expression. `label`: a `:` that ends a label.
|
|
32
|
+
*/
|
|
33
|
+
kind: "select" | "query" | "label";
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Code SQL Server may split into tokens differently from the lexer, described for an
|
|
37
|
+
* error message, or `undefined`. Text inside strings and quoted identifiers is not
|
|
38
|
+
* checked; `tokens` holds no comments. Fails closed on:
|
|
39
|
+
*
|
|
40
|
+
* - a character outside {@link PLAIN_CODE}: Unicode whitespace and format characters
|
|
41
|
+
* (U+00A0, U+200B, U+FEFF), symbols, currency signs other than `$`, `\` (a money
|
|
42
|
+
* sign to SQL Server) and ASCII control characters;
|
|
43
|
+
* - a money sign with no digits after it (`$ .5` is one money literal to SQL Server);
|
|
44
|
+
* - a number written directly against a word or variable (`1ESELECT`, `0xSELECT`);
|
|
45
|
+
* - a `?` written directly against a name or number: Studio binds it as `@p0`, which
|
|
46
|
+
* SQL Server reads as part of that name (`x?:` becomes the label `x@p0:`).
|
|
47
|
+
*
|
|
48
|
+
* Model-written SQL contains none of these.
|
|
49
|
+
*/
|
|
50
|
+
export declare function findAmbiguousTsqlText(sql: string, tokens: readonly SqlToken[]): string | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* The first place `tokens` starts a second T-SQL statement, or `undefined`.
|
|
53
|
+
* `tokens` holds no comments and no trailing `;`.
|
|
54
|
+
*/
|
|
55
|
+
export declare function findTsqlStatementBoundary(tokens: readonly SqlToken[]): TsqlStatementBoundary | undefined;
|
|
56
|
+
//# sourceMappingURL=tsql-batch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tsql-batch.d.ts","sourceRoot":"","sources":["../../src/sql/tsql-batch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAG3C,iDAAiD;AACjD,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,EAAE,QAAQ,CAAC;IAChB;;;OAGG;IACH,IAAI,EAAE,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;CACpC,CAAC;AAqCF;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,QAAQ,EAAE,GAAG,MAAM,GAAG,SAAS,CA6BlG;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,QAAQ,EAAE,GAAG,qBAAqB,GAAG,SAAS,CA8HxG"}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { SQLSERVER_RESERVED_WORDS } from "./reserved-words.js";
|
|
2
|
+
/**
|
|
3
|
+
* Reserved words after which an expression or a subquery follows, so a `(SELECT …`
|
|
4
|
+
* after one of them is a subquery. Each is reserved in T-SQL, so none is an alias.
|
|
5
|
+
*/
|
|
6
|
+
const EXPRESSION_KEYWORDS = new Set([
|
|
7
|
+
"select", "distinct", "all", "any", "some", "top", "percent",
|
|
8
|
+
"from", "join", "where", "on", "having", "by",
|
|
9
|
+
"and", "or", "not", "in", "exists", "like", "between", "escape",
|
|
10
|
+
"case", "when", "then", "else",
|
|
11
|
+
"union", "except", "intersect",
|
|
12
|
+
]);
|
|
13
|
+
/** Reserved words that are also function names, so `name((SELECT …))` is a call. */
|
|
14
|
+
const RESERVED_FUNCTIONS = new Set([
|
|
15
|
+
"coalesce", "nullif", "convert", "try_convert", "left", "right",
|
|
16
|
+
"contains", "containstable", "freetext", "freetexttable", "openxml", "tsequal",
|
|
17
|
+
]);
|
|
18
|
+
/**
|
|
19
|
+
* Punctuation an expression may follow: `(`, a comma, an operator, and `.` or `::`
|
|
20
|
+
* before a qualified function name.
|
|
21
|
+
*/
|
|
22
|
+
const EXPRESSION_PUNCTUATION = new Set(["(", ",", "=", "<", ">", "+", "-", "*", "/", "%", "&", "|", "^", "~", ".", "::"]);
|
|
23
|
+
const RESERVED = new Set(SQLSERVER_RESERVED_WORDS);
|
|
24
|
+
const SET_OPERATORS = new Set(["union", "except", "intersect"]);
|
|
25
|
+
/**
|
|
26
|
+
* A character allowed in T-SQL code outside strings and quoted names: printable ASCII
|
|
27
|
+
* except `\`, tab, line feed, carriage return, and Unicode letters, combining marks
|
|
28
|
+
* and decimal digits.
|
|
29
|
+
*/
|
|
30
|
+
const PLAIN_CODE = /[\t\n\r\x20-\x5b\x5d-\x7e\p{L}\p{M}\p{Nd}]/u;
|
|
31
|
+
const MONEY_SIGN = /^\p{Sc}$/u;
|
|
32
|
+
/**
|
|
33
|
+
* Code SQL Server may split into tokens differently from the lexer, described for an
|
|
34
|
+
* error message, or `undefined`. Text inside strings and quoted identifiers is not
|
|
35
|
+
* checked; `tokens` holds no comments. Fails closed on:
|
|
36
|
+
*
|
|
37
|
+
* - a character outside {@link PLAIN_CODE}: Unicode whitespace and format characters
|
|
38
|
+
* (U+00A0, U+200B, U+FEFF), symbols, currency signs other than `$`, `\` (a money
|
|
39
|
+
* sign to SQL Server) and ASCII control characters;
|
|
40
|
+
* - a money sign with no digits after it (`$ .5` is one money literal to SQL Server);
|
|
41
|
+
* - a number written directly against a word or variable (`1ESELECT`, `0xSELECT`);
|
|
42
|
+
* - a `?` written directly against a name or number: Studio binds it as `@p0`, which
|
|
43
|
+
* SQL Server reads as part of that name (`x?:` becomes the label `x@p0:`).
|
|
44
|
+
*
|
|
45
|
+
* Model-written SQL contains none of these.
|
|
46
|
+
*/
|
|
47
|
+
export function findAmbiguousTsqlText(sql, tokens) {
|
|
48
|
+
let from = 0;
|
|
49
|
+
const scanTo = (end) => {
|
|
50
|
+
for (const ch of sql.slice(from, end)) {
|
|
51
|
+
if (PLAIN_CODE.test(ch))
|
|
52
|
+
continue;
|
|
53
|
+
if (ch === "\\")
|
|
54
|
+
return "the character '\\'";
|
|
55
|
+
return `the character U+${ch.codePointAt(0).toString(16).toUpperCase().padStart(4, "0")}`;
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
};
|
|
59
|
+
for (const [i, t] of tokens.entries()) {
|
|
60
|
+
if (t.kind === "string" || t.kind === "quoted_identifier") {
|
|
61
|
+
const found = scanTo(t.start);
|
|
62
|
+
if (found)
|
|
63
|
+
return found;
|
|
64
|
+
from = t.end;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const prev = tokens[i - 1];
|
|
68
|
+
if (isPunct(t, "?") && prev?.end === t.start && prev.kind !== "punct" && prev.kind !== "string") {
|
|
69
|
+
return `'${prev.text}?', a parameter marker written against a name`;
|
|
70
|
+
}
|
|
71
|
+
if (t.kind !== "number")
|
|
72
|
+
continue;
|
|
73
|
+
if (MONEY_SIGN.test(t.text))
|
|
74
|
+
return `the money sign '${t.text}'`;
|
|
75
|
+
const next = tokens[i + 1];
|
|
76
|
+
if (next?.start === t.end && (next.kind === "word" || next.kind === "parameter")) {
|
|
77
|
+
return `'${t.text}${next.text}', a number written against a word`;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return scanTo(sql.length);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The first place `tokens` starts a second T-SQL statement, or `undefined`.
|
|
84
|
+
* `tokens` holds no comments and no trailing `;`.
|
|
85
|
+
*/
|
|
86
|
+
export function findTsqlStatementBoundary(tokens) {
|
|
87
|
+
/** Index of each `)` mapped to the index of the `(` it closes. */
|
|
88
|
+
const opener = new Map();
|
|
89
|
+
/** A top-level `ORDER BY` came earlier, so a later `OFFSET` is the keyword. */
|
|
90
|
+
let orderBy = false;
|
|
91
|
+
/** `tokens[i]` is `(` and a `SELECT` follows it, after any further `(`. */
|
|
92
|
+
const opensQuery = (i) => {
|
|
93
|
+
let j = i;
|
|
94
|
+
while (isPunct(tokens[j], "("))
|
|
95
|
+
j++;
|
|
96
|
+
return isWord(tokens[j], "select");
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* The word at `j` is a keyword an expression follows. Keywords SQL Server does not
|
|
100
|
+
* reserve count only in their clause; anywhere else they are aliases
|
|
101
|
+
* (`SELECT 1 offset (SELECT 2)` is two statements).
|
|
102
|
+
*/
|
|
103
|
+
const introducesExpression = (j) => {
|
|
104
|
+
const word = tokens[j].lower;
|
|
105
|
+
if (EXPRESSION_KEYWORDS.has(word))
|
|
106
|
+
return true;
|
|
107
|
+
const prev = tokens[j - 1];
|
|
108
|
+
switch (word) {
|
|
109
|
+
case "offset":
|
|
110
|
+
// After a sort item, not as one (`ORDER BY offset` sorts by a column).
|
|
111
|
+
return orderBy && !isWord(prev, "by") && !isPunct(prev, ",");
|
|
112
|
+
case "next":
|
|
113
|
+
case "first":
|
|
114
|
+
return isWord(prev, "fetch");
|
|
115
|
+
case "ties":
|
|
116
|
+
return isWord(prev, "with");
|
|
117
|
+
case "apply":
|
|
118
|
+
return isWord(prev, "cross") || isWord(prev, "outer");
|
|
119
|
+
case "zone":
|
|
120
|
+
return isWord(prev, "time") && isWord(tokens[j - 2], "at");
|
|
121
|
+
default:
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* An expression may start right after `tokens[j]`, so a `(SELECT …` there is a
|
|
127
|
+
* subquery. Fails closed: anything not listed (a value, a name, `}`, an unknown
|
|
128
|
+
* keyword) ends the statement, and a `(SELECT …` after it starts a second one.
|
|
129
|
+
*/
|
|
130
|
+
const expressionMayFollow = (j) => {
|
|
131
|
+
const t = tokens[j];
|
|
132
|
+
if (!t)
|
|
133
|
+
return true;
|
|
134
|
+
switch (t.kind) {
|
|
135
|
+
case "number":
|
|
136
|
+
case "string":
|
|
137
|
+
case "parameter":
|
|
138
|
+
// `TOP 5 (SELECT …)`: the count, then the first select-list item.
|
|
139
|
+
return isWord(tokens[j - 1], "top");
|
|
140
|
+
case "word":
|
|
141
|
+
return introducesExpression(j);
|
|
142
|
+
case "punct": {
|
|
143
|
+
if (t.text !== ")")
|
|
144
|
+
return EXPRESSION_PUNCTUATION.has(t.text);
|
|
145
|
+
const openIndex = opener.get(j);
|
|
146
|
+
// `TOP (5) (SELECT …)`.
|
|
147
|
+
return openIndex !== undefined && isWord(tokens[openIndex - 1], "top");
|
|
148
|
+
}
|
|
149
|
+
default:
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* `name((SELECT …))` where `name` is a function: SQL Server reads an unquoted `name(`
|
|
155
|
+
* as a call whenever `name` is not an alias, that is, when an expression may start
|
|
156
|
+
* before it. A quoted name is a call only when qualified (`[dbo].[fn](`); alone,
|
|
157
|
+
* `[name]` is a column.
|
|
158
|
+
*/
|
|
159
|
+
const opensCallArgument = (i) => {
|
|
160
|
+
if (!isPunct(tokens[i + 1], "("))
|
|
161
|
+
return false;
|
|
162
|
+
const name = tokens[i - 1];
|
|
163
|
+
const callable = (name?.kind === "quoted_identifier" && isPunct(tokens[i - 2], ".")) ||
|
|
164
|
+
(name?.kind === "word" && (!RESERVED.has(name.lower) || RESERVED_FUNCTIONS.has(name.lower)));
|
|
165
|
+
return callable && expressionMayFollow(i - 2);
|
|
166
|
+
};
|
|
167
|
+
const open = [];
|
|
168
|
+
let headSeen = false;
|
|
169
|
+
/** After a leading `WITH`, the main query that follows the CTE list is still to come. */
|
|
170
|
+
let mainPending = false;
|
|
171
|
+
for (const [i, t] of tokens.entries()) {
|
|
172
|
+
const topLevel = open.length === 0;
|
|
173
|
+
if (isPunct(t, "(")) {
|
|
174
|
+
if (topLevel && opensQuery(i)) {
|
|
175
|
+
const prev = tokens[i - 1];
|
|
176
|
+
if (mainPending && (isWord(prev, "as") || isWord(prev, "materialized"))) {
|
|
177
|
+
// A CTE body.
|
|
178
|
+
}
|
|
179
|
+
else if (mainPending && isPunct(prev, ")")) {
|
|
180
|
+
mainPending = false;
|
|
181
|
+
}
|
|
182
|
+
else if (!expressionMayFollow(i - 1) && !opensCallArgument(i)) {
|
|
183
|
+
return { token: t, kind: "query" };
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
open.push(i);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (isPunct(t, ")")) {
|
|
190
|
+
const openIndex = open.pop();
|
|
191
|
+
if (openIndex !== undefined)
|
|
192
|
+
opener.set(i, openIndex);
|
|
193
|
+
if (open.length === 0 && startsLabel(t, tokens[i + 1]))
|
|
194
|
+
return { token: t, kind: "label" };
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (!headSeen) {
|
|
198
|
+
headSeen = true;
|
|
199
|
+
mainPending = isWord(t, "with");
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (!topLevel)
|
|
203
|
+
continue;
|
|
204
|
+
if (startsLabel(t, tokens[i + 1]))
|
|
205
|
+
return { token: t, kind: "label" };
|
|
206
|
+
if (isWord(t, "by") && isWord(tokens[i - 1], "order"))
|
|
207
|
+
orderBy = true;
|
|
208
|
+
if (isWord(t, "select")) {
|
|
209
|
+
if (mainPending && isPunct(tokens[i - 1], ")")) {
|
|
210
|
+
mainPending = false;
|
|
211
|
+
}
|
|
212
|
+
else if (!followsSetOperator(tokens, i)) {
|
|
213
|
+
return { token: t, kind: "select" };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return undefined;
|
|
218
|
+
}
|
|
219
|
+
/** `UNION [ALL | DISTINCT]`, `EXCEPT` or `INTERSECT` right before `tokens[i]`. */
|
|
220
|
+
function followsSetOperator(tokens, i) {
|
|
221
|
+
let prev = tokens[i - 1];
|
|
222
|
+
if (isWord(prev, "all") || isWord(prev, "distinct"))
|
|
223
|
+
prev = tokens[i - 2];
|
|
224
|
+
return prev?.kind === "word" && SET_OPERATORS.has(prev.lower);
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* A `:` written directly against `t`, which T-SQL reads as ending a label (`name :` is a
|
|
228
|
+
* syntax error). Fails closed: any `t` counts except `(`, a comma or an operator, where
|
|
229
|
+
* an AskDB `:name` placeholder may follow, because SQL Server's label names include
|
|
230
|
+
* forms the lexer splits (`#@:`, `#1#:`). The lexer reads `l:SELECT` as `l` and a
|
|
231
|
+
* `:SELECT` parameter; `::` is one token and never a label.
|
|
232
|
+
*/
|
|
233
|
+
function startsLabel(t, next) {
|
|
234
|
+
if (next?.start !== t.end)
|
|
235
|
+
return false;
|
|
236
|
+
if (t.kind === "punct" && t.text !== "." && t.text !== "::" && EXPRESSION_PUNCTUATION.has(t.text))
|
|
237
|
+
return false;
|
|
238
|
+
return isPunct(next, ":") || (next.kind === "parameter" && next.text.startsWith(":"));
|
|
239
|
+
}
|
|
240
|
+
function isPunct(t, text) {
|
|
241
|
+
return t?.kind === "punct" && t.text === text;
|
|
242
|
+
}
|
|
243
|
+
function isWord(t, lower) {
|
|
244
|
+
return t?.kind === "word" && t.lower === lower;
|
|
245
|
+
}
|
|
246
|
+
//# sourceMappingURL=tsql-batch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tsql-batch.js","sourceRoot":"","sources":["../../src/sql/tsql-batch.ts"],"names":[],"mappings":"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"}
|
package/dist/sql/validate.d.ts
CHANGED
|
@@ -14,9 +14,17 @@ import { type DialectSpec } from "./dialect-spec.js";
|
|
|
14
14
|
* 3. `SQL_MULTI_STATEMENT` — any `;` other than a single trailing one.
|
|
15
15
|
* 4. `SQL_COMMENT` — any comment (`--`, `/* *\/`, and MySQL `#`).
|
|
16
16
|
* 5. `SQL_NOT_SELECT_OR_WITH` — the first token (after any `(`) is not `SELECT`/`WITH`.
|
|
17
|
-
* 6. `
|
|
17
|
+
* 6. `SQL_MULTI_STATEMENT`, SQL Server lexer only (so also for a dialect id that is no
|
|
18
|
+
* built-in engine) — a second statement written without `;`, which T-SQL doesn't
|
|
19
|
+
* need, or code SQL Server may split differently from the lexer (see `tsql-batch.ts`).
|
|
20
|
+
* 7. `SQL_LOCKING_READ` — a locking read, at any depth: `FOR UPDATE`, `FOR NO KEY UPDATE`,
|
|
21
|
+
* `FOR SHARE`, `FOR KEY SHARE` or `LOCK IN SHARE MODE` on every lexer, and on the SQL Server
|
|
22
|
+
* lexer a lock hint such as `UPDLOCK` or `HOLDLOCK` unquoted anywhere, or quoted alone in
|
|
23
|
+
* parentheses right after a name, at any depth and with trailing spaces, and any such name
|
|
24
|
+
* that isn't printable ASCII, which SQL Server may read as a hint (#319).
|
|
25
|
+
* 8. `SQL_FORBIDDEN_KEYWORD` — an unquoted keyword from the shared denylist or the
|
|
18
26
|
* dialect's `extraForbiddenKeywords`.
|
|
19
|
-
*
|
|
27
|
+
* 9. `SQL_FORBIDDEN_FUNCTION` — a call (`name(`) to a function in the dialect's
|
|
20
28
|
* `blockedFunctions` (side-effecting or file/network/sleep functions).
|
|
21
29
|
*
|
|
22
30
|
* Then the dialect's `extraValidate` runs on the statement without its trailing `;`.
|
|
@@ -32,9 +40,16 @@ export declare function validateSelectSql(dialect: DialectSpec, sql: string): st
|
|
|
32
40
|
/**
|
|
33
41
|
* The words `validateSelectSql` rejects unquoted for a dialect, lowercase: the shared denylist
|
|
34
42
|
* and the spec's `extraForbiddenKeywords`, plus every built-in spec's for an id that is no
|
|
35
|
-
* built-in engine family
|
|
43
|
+
* built-in engine family, and T-SQL's lock hints wherever the SQL Server lexer runs. The
|
|
44
|
+
* NL→SQL prompt quotes names in this set.
|
|
36
45
|
*/
|
|
37
46
|
export declare function keywordsRejectedUnquoted(dialect: Pick<DialectSpec, "id" | "backslashEscapes" | "extraForbiddenKeywords">): Set<string>;
|
|
47
|
+
/**
|
|
48
|
+
* Whether `validateSelectSql` runs the SQL Server lexer, and so the T-SQL hint checks, for a
|
|
49
|
+
* dialect: `sqlserver`, and an id that is no built-in engine family, which is checked every
|
|
50
|
+
* engine's way. The NL→SQL prompt follows it (#319).
|
|
51
|
+
*/
|
|
52
|
+
export declare function checkedAsTsql(dialect: Pick<DialectSpec, "id" | "backslashEscapes">): boolean;
|
|
38
53
|
/** Explanation of guardrails satisfied by a string already passing {@link validateSelectSql}. */
|
|
39
54
|
export type SelectGuardrailExplain = {
|
|
40
55
|
statementKind: "select" | "with";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/sql/validate.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/sql/validate.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAyCxE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAyB3E;AAsHD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,kBAAkB,GAAG,wBAAwB,CAAC,GAC/E,GAAG,CAAC,MAAM,CAAC,CAIb;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,kBAAkB,CAAC,GAAG,OAAO,CAG5F;AAqHD,iGAAiG;AACjG,MAAM,MAAM,sBAAsB,GAAG;IACnC,aAAa,EAAE,QAAQ,GAAG,MAAM,CAAC;IACjC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,6FAA6F;AAC7F,wBAAgB,+BAA+B,CAAC,YAAY,EAAE,MAAM,GAAG,sBAAsB,CAkB5F"}
|