@kuindji/typed-sql 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +227 -0
- package/dist/builder/assemble.d.ts +13 -0
- package/dist/builder/assemble.d.ts.map +1 -0
- package/dist/builder/assemble.js +86 -0
- package/dist/builder/assemble.js.map +1 -0
- package/dist/builder/condition-tree.d.ts +27 -0
- package/dist/builder/condition-tree.d.ts.map +1 -0
- package/dist/builder/condition-tree.js +91 -0
- package/dist/builder/condition-tree.js.map +1 -0
- package/dist/builder/conditional-sql.d.ts +80 -0
- package/dist/builder/conditional-sql.d.ts.map +1 -0
- package/dist/builder/conditional-sql.js +88 -0
- package/dist/builder/conditional-sql.js.map +1 -0
- package/dist/builder/db.d.ts +76 -0
- package/dist/builder/db.d.ts.map +1 -0
- package/dist/builder/db.js +12 -0
- package/dist/builder/db.js.map +1 -0
- package/dist/builder/delete.d.ts +39 -0
- package/dist/builder/delete.d.ts.map +1 -0
- package/dist/builder/delete.js +33 -0
- package/dist/builder/delete.js.map +1 -0
- package/dist/builder/extract-params.d.ts +97 -0
- package/dist/builder/extract-params.d.ts.map +1 -0
- package/dist/builder/extract-params.js +2 -0
- package/dist/builder/extract-params.js.map +1 -0
- package/dist/builder/index.d.ts +23 -0
- package/dist/builder/index.d.ts.map +1 -0
- package/dist/builder/index.js +14 -0
- package/dist/builder/index.js.map +1 -0
- package/dist/builder/insert.d.ts +51 -0
- package/dist/builder/insert.d.ts.map +1 -0
- package/dist/builder/insert.js +39 -0
- package/dist/builder/insert.js.map +1 -0
- package/dist/builder/mutate.d.ts +28 -0
- package/dist/builder/mutate.d.ts.map +1 -0
- package/dist/builder/mutate.js +17 -0
- package/dist/builder/mutate.js.map +1 -0
- package/dist/builder/params.d.ts +22 -0
- package/dist/builder/params.d.ts.map +1 -0
- package/dist/builder/params.js +65 -0
- package/dist/builder/params.js.map +1 -0
- package/dist/builder/return-type.d.ts +39 -0
- package/dist/builder/return-type.d.ts.map +1 -0
- package/dist/builder/return-type.js +2 -0
- package/dist/builder/return-type.js.map +1 -0
- package/dist/builder/scanner.d.ts +49 -0
- package/dist/builder/scanner.d.ts.map +1 -0
- package/dist/builder/scanner.js +240 -0
- package/dist/builder/scanner.js.map +1 -0
- package/dist/builder/select.d.ts +76 -0
- package/dist/builder/select.d.ts.map +1 -0
- package/dist/builder/select.js +240 -0
- package/dist/builder/select.js.map +1 -0
- package/dist/builder/sql-tag.d.ts +319 -0
- package/dist/builder/sql-tag.d.ts.map +1 -0
- package/dist/builder/sql-tag.js +3 -0
- package/dist/builder/sql-tag.js.map +1 -0
- package/dist/builder/sql.d.ts +17 -0
- package/dist/builder/sql.d.ts.map +1 -0
- package/dist/builder/sql.js +36 -0
- package/dist/builder/sql.js.map +1 -0
- package/dist/builder/state.d.ts +53 -0
- package/dist/builder/state.d.ts.map +1 -0
- package/dist/builder/state.js +18 -0
- package/dist/builder/state.js.map +1 -0
- package/dist/builder/update.d.ts +60 -0
- package/dist/builder/update.d.ts.map +1 -0
- package/dist/builder/update.js +40 -0
- package/dist/builder/update.js.map +1 -0
- package/dist/builder/write-assemble.d.ts +5 -0
- package/dist/builder/write-assemble.d.ts.map +1 -0
- package/dist/builder/write-assemble.js +57 -0
- package/dist/builder/write-assemble.js.map +1 -0
- package/dist/builder/write-state.d.ts +39 -0
- package/dist/builder/write-state.d.ts.map +1 -0
- package/dist/builder/write-state.js +6 -0
- package/dist/builder/write-state.js.map +1 -0
- package/dist/builder/write-tag.d.ts +91 -0
- package/dist/builder/write-tag.d.ts.map +1 -0
- package/dist/builder/write-tag.js +2 -0
- package/dist/builder/write-tag.js.map +1 -0
- package/dist/columns.d.ts +33 -0
- package/dist/columns.d.ts.map +1 -0
- package/dist/columns.js +2 -0
- package/dist/columns.js.map +1 -0
- package/dist/expressions.d.ts +71 -0
- package/dist/expressions.d.ts.map +1 -0
- package/dist/expressions.js +2 -0
- package/dist/expressions.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/parsing/extract.d.ts +47 -0
- package/dist/parsing/extract.d.ts.map +1 -0
- package/dist/parsing/extract.js +2 -0
- package/dist/parsing/extract.js.map +1 -0
- package/dist/parsing/normalize.d.ts +44 -0
- package/dist/parsing/normalize.d.ts.map +1 -0
- package/dist/parsing/normalize.js +2 -0
- package/dist/parsing/normalize.js.map +1 -0
- package/dist/parsing/pg-literals.d.ts +37 -0
- package/dist/parsing/pg-literals.d.ts.map +1 -0
- package/dist/parsing/pg-literals.js +2 -0
- package/dist/parsing/pg-literals.js.map +1 -0
- package/dist/parsing/split.d.ts +100 -0
- package/dist/parsing/split.d.ts.map +1 -0
- package/dist/parsing/split.js +2 -0
- package/dist/parsing/split.js.map +1 -0
- package/dist/parsing/string-utils.d.ts +29 -0
- package/dist/parsing/string-utils.d.ts.map +1 -0
- package/dist/parsing/string-utils.js +2 -0
- package/dist/parsing/string-utils.js.map +1 -0
- package/dist/parsing/tokenize.d.ts +27 -0
- package/dist/parsing/tokenize.d.ts.map +1 -0
- package/dist/parsing/tokenize.js +2 -0
- package/dist/parsing/tokenize.js.map +1 -0
- package/dist/parsing.d.ts +7 -0
- package/dist/parsing.d.ts.map +1 -0
- package/dist/parsing.js +9 -0
- package/dist/parsing.js.map +1 -0
- package/dist/partial.d.ts +30 -0
- package/dist/partial.d.ts.map +1 -0
- package/dist/partial.js +10 -0
- package/dist/partial.js.map +1 -0
- package/dist/schema.d.ts +28 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +2 -0
- package/dist/schema.js.map +1 -0
- package/dist/tables.d.ts +34 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +2 -0
- package/dist/tables.js.map +1 -0
- package/dist/utils.d.ts +13 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +3 -0
- package/dist/utils.js.map +1 -0
- package/dist/validation/cte.d.ts +54 -0
- package/dist/validation/cte.d.ts.map +1 -0
- package/dist/validation/cte.js +2 -0
- package/dist/validation/cte.js.map +1 -0
- package/dist/validation/dispatch.d.ts +31 -0
- package/dist/validation/dispatch.d.ts.map +1 -0
- package/dist/validation/dispatch.js +2 -0
- package/dist/validation/dispatch.js.map +1 -0
- package/dist/validation/joins.d.ts +16 -0
- package/dist/validation/joins.d.ts.map +1 -0
- package/dist/validation/joins.js +2 -0
- package/dist/validation/joins.js.map +1 -0
- package/dist/validation/return-derived.d.ts +67 -0
- package/dist/validation/return-derived.d.ts.map +1 -0
- package/dist/validation/return-derived.js +5 -0
- package/dist/validation/return-derived.js.map +1 -0
- package/dist/validation/return-types.d.ts +41 -0
- package/dist/validation/return-types.d.ts.map +1 -0
- package/dist/validation/return-types.js +2 -0
- package/dist/validation/return-types.js.map +1 -0
- package/dist/validation/validate-columns.d.ts +63 -0
- package/dist/validation/validate-columns.d.ts.map +1 -0
- package/dist/validation/validate-columns.js +2 -0
- package/dist/validation/validate-columns.js.map +1 -0
- package/dist/validation.d.ts +7 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +9 -0
- package/dist/validation.js.map +1 -0
- package/package.json +64 -0
- package/src/builder/assemble.ts +100 -0
- package/src/builder/condition-tree.ts +162 -0
- package/src/builder/conditional-sql.ts +325 -0
- package/src/builder/db.ts +281 -0
- package/src/builder/delete.ts +57 -0
- package/src/builder/extract-params.ts +507 -0
- package/src/builder/index.ts +58 -0
- package/src/builder/insert.ts +75 -0
- package/src/builder/mutate.ts +55 -0
- package/src/builder/params.ts +95 -0
- package/src/builder/return-type.ts +66 -0
- package/src/builder/scanner.ts +254 -0
- package/src/builder/select.ts +470 -0
- package/src/builder/sql-tag.ts +422 -0
- package/src/builder/sql.ts +51 -0
- package/src/builder/state.ts +55 -0
- package/src/builder/update.ts +77 -0
- package/src/builder/write-assemble.ts +52 -0
- package/src/builder/write-state.ts +43 -0
- package/src/builder/write-tag.ts +119 -0
- package/src/columns.ts +336 -0
- package/src/expressions.ts +745 -0
- package/src/index.ts +81 -0
- package/src/parsing/extract.ts +260 -0
- package/src/parsing/normalize.ts +243 -0
- package/src/parsing/pg-literals.ts +289 -0
- package/src/parsing/split.ts +288 -0
- package/src/parsing/string-utils.ts +172 -0
- package/src/parsing/tokenize.ts +321 -0
- package/src/parsing.ts +8 -0
- package/src/partial.ts +241 -0
- package/src/schema.ts +130 -0
- package/src/tables.ts +278 -0
- package/src/utils.ts +43 -0
- package/src/validation/cte.ts +198 -0
- package/src/validation/dispatch.ts +312 -0
- package/src/validation/joins.ts +198 -0
- package/src/validation/return-derived.ts +253 -0
- package/src/validation/return-types.ts +271 -0
- package/src/validation/validate-columns.ts +489 -0
- package/src/validation.ts +8 -0
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
// Table/column existence + qualified/unqualified ref + scope-shape validation.
|
|
2
|
+
import type { AliasesInQuery, InsertTargetTable, TableKeyValid, TablesInQuery, UpdateTargetTable } from "../tables.js";
|
|
3
|
+
import type { AllTrue, And, StartsWith } from "../utils.js";
|
|
4
|
+
import type { CleanIdent, DQuoteSpaceSentinel, ExceedsLengthBudget, ExtractAliasResult, ExtractBefore, ExtractConflictColumns, ExtractConflictUpdateExcludedCols, ExtractConflictUpdateSetColumns, ExtractInsertColumns, ExtractLastWhere, ExtractReturningList, ExtractSelectList, ExtractUpdateSetColumns, ReplaceAll, SplitSelectList, StripSubqueries, TokenizeLoose, Trim } from "../parsing.js";
|
|
5
|
+
import type { ColumnRefValidLooseWith, IsSimpleRefPart, QualifiedColumnRefs, ResolveAlias, TableKeysByName, UnqualifiedColumnRefs, UnqualifiedColumnValid } from "../columns.js";
|
|
6
|
+
import type { ColumnsExistInTable, RefScanBeforeOrderBy, RefScanOrderBy, RefScanSegment, SelectAliasesInQuery, SelectAliasSet } from "./return-types.js";
|
|
7
|
+
import type { CteRow, SingleCteMatch } from "./cte.js";
|
|
8
|
+
import type { DatabaseSchema } from "../schema.js";
|
|
9
|
+
import type { DerivedRenamedRow, DerivedTableMatch } from "./return-derived.js";
|
|
10
|
+
import type { ExprsValidList } from "../expressions.js";
|
|
11
|
+
import type { HasReturning, QueryKind, ValidateSQLNormalized } from "./dispatch.js";
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// CTE / derived-table VALIDATION (scope surface).
|
|
15
|
+
//
|
|
16
|
+
// A CTE or derived table exposes ONLY its projected output row to the outer
|
|
17
|
+
// query — never the base tables in its body. Result inference already computes
|
|
18
|
+
// that row (`CteRow` / `DerivedSubRow`, including `t(a,b)` renaming); validation
|
|
19
|
+
// reuses it. A single-CTE / leading-derived SELECT is valid when:
|
|
20
|
+
// 1. the body is itself a valid query, AND
|
|
21
|
+
// 2. every outer projection references a column the body actually exposes
|
|
22
|
+
// (qualified by the CTE/derived relation name, or unqualified).
|
|
23
|
+
// This both ACCEPTS the projected/renamed reads and REJECTS reads of a column
|
|
24
|
+
// the body did not project (or whose name a `t(cols)` list renamed away).
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
export type ValidateCteShape<N extends string, S extends DatabaseSchema> =
|
|
28
|
+
SingleCteMatch<N> extends {
|
|
29
|
+
body: infer Body extends string;
|
|
30
|
+
outer: infer Outer extends string;
|
|
31
|
+
name: infer Name extends string;
|
|
32
|
+
cols: infer Cols extends string[];
|
|
33
|
+
}
|
|
34
|
+
? CteRow<Body, Cols, S> extends infer Row
|
|
35
|
+
? And<
|
|
36
|
+
ValidateSQLNormalized<Body, S>,
|
|
37
|
+
OuterProjectionInRow<SplitSelectList<ExtractSelectList<Outer>>, Name, Row, S>,
|
|
38
|
+
// Outer WHERE refs also see only the CTE's exposed row.
|
|
39
|
+
OuterWhereRefsInRow<Outer, Name, Row, S>,
|
|
40
|
+
// ORDER BY / GROUP BY / HAVING are outer clauses too.
|
|
41
|
+
OuterTailClauseRefsInRow<Outer, Name, Row, S>,
|
|
42
|
+
true
|
|
43
|
+
>
|
|
44
|
+
: true
|
|
45
|
+
: true;
|
|
46
|
+
|
|
47
|
+
export type ValidateDerivedShape<N extends string, S extends DatabaseSchema> =
|
|
48
|
+
DerivedTableMatch<N> extends { body: infer Body extends string; alias: infer DAlias extends string; cols: infer Cols extends string[] }
|
|
49
|
+
? DerivedRenamedRow<Body, Cols, S> extends infer Row
|
|
50
|
+
? And<
|
|
51
|
+
ValidateSQLNormalized<Body, S>,
|
|
52
|
+
OuterProjectionInRow<SplitSelectList<ExtractSelectList<N>>, DAlias, Row, S>,
|
|
53
|
+
// Outer WHERE refs see only the derived row; the body (and its own
|
|
54
|
+
// WHERE) is stripped first and validated by the recursive body check.
|
|
55
|
+
OuterWhereRefsInRow<N, DAlias, Row, S>,
|
|
56
|
+
// ORDER BY / GROUP BY / HAVING are outer clauses too.
|
|
57
|
+
OuterTailClauseRefsInRow<N, DAlias, Row, S>,
|
|
58
|
+
true
|
|
59
|
+
>
|
|
60
|
+
: true
|
|
61
|
+
: true;
|
|
62
|
+
|
|
63
|
+
// Every projected expression of the outer query must reference a column the
|
|
64
|
+
// derived/CTE relation exposes. Functions, literals, `*`, and `<rel>.*` are
|
|
65
|
+
// left unchecked (always valid); only plain column reads are constrained.
|
|
66
|
+
export type OuterProjectionInRow<Exprs extends string[], Name extends string, Row, S extends DatabaseSchema> =
|
|
67
|
+
AllTrue<
|
|
68
|
+
Exprs[number] extends infer E
|
|
69
|
+
? E extends string
|
|
70
|
+
? OuterProjInRow<E, Name, Row, S>
|
|
71
|
+
: true
|
|
72
|
+
: true
|
|
73
|
+
>;
|
|
74
|
+
|
|
75
|
+
export type OuterProjInRow<E extends string, Name extends string, Row, S extends DatabaseSchema> =
|
|
76
|
+
ExtractAliasResult<E> extends { expr: infer Raw extends string }
|
|
77
|
+
? ProjRefInRow<Trim<Raw>, Name, Row, S>
|
|
78
|
+
: ProjRefInRow<Trim<E>, Name, Row, S>;
|
|
79
|
+
|
|
80
|
+
export type ProjRefInRow<Raw extends string, Name extends string, Row, S extends DatabaseSchema> =
|
|
81
|
+
Raw extends "*" ? true :
|
|
82
|
+
Raw extends `${Name}.*` ? true :
|
|
83
|
+
Raw extends `${number}` ? true :
|
|
84
|
+
Raw extends `'${string}` ? true :
|
|
85
|
+
Raw extends `"${string}` ? true :
|
|
86
|
+
// A call / parenthesised expression (`upper(status)`, `upper(dt.status)`):
|
|
87
|
+
// the wrapper must not smuggle in a column the relation never exposed, so
|
|
88
|
+
// validate the refs INSIDE the parens against the exposed row rather than
|
|
89
|
+
// accepting the whole expression as a non-simple ref.
|
|
90
|
+
Raw extends `${string}(${infer Inner})${string}`
|
|
91
|
+
? SegRefsInRow<Inner, Name, Row, S>
|
|
92
|
+
: Raw extends `${infer Q}.${infer Col}`
|
|
93
|
+
? IsSimpleRefPart<Q> extends true
|
|
94
|
+
? IsSimpleRefPart<Col> extends true
|
|
95
|
+
? CleanIdent<Q> extends Name
|
|
96
|
+
? KeyInRow<CleanIdent<Col>, Row>
|
|
97
|
+
: false
|
|
98
|
+
: true
|
|
99
|
+
: true
|
|
100
|
+
: IsSimpleRefPart<Raw> extends true
|
|
101
|
+
? KeyInRow<CleanIdent<Raw>, Row>
|
|
102
|
+
: true;
|
|
103
|
+
|
|
104
|
+
export type KeyInRow<K extends string, Row> = [K] extends [keyof Row] ? true : false;
|
|
105
|
+
|
|
106
|
+
// Validate every column-reference candidate in an arbitrary text segment (an
|
|
107
|
+
// outer WHERE predicate, or a function call's argument list) against the
|
|
108
|
+
// CTE/derived relation's exposed `Row`. Reuses the same token walkers the core
|
|
109
|
+
// validator uses to surface refs, then checks each via `ProjRefInRow` (qualifier
|
|
110
|
+
// must equal the relation `Name`, column must be a key of `Row`). `Tables`/
|
|
111
|
+
// `Aliases` are `never`: the walkers only consult them to EXCLUDE table/alias
|
|
112
|
+
// tokens, and here the relation name is already excluded by its `from`/`join`/`)`
|
|
113
|
+
// predecessor and output aliases by the walker's `Prev extends "as"` guard. An
|
|
114
|
+
// empty/whitespace segment yields no refs → `true` (no-op).
|
|
115
|
+
export type SegRefsInRow<Seg extends string, Name extends string, Row, S extends DatabaseSchema> =
|
|
116
|
+
Trim<Seg> extends ""
|
|
117
|
+
? true
|
|
118
|
+
: TokenizeLoose<Seg> extends infer Toks extends string[]
|
|
119
|
+
? And<
|
|
120
|
+
AllTrue<
|
|
121
|
+
QualifiedColumnRefs<Toks, S, never, never> extends infer R
|
|
122
|
+
? R extends string ? ProjRefInRow<R, Name, Row, S> : true
|
|
123
|
+
: true
|
|
124
|
+
>,
|
|
125
|
+
AllTrue<
|
|
126
|
+
UnqualifiedColumnRefs<Toks, S, never, never> extends infer R
|
|
127
|
+
? R extends string ? ProjRefInRow<R, Name, Row, S> : true
|
|
128
|
+
: true
|
|
129
|
+
>,
|
|
130
|
+
true,
|
|
131
|
+
true
|
|
132
|
+
>
|
|
133
|
+
: true;
|
|
134
|
+
|
|
135
|
+
// The outer query's WHERE predicate, scoped to the CTE/derived relation's exposed
|
|
136
|
+
// row. Strip subquery bodies FIRST so the derived/CTE body's own WHERE is never
|
|
137
|
+
// scanned here (it is validated recursively), then scan ONLY when a real outer
|
|
138
|
+
// WHERE survives — `ExtractLastWhere` returns the whole string when there is no
|
|
139
|
+
// ` where `, so the explicit guard keeps the no-WHERE case a true no-op.
|
|
140
|
+
export type OuterWhereRefsInRow<OuterText extends string, Name extends string, Row, S extends DatabaseSchema> =
|
|
141
|
+
StripSubqueries<OuterText> extends infer Stripped extends string
|
|
142
|
+
? Stripped extends `${string} where ${string}`
|
|
143
|
+
? SegRefsInRow<ExtractLastWhere<Stripped>, Name, Row, S>
|
|
144
|
+
: true
|
|
145
|
+
: true;
|
|
146
|
+
|
|
147
|
+
// ORDER BY / GROUP BY / HAVING are outer clauses with the same exposed-row scope
|
|
148
|
+
// as WHERE: they may read only the columns the CTE/derived relation projected,
|
|
149
|
+
// never an unprojected base-table column from its body. Subquery bodies are
|
|
150
|
+
// stripped first (a derived body's own trailing clauses live inside its parens),
|
|
151
|
+
// then each clause's expression segment is scanned against the exposed `Row`.
|
|
152
|
+
// Output aliases from the outer SELECT list are blessed — Postgres permits an
|
|
153
|
+
// output column name in GROUP BY / ORDER BY — so referencing one is never a
|
|
154
|
+
// false rejection (HAVING is treated leniently the same way).
|
|
155
|
+
export type OuterTailClauseRefsInRow<OuterText extends string, Name extends string, Row, S extends DatabaseSchema> =
|
|
156
|
+
StripSubqueries<OuterText> extends infer Stripped extends string
|
|
157
|
+
? SelectAliasesInQuery<Stripped> extends infer SelAliases extends string
|
|
158
|
+
? And<
|
|
159
|
+
SegRefsInRowWithAliases<ExtractGroupByExpr<Stripped>, Name, Row, S, SelAliases>,
|
|
160
|
+
SegRefsInRowWithAliases<ExtractHavingExpr<Stripped>, Name, Row, S, SelAliases>,
|
|
161
|
+
SegRefsInRowWithAliases<ExtractOrderByExpr<Stripped>, Name, Row, S, SelAliases>,
|
|
162
|
+
true,
|
|
163
|
+
true
|
|
164
|
+
>
|
|
165
|
+
: true
|
|
166
|
+
: true;
|
|
167
|
+
|
|
168
|
+
// Trailing-clause expression extractors. Each yields the clause's expression
|
|
169
|
+
// text bounded by the clauses that may follow it (clause order is
|
|
170
|
+
// GROUP BY → HAVING → ORDER BY → LIMIT/OFFSET/UNION), or "" when absent.
|
|
171
|
+
type StopAtLimitTail<S extends string> =
|
|
172
|
+
ExtractBefore<ExtractBefore<ExtractBefore<S, " limit ">, " offset ">, " union ">;
|
|
173
|
+
|
|
174
|
+
type ExtractGroupByExpr<S extends string> =
|
|
175
|
+
S extends `${string} group by ${infer R}`
|
|
176
|
+
? StopAtLimitTail<ExtractBefore<ExtractBefore<R, " having ">, " order by ">>
|
|
177
|
+
: "";
|
|
178
|
+
|
|
179
|
+
type ExtractHavingExpr<S extends string> =
|
|
180
|
+
S extends `${string} having ${infer R}`
|
|
181
|
+
? StopAtLimitTail<ExtractBefore<R, " order by ">>
|
|
182
|
+
: "";
|
|
183
|
+
|
|
184
|
+
type ExtractOrderByExpr<S extends string> =
|
|
185
|
+
S extends `${string} order by ${infer R}`
|
|
186
|
+
? StopAtLimitTail<R>
|
|
187
|
+
: "";
|
|
188
|
+
|
|
189
|
+
// As `SegRefsInRow`, but additionally blesses a set of outer SELECT-list output
|
|
190
|
+
// aliases (an unqualified ref equal to one is accepted without consulting `Row`).
|
|
191
|
+
export type SegRefsInRowWithAliases<Seg extends string, Name extends string, Row, S extends DatabaseSchema, SelAliases extends string> =
|
|
192
|
+
Trim<Seg> extends ""
|
|
193
|
+
? true
|
|
194
|
+
: TokenizeLoose<Seg> extends infer Toks extends string[]
|
|
195
|
+
? And<
|
|
196
|
+
AllTrue<
|
|
197
|
+
QualifiedColumnRefs<Toks, S, never, never> extends infer R
|
|
198
|
+
? R extends string ? RefInRowOrAlias<R, Name, Row, S, SelAliases> : true
|
|
199
|
+
: true
|
|
200
|
+
>,
|
|
201
|
+
AllTrue<
|
|
202
|
+
UnqualifiedColumnRefs<Toks, S, never, never> extends infer R
|
|
203
|
+
? R extends string ? RefInRowOrAlias<R, Name, Row, S, SelAliases> : true
|
|
204
|
+
: true
|
|
205
|
+
>,
|
|
206
|
+
true,
|
|
207
|
+
true
|
|
208
|
+
>
|
|
209
|
+
: true;
|
|
210
|
+
|
|
211
|
+
export type RefInRowOrAlias<R extends string, Name extends string, Row, S extends DatabaseSchema, SelAliases extends string> =
|
|
212
|
+
[SelAliases] extends [never]
|
|
213
|
+
? ProjRefInRow<R, Name, Row, S>
|
|
214
|
+
: CleanIdent<R> extends SelAliases
|
|
215
|
+
? true
|
|
216
|
+
: ProjRefInRow<R, Name, Row, S>;
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
// Table and alias extraction
|
|
220
|
+
|
|
221
|
+
export type AllTablesValid<N extends string, S extends DatabaseSchema> =
|
|
222
|
+
TablesInQuery<N, S> extends infer Tables extends string
|
|
223
|
+
? AllTablesValidFor<Tables, S>
|
|
224
|
+
: true;
|
|
225
|
+
|
|
226
|
+
export type AllTablesValidFor<Tables extends string, S extends DatabaseSchema> =
|
|
227
|
+
AllTrue<Tables extends string ? TableKeyValid<Tables, S> : true>;
|
|
228
|
+
|
|
229
|
+
// Column validation
|
|
230
|
+
|
|
231
|
+
export type AllColumnsValid<N extends string, S extends DatabaseSchema> =
|
|
232
|
+
TablesInQuery<N, S> extends infer Tables extends string
|
|
233
|
+
? AliasesInQuery<N, S> extends infer Aliases extends string
|
|
234
|
+
? TokenizeLoose<RefScanSegment<N>> extends infer LooseTokens extends string[]
|
|
235
|
+
? AllColumnsValidFor<N, S, Tables, Aliases, LooseTokens>
|
|
236
|
+
: false
|
|
237
|
+
: false
|
|
238
|
+
: false;
|
|
239
|
+
|
|
240
|
+
export type AllColumnsValidFor<
|
|
241
|
+
N extends string,
|
|
242
|
+
S extends DatabaseSchema,
|
|
243
|
+
Tables extends string,
|
|
244
|
+
Aliases extends string,
|
|
245
|
+
LooseTokens extends string[]
|
|
246
|
+
> = SelectAliasSet<N> extends infer SelectAliases extends string
|
|
247
|
+
? QueryKind<N> extends "update"
|
|
248
|
+
// A normal (non-high-complexity) UPDATE has no subquery/CASE SET, so its
|
|
249
|
+
// SET-RHS and top-level WHERE column refs are safe to scan the same way a
|
|
250
|
+
// SELECT's are. This catches invalid columns hidden in `SET x = bogus` or
|
|
251
|
+
// `WHERE bogus = 1` that the SET-target-only check (`ColumnsValidInUpdate`)
|
|
252
|
+
// misses. There are no SELECT-list aliases in an UPDATE, so `never`.
|
|
253
|
+
? And<
|
|
254
|
+
ColumnsValidInUpdate<N, S>,
|
|
255
|
+
ColumnsValidInInsert<N, S>,
|
|
256
|
+
ColumnsValidInSelectOrReturningFor<N, S, Tables, Aliases>,
|
|
257
|
+
QualifiedColumnRefsValidFor<N, S, Tables, Aliases, LooseTokens>,
|
|
258
|
+
UnqualifiedColumnRefsValidFor<N, S, Tables, Aliases, LooseTokens, never>
|
|
259
|
+
>
|
|
260
|
+
: And<
|
|
261
|
+
ColumnsValidInSelectOrReturningFor<N, S, Tables, Aliases>,
|
|
262
|
+
ColumnsValidInInsert<N, S>,
|
|
263
|
+
ColumnsValidInUpdate<N, S>,
|
|
264
|
+
QualifiedColumnRefsValidFor<N, S, Tables, Aliases, LooseTokens>,
|
|
265
|
+
// A SELECT-list alias is only resolvable in ORDER BY — NOT in
|
|
266
|
+
// WHERE/GROUP/HAVING. So the unqualified ref-scan blesses the alias
|
|
267
|
+
// set ONLY for the ORDER BY token slice; the FROM..(pre-order-by)
|
|
268
|
+
// slice (WHERE/GROUP/HAVING/JOIN-ON) is validated against real
|
|
269
|
+
// columns with `never` aliases. When there is no ORDER BY this is
|
|
270
|
+
// equivalent to the old single-pass `never`-alias scan.
|
|
271
|
+
SelectUnqualifiedRefsScoped<N, S, Tables, Aliases, SelectAliases>
|
|
272
|
+
>
|
|
273
|
+
: false;
|
|
274
|
+
|
|
275
|
+
// Validate the SELECT's unqualified column refs with ORDER-BY-scoped alias
|
|
276
|
+
// resolution. The pre-ORDER-BY ref segment (WHERE/GROUP/HAVING/ON) is validated
|
|
277
|
+
// against real columns only; the ORDER-BY segment additionally accepts the
|
|
278
|
+
// SELECT-list output aliases.
|
|
279
|
+
export type SelectUnqualifiedRefsScoped<
|
|
280
|
+
N extends string,
|
|
281
|
+
S extends DatabaseSchema,
|
|
282
|
+
Tables extends string,
|
|
283
|
+
Aliases extends string,
|
|
284
|
+
SelectAliases extends string
|
|
285
|
+
> =
|
|
286
|
+
[SelectAliases] extends [never]
|
|
287
|
+
? UnqualifiedColumnRefsValidFor<N, S, Tables, Aliases, TokenizeLoose<RefScanSegment<N>>, never>
|
|
288
|
+
: And<
|
|
289
|
+
UnqualifiedColumnRefsValidFor<N, S, Tables, Aliases, TokenizeLoose<RefScanBeforeOrderBy<N>>, never>,
|
|
290
|
+
UnqualifiedColumnRefsValidFor<N, S, Tables, Aliases, TokenizeLoose<RefScanOrderBy<N>>, SelectAliases>,
|
|
291
|
+
true,
|
|
292
|
+
true,
|
|
293
|
+
true
|
|
294
|
+
>;
|
|
295
|
+
|
|
296
|
+
export type ColumnsValidInSelectOrReturning<N extends string, S extends DatabaseSchema> =
|
|
297
|
+
TablesInQuery<N, S> extends infer Tables extends string
|
|
298
|
+
? AliasesInQuery<N, S> extends infer Aliases extends string
|
|
299
|
+
? ColumnsValidInSelectOrReturningFor<N, S, Tables, Aliases>
|
|
300
|
+
: true
|
|
301
|
+
: true;
|
|
302
|
+
|
|
303
|
+
export type ColumnsValidInSelectOrReturningFor<
|
|
304
|
+
N extends string,
|
|
305
|
+
S extends DatabaseSchema,
|
|
306
|
+
Tables extends string,
|
|
307
|
+
Aliases extends string
|
|
308
|
+
> =
|
|
309
|
+
HasReturning<N> extends true
|
|
310
|
+
? [Tables] extends [never]
|
|
311
|
+
? true
|
|
312
|
+
: ExprsValidList<SplitSelectList<ReplaceAll<ExtractReturningList<N>, DQuoteSpaceSentinel, " ">>, Tables, Aliases, S>
|
|
313
|
+
: QueryKind<N> extends "select"
|
|
314
|
+
? [Tables] extends [never]
|
|
315
|
+
? true
|
|
316
|
+
// The projection list is extracted from the validation scan view,
|
|
317
|
+
// whose double-quoted-identifier spaces are masked with a sentinel.
|
|
318
|
+
// The alias set restores those spaces, so restore them here too or a
|
|
319
|
+
// qualifier through a space-bearing quoted alias (`"user alias".id`)
|
|
320
|
+
// would never match its registered alias (round-12 regression).
|
|
321
|
+
: ExprsValidList<SplitSelectList<ReplaceAll<ExtractSelectList<N>, DQuoteSpaceSentinel, " ">>, Tables, Aliases, S>
|
|
322
|
+
: true;
|
|
323
|
+
|
|
324
|
+
// insert
|
|
325
|
+
|
|
326
|
+
export type ColumnsValidInInsert<N extends string, S extends DatabaseSchema> =
|
|
327
|
+
QueryKind<N> extends "insert"
|
|
328
|
+
? InsertTargetTable<N, S> extends infer TableKey extends string
|
|
329
|
+
? TableKey extends never
|
|
330
|
+
? true
|
|
331
|
+
: And<
|
|
332
|
+
ColumnsExistInTable<ExtractInsertColumns<N>, TableKey, S>,
|
|
333
|
+
ColumnsExistInTable<ExtractConflictColumns<N>, TableKey, S>,
|
|
334
|
+
ColumnsExistInTable<ExtractConflictUpdateSetColumns<N>, TableKey, S>,
|
|
335
|
+
// RHS `excluded.<col>` refs must name a column of the target
|
|
336
|
+
// table (the `excluded` pseudo-row mirrors it).
|
|
337
|
+
ColumnsExistInTable<ExtractConflictUpdateExcludedCols<N>, TableKey, S>,
|
|
338
|
+
true
|
|
339
|
+
>
|
|
340
|
+
: true
|
|
341
|
+
: true;
|
|
342
|
+
|
|
343
|
+
// update
|
|
344
|
+
|
|
345
|
+
export type ColumnsValidInUpdate<N extends string, S extends DatabaseSchema> =
|
|
346
|
+
QueryKind<N> extends "update"
|
|
347
|
+
? UpdateTargetTable<N, S> extends infer TableKey extends string
|
|
348
|
+
? TableKey extends never
|
|
349
|
+
? true
|
|
350
|
+
: ColumnsExistInTable<ExtractUpdateSetColumns<N>, TableKey, S>
|
|
351
|
+
: true
|
|
352
|
+
: true;
|
|
353
|
+
|
|
354
|
+
// qualified refs across query (best-effort)
|
|
355
|
+
|
|
356
|
+
export type QualifiedColumnRefsValid<N extends string, S extends DatabaseSchema> =
|
|
357
|
+
TablesInQuery<N, S> extends infer Tables extends string
|
|
358
|
+
? AliasesInQuery<N, S> extends infer Aliases extends string
|
|
359
|
+
? TokenizeLoose<RefScanSegment<N>> extends infer LooseTokens extends string[]
|
|
360
|
+
? QualifiedColumnRefsValidFor<N, S, Tables, Aliases, LooseTokens>
|
|
361
|
+
: true
|
|
362
|
+
: true
|
|
363
|
+
: true;
|
|
364
|
+
|
|
365
|
+
export type QualifiedColumnRefsValidFor<
|
|
366
|
+
N extends string,
|
|
367
|
+
S extends DatabaseSchema,
|
|
368
|
+
Tables extends string,
|
|
369
|
+
Aliases extends string,
|
|
370
|
+
LooseTokens extends string[]
|
|
371
|
+
> = QualifiedColumnRefs<LooseTokens, S, Tables, Aliases> extends infer Cols
|
|
372
|
+
? AllTrue<Cols extends string ? ColumnRefValidLooseWith<Cols, Tables, Aliases, S> : true>
|
|
373
|
+
: true;
|
|
374
|
+
|
|
375
|
+
// Once a table is given a range alias (`FROM products p`), PostgreSQL hides the
|
|
376
|
+
// original table name as a correlation name for that query level — `products.id`
|
|
377
|
+
// is then invalid; only `p.id` works. The lenient qualified-ref check accepts
|
|
378
|
+
// the base-name qualifier (it still resolves to a real column), so reject it
|
|
379
|
+
// explicitly: any qualified ref whose qualifier is the BASE NAME of a table that
|
|
380
|
+
// carries a range alias (and is not itself an alias) is invalid. Scans the whole
|
|
381
|
+
// query (the offending ref can sit in the SELECT list, not just the ref segment).
|
|
382
|
+
export type AliasedTableKeys<Aliases extends string> =
|
|
383
|
+
Aliases extends `${string}=>${infer T}` ? T : never;
|
|
384
|
+
|
|
385
|
+
export type QualifierShadowedByAlias<
|
|
386
|
+
Q extends string,
|
|
387
|
+
Tables extends string,
|
|
388
|
+
Aliases extends string,
|
|
389
|
+
S extends DatabaseSchema
|
|
390
|
+
> =
|
|
391
|
+
[ResolveAlias<CleanIdent<Q>, Aliases>] extends [never]
|
|
392
|
+
? [Extract<TableKeysByName<CleanIdent<Q>, Tables>, AliasedTableKeys<Aliases>>] extends [never]
|
|
393
|
+
? false
|
|
394
|
+
: true
|
|
395
|
+
: false;
|
|
396
|
+
|
|
397
|
+
export type NoAliasShadowedQualifiers<
|
|
398
|
+
N extends string,
|
|
399
|
+
S extends DatabaseSchema,
|
|
400
|
+
Tables extends string,
|
|
401
|
+
Aliases extends string
|
|
402
|
+
> =
|
|
403
|
+
[AliasedTableKeys<Aliases>] extends [never]
|
|
404
|
+
? true
|
|
405
|
+
: AllTrue<
|
|
406
|
+
QualifiedColumnRefs<TokenizeLoose<N>, S, Tables, Aliases> extends infer R
|
|
407
|
+
? R extends `${infer Q}.${string}`
|
|
408
|
+
? QualifierShadowedByAlias<Q, Tables, Aliases, S> extends true
|
|
409
|
+
? false
|
|
410
|
+
: true
|
|
411
|
+
: true
|
|
412
|
+
: true
|
|
413
|
+
>;
|
|
414
|
+
|
|
415
|
+
// A table introduced INSIDE a subquery is in scope only there — it must not
|
|
416
|
+
// satisfy an UNQUALIFIED column reference in the OUTER query. The whole-query
|
|
417
|
+
// table scan flattens every relation into one set, so an outer `email` resolves
|
|
418
|
+
// against a `users` table that only exists inside an `EXISTS (...)`. Re-validate
|
|
419
|
+
// the outer scope in isolation: strip every parenthesised group (excising
|
|
420
|
+
// subquery bodies), collect only the depth-0 FROM/JOIN relations, and require
|
|
421
|
+
// each outer unqualified ref to resolve against THOSE. Correlated subquery refs
|
|
422
|
+
// live inside the stripped parens, so they are never checked against the outer
|
|
423
|
+
// tables here (the core whole-query scan still validates them). Gated to plain
|
|
424
|
+
// SELECTs whose FROM clause has no derived/subquery source, and skipped for
|
|
425
|
+
// report-scale queries to bound the char-walk.
|
|
426
|
+
export type OuterScopeUnqualifiedValid<N extends string, S extends DatabaseSchema> =
|
|
427
|
+
StartsWith<N, "select "> extends true
|
|
428
|
+
// Cheap pre-gate: `StripSubqueries` only excises parens that contain a
|
|
429
|
+
// `select`, so unless the query has a `(` followed somewhere by `select`
|
|
430
|
+
// (a parenthesised subquery), the body validates the whole query — already
|
|
431
|
+
// done by the core scan — and is necessarily `true`. Tightening the old
|
|
432
|
+
// "any `(`" gate skips `StripSubqueries` on queries whose only parens are
|
|
433
|
+
// function/grouping. A subquery's `select` is always preceded by its own
|
|
434
|
+
// `(`, so this superset never misses a real scope leak (incl. `( select`).
|
|
435
|
+
? Lowercase<N> extends `${string}(${string}select${string}`
|
|
436
|
+
? ExceedsLengthBudget<N> extends true
|
|
437
|
+
? true
|
|
438
|
+
: StripSubqueries<N> extends infer Stripped extends string
|
|
439
|
+
? TablesInQuery<Stripped, S> extends infer OT extends string
|
|
440
|
+
? [OT] extends [never]
|
|
441
|
+
? true
|
|
442
|
+
// Only trust the depth-0 view when every relation it
|
|
443
|
+
// recovers is a real base table — a JOINed derived
|
|
444
|
+
// source survives stripping as a bare alias token and
|
|
445
|
+
// would otherwise look like a missing table.
|
|
446
|
+
: AllTablesValidFor<OT, S> extends true
|
|
447
|
+
? AliasesInQuery<Stripped, S> extends infer OA extends string
|
|
448
|
+
? UnqualifiedColumnRefsValidFor<
|
|
449
|
+
Stripped,
|
|
450
|
+
S,
|
|
451
|
+
OT,
|
|
452
|
+
OA,
|
|
453
|
+
TokenizeLoose<RefScanSegment<Stripped>>,
|
|
454
|
+
SelectAliasesInQuery<Stripped>
|
|
455
|
+
>
|
|
456
|
+
: true
|
|
457
|
+
: true
|
|
458
|
+
: true
|
|
459
|
+
: true
|
|
460
|
+
: true
|
|
461
|
+
: true;
|
|
462
|
+
|
|
463
|
+
export type UnqualifiedColumnRefsValid<N extends string, S extends DatabaseSchema> =
|
|
464
|
+
TablesInQuery<N, S> extends infer Tables extends string
|
|
465
|
+
? AliasesInQuery<N, S> extends infer Aliases extends string
|
|
466
|
+
? TokenizeLoose<RefScanSegment<N>> extends infer LooseTokens extends string[]
|
|
467
|
+
? SelectAliasesInQuery<N> extends infer SelectAliases extends string
|
|
468
|
+
? UnqualifiedColumnRefsValidFor<N, S, Tables, Aliases, LooseTokens, SelectAliases>
|
|
469
|
+
: true
|
|
470
|
+
: true
|
|
471
|
+
: true
|
|
472
|
+
: true;
|
|
473
|
+
|
|
474
|
+
export type UnqualifiedColumnRefsValidFor<
|
|
475
|
+
N extends string,
|
|
476
|
+
S extends DatabaseSchema,
|
|
477
|
+
Tables extends string,
|
|
478
|
+
Aliases extends string,
|
|
479
|
+
LooseTokens extends string[],
|
|
480
|
+
SelectAliases extends string
|
|
481
|
+
> = UnqualifiedColumnRefs<LooseTokens, S, Tables, Aliases> extends infer Cols
|
|
482
|
+
? AllTrue<
|
|
483
|
+
Cols extends string
|
|
484
|
+
? CleanIdent<Cols> extends SelectAliases
|
|
485
|
+
? true
|
|
486
|
+
: UnqualifiedColumnValid<Cols, Tables, Aliases, S>
|
|
487
|
+
: true
|
|
488
|
+
>
|
|
489
|
+
: true;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Barrel for type-level SQL validation + result inference. Real declarations
|
|
2
|
+
// live in ./validation/*. Importers keep using `from "./validation.js"`.
|
|
3
|
+
export * from "./validation/dispatch.js";
|
|
4
|
+
export * from "./validation/joins.js";
|
|
5
|
+
export * from "./validation/return-types.js";
|
|
6
|
+
export * from "./validation/return-derived.js";
|
|
7
|
+
export * from "./validation/cte.js";
|
|
8
|
+
export * from "./validation/validate-columns.js";
|