@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
package/src/partial.ts
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// Partial (fragment) query validation for the query builder. Each clause of a
|
|
2
|
+
// query gets its own validation entry point. Clause/SELECT validation is now
|
|
3
|
+
// scope-aware: the builder derives an alias→table map from the FROM+JOIN
|
|
4
|
+
// fragments once and passes it in (Tables/Aliases) so a qualified reference is
|
|
5
|
+
// checked against the whole query's scope, not just one fragment in isolation.
|
|
6
|
+
// Refs whose alias/table is still out of that scope, or that aren't plain
|
|
7
|
+
// `alias.col` identifiers, are SKIPPED rather than failed. FROM/JOIN fragments
|
|
8
|
+
// remain self-contained (validated against the schema directly).
|
|
9
|
+
|
|
10
|
+
import type { DatabaseSchema, ColumnExists, TableExists } from "./schema.js";
|
|
11
|
+
import type {
|
|
12
|
+
CleanExpr,
|
|
13
|
+
CleanIdent,
|
|
14
|
+
ExtractBefore,
|
|
15
|
+
HasSpecial,
|
|
16
|
+
NormalizeQuery,
|
|
17
|
+
SplitOnDotClean,
|
|
18
|
+
SplitTopLevel,
|
|
19
|
+
TokenizeLoose,
|
|
20
|
+
Trim
|
|
21
|
+
} from "./parsing.js";
|
|
22
|
+
import type {
|
|
23
|
+
QualifiedColumnRefs,
|
|
24
|
+
ResolveAlias,
|
|
25
|
+
StripDoubleQuotes,
|
|
26
|
+
TableKeysByName
|
|
27
|
+
} from "./columns.js";
|
|
28
|
+
import type { AliasesInQuery, TableKeyValid, TablesInQuery } from "./tables.js";
|
|
29
|
+
import type { AllTrue } from "./utils.js";
|
|
30
|
+
|
|
31
|
+
// Resolve a qualified-ref prefix to a known table key WITHIN the fragment, or
|
|
32
|
+
// `never` when it cannot be resolved (an out-of-scope alias/table -> skip):
|
|
33
|
+
// 1. an alias defined in this part
|
|
34
|
+
// 2. a table named in this part
|
|
35
|
+
// 3. a real table in the default schema with this name
|
|
36
|
+
// Unlike the full-query `ResolveTableKey`, there is NO phantom
|
|
37
|
+
// `${defaultSchema}.${Name}` fallback for names that are not real tables.
|
|
38
|
+
export type PartialResolvePrefix<
|
|
39
|
+
Name extends string,
|
|
40
|
+
Tables extends string,
|
|
41
|
+
Aliases extends string,
|
|
42
|
+
S extends DatabaseSchema
|
|
43
|
+
> =
|
|
44
|
+
[ResolveAlias<CleanIdent<Name>, Aliases>] extends [never]
|
|
45
|
+
? [TableKeysByName<CleanIdent<Name>, Tables>] extends [never]
|
|
46
|
+
? TableExists<S, S["defaultSchema"], CleanIdent<Name>> extends true
|
|
47
|
+
? `${S["defaultSchema"]}.${CleanIdent<Name>}`
|
|
48
|
+
: never
|
|
49
|
+
: TableKeysByName<CleanIdent<Name>, Tables>
|
|
50
|
+
: ResolveAlias<CleanIdent<Name>, Aliases>;
|
|
51
|
+
|
|
52
|
+
// Validate a single column-ref string in fragment mode:
|
|
53
|
+
// - `prefix.*` -> skip (no column to validate)
|
|
54
|
+
// - schema.table.col -> strict if schema.table is real, else skip
|
|
55
|
+
// - prefix.col -> strict if prefix resolves in-fragment, else skip
|
|
56
|
+
// - bare col -> skip (may belong to an out-of-fragment table)
|
|
57
|
+
export type ColumnRefValidPartialWith<
|
|
58
|
+
ColRef extends string,
|
|
59
|
+
Tables extends string,
|
|
60
|
+
Aliases extends string,
|
|
61
|
+
S extends DatabaseSchema
|
|
62
|
+
> =
|
|
63
|
+
ColRef extends `${string}.*`
|
|
64
|
+
? true
|
|
65
|
+
: SplitOnDotClean<StripDoubleQuotes<CleanExpr<ColRef>>> extends [infer A extends string, infer B extends string, infer C extends string]
|
|
66
|
+
? TableExists<S, A, B> extends true
|
|
67
|
+
? ColumnExists<`${A}.${B}`, C, S>
|
|
68
|
+
: true
|
|
69
|
+
: SplitOnDotClean<StripDoubleQuotes<CleanExpr<ColRef>>> extends [infer A extends string, infer B extends string]
|
|
70
|
+
? PartialResolvePrefix<A, Tables, Aliases, S> extends infer TK
|
|
71
|
+
? [TK] extends [never]
|
|
72
|
+
? true
|
|
73
|
+
: TK extends string
|
|
74
|
+
? ColumnExists<TK, B, S>
|
|
75
|
+
: true
|
|
76
|
+
: true
|
|
77
|
+
: true;
|
|
78
|
+
|
|
79
|
+
// Validate every qualified column ref in a token list, partial-mode.
|
|
80
|
+
export type QualifiedColumnRefsValidPartialFor<
|
|
81
|
+
S extends DatabaseSchema,
|
|
82
|
+
Tables extends string,
|
|
83
|
+
Aliases extends string,
|
|
84
|
+
LooseTokens extends string[]
|
|
85
|
+
> = QualifiedColumnRefs<LooseTokens, S, Tables, Aliases> extends infer Cols
|
|
86
|
+
? AllTrue<Cols extends string ? ColumnRefValidPartialWith<Cols, Tables, Aliases, S> : true>
|
|
87
|
+
: true;
|
|
88
|
+
|
|
89
|
+
// Strict table-existence over every table named in the fragment. Vacuously
|
|
90
|
+
// `true` when the fragment names no table (`Tables` is `never`).
|
|
91
|
+
export type AllPartTablesValid<Tables extends string, S extends DatabaseSchema> =
|
|
92
|
+
AllTrue<Tables extends string ? TableKeyValid<Tables, S> : true>;
|
|
93
|
+
|
|
94
|
+
// Shared validator for table-source fragments (FROM / JOIN): every named table
|
|
95
|
+
// must exist (strict), and every resolvable qualified ref must check out.
|
|
96
|
+
export type ValidateTableSourcePart<N extends string, S extends DatabaseSchema> =
|
|
97
|
+
TablesInQuery<N, S> extends infer Tables extends string
|
|
98
|
+
? AliasesInQuery<N, S> extends infer Aliases extends string
|
|
99
|
+
? AllPartTablesValid<Tables, S> extends true
|
|
100
|
+
? TokenizeLoose<N> extends infer Toks extends string[]
|
|
101
|
+
? QualifiedColumnRefsValidPartialFor<S, Tables, Aliases, Toks>
|
|
102
|
+
: true
|
|
103
|
+
: false
|
|
104
|
+
: true
|
|
105
|
+
: true;
|
|
106
|
+
|
|
107
|
+
// A FROM fragment may arrive bare (`users u`) or led (`from users u`). The
|
|
108
|
+
// table/alias collectors key off the `from` keyword, so ensure it is present.
|
|
109
|
+
export type EnsureFromLed<N extends string> =
|
|
110
|
+
N extends `from ${string}` ? N : `from ${N}`;
|
|
111
|
+
|
|
112
|
+
export type ValidateFromPart<Part extends string, S extends DatabaseSchema> =
|
|
113
|
+
string extends Part
|
|
114
|
+
? false
|
|
115
|
+
: NormalizeQuery<Part> extends infer N extends string
|
|
116
|
+
? ValidateTableSourcePart<EnsureFromLed<N>, S>
|
|
117
|
+
: false;
|
|
118
|
+
|
|
119
|
+
export type ValidateJoinPart<Part extends string, S extends DatabaseSchema> =
|
|
120
|
+
string extends Part
|
|
121
|
+
? false
|
|
122
|
+
: NormalizeQuery<Part> extends infer N extends string
|
|
123
|
+
? ValidateTableSourcePart<N, S>
|
|
124
|
+
: false;
|
|
125
|
+
|
|
126
|
+
// Clause fragments (SELECT list, WHERE, HAVING, GROUP BY, ORDER BY) carry no
|
|
127
|
+
// table source in isolation. Validate only refs resolvable without one
|
|
128
|
+
// (`schema.table.col` and real `table.col`); skip alias-qualified and bare cols.
|
|
129
|
+
export type ValidateClausePart<Part extends string, S extends DatabaseSchema> =
|
|
130
|
+
string extends Part
|
|
131
|
+
? false
|
|
132
|
+
: NormalizeQuery<Part> extends infer N extends string
|
|
133
|
+
? TokenizeLoose<N> extends infer Toks extends string[]
|
|
134
|
+
? QualifiedColumnRefsValidPartialFor<S, never, never, Toks>
|
|
135
|
+
: true
|
|
136
|
+
: false;
|
|
137
|
+
|
|
138
|
+
// Scope-aware clause validation: identical to ValidateClausePart, but the
|
|
139
|
+
// alias->table map (built from FROM+JOIN by the builder) is threaded in so that
|
|
140
|
+
// alias-qualified refs (`u.col`) resolve and typos are caught.
|
|
141
|
+
export type ValidateClausePartScoped<
|
|
142
|
+
Part extends string,
|
|
143
|
+
Tables extends string,
|
|
144
|
+
Aliases extends string,
|
|
145
|
+
S extends DatabaseSchema
|
|
146
|
+
> =
|
|
147
|
+
string extends Part
|
|
148
|
+
? false
|
|
149
|
+
: NormalizeQuery<Part> extends infer N extends string
|
|
150
|
+
? TokenizeLoose<N> extends infer Toks extends string[]
|
|
151
|
+
? QualifiedColumnRefsValidPartialFor<S, Tables, Aliases, Toks>
|
|
152
|
+
: true
|
|
153
|
+
: false;
|
|
154
|
+
|
|
155
|
+
// Expression-detector for a single SELECT-item token. HasSpecial covers space,
|
|
156
|
+
// parens, arithmetic/comparison operators, comma, `::`, `||`. We additionally
|
|
157
|
+
// reject `[ ] " ' :` so array-indexing, quoted-with-space idents, json arrows,
|
|
158
|
+
// and param/cast colons are treated as expressions (skipped, never falsely
|
|
159
|
+
// rejected). A token clearing this guard is a plain identifier piece.
|
|
160
|
+
type RefHasSpecial<S extends string> =
|
|
161
|
+
HasSpecial<S> extends true ? true :
|
|
162
|
+
S extends `${string}[${string}` ? true :
|
|
163
|
+
S extends `${string}]${string}` ? true :
|
|
164
|
+
S extends `${string}"${string}` ? true :
|
|
165
|
+
S extends `${string}'${string}` ? true :
|
|
166
|
+
S extends `${string}:${string}` ? true :
|
|
167
|
+
false;
|
|
168
|
+
|
|
169
|
+
// True iff S is a plain two-part `alias.col` ref (no expression syntax).
|
|
170
|
+
// `${infer A}.${infer B}` binds A to the shortest pre-first-dot match; a 3-part
|
|
171
|
+
// `schema.table.col` leaves a dot in B and is rejected (skipped).
|
|
172
|
+
type IsPlainQualifiedRef<S extends string> =
|
|
173
|
+
S extends `${infer A}.${infer B}`
|
|
174
|
+
? RefHasSpecial<A> extends true
|
|
175
|
+
? false
|
|
176
|
+
: RefHasSpecial<B> extends true
|
|
177
|
+
? false
|
|
178
|
+
: B extends `${string}.${string}`
|
|
179
|
+
? false
|
|
180
|
+
: true
|
|
181
|
+
: false;
|
|
182
|
+
|
|
183
|
+
// The leading token of a SELECT item with any trailing alias dropped
|
|
184
|
+
// (`o.id as foo` / `o.id foo` -> `o.id`). ExtractBefore returns the whole string
|
|
185
|
+
// when there is no space.
|
|
186
|
+
type SelectItemRef<Item extends string> = ExtractBefore<Trim<Item>, " ">;
|
|
187
|
+
|
|
188
|
+
// Validate ONE select item: resolve only plain `alias.col` refs; skip everything
|
|
189
|
+
// else (functions, CASE, casts, literals, `*`, quoted-space idents) -> true.
|
|
190
|
+
type SelectItemValid<
|
|
191
|
+
Item extends string,
|
|
192
|
+
Tables extends string,
|
|
193
|
+
Aliases extends string,
|
|
194
|
+
S extends DatabaseSchema
|
|
195
|
+
> = SelectItemRef<Item> extends infer Ref extends string
|
|
196
|
+
? IsPlainQualifiedRef<Ref> extends true
|
|
197
|
+
? ColumnRefValidPartialWith<Ref, Tables, Aliases, S>
|
|
198
|
+
: true
|
|
199
|
+
: true;
|
|
200
|
+
|
|
201
|
+
// Validate every top-level SELECT item. Early-exit on first false. Step-capped.
|
|
202
|
+
type SelectListValidScoped<
|
|
203
|
+
List extends readonly string[],
|
|
204
|
+
Tables extends string,
|
|
205
|
+
Aliases extends string,
|
|
206
|
+
S extends DatabaseSchema,
|
|
207
|
+
Steps extends any[] = []
|
|
208
|
+
> = Steps["length"] extends 200
|
|
209
|
+
? true
|
|
210
|
+
: List extends readonly [infer H extends string, ...infer R extends readonly string[]]
|
|
211
|
+
? SelectItemValid<H, Tables, Aliases, S> extends false
|
|
212
|
+
? false
|
|
213
|
+
: SelectListValidScoped<R, Tables, Aliases, S, [any, ...Steps]>
|
|
214
|
+
: true;
|
|
215
|
+
|
|
216
|
+
// Identifiers-only SELECT validation: split the list at top-level commas
|
|
217
|
+
// (depth-safe, never normalizes expression bodies) and check only plain refs.
|
|
218
|
+
export type ValidateSelectIdentifiersScoped<
|
|
219
|
+
Part extends string,
|
|
220
|
+
Tables extends string,
|
|
221
|
+
Aliases extends string,
|
|
222
|
+
S extends DatabaseSchema
|
|
223
|
+
> = string extends Part
|
|
224
|
+
? false
|
|
225
|
+
: SplitTopLevel<Part> extends infer Items extends readonly string[]
|
|
226
|
+
? SelectListValidScoped<Items, Tables, Aliases, S>
|
|
227
|
+
: true;
|
|
228
|
+
|
|
229
|
+
// Distinct public entry points, identical in isolation; they will diverge once
|
|
230
|
+
// clause-specific context is threaded in (HAVING aggregates, GROUP BY/ORDER BY
|
|
231
|
+
// ordinals, SELECT `*`), so keep them as separate names rather than collapsing.
|
|
232
|
+
export type ValidateSelectPart<Part extends string, S extends DatabaseSchema> =
|
|
233
|
+
ValidateClausePart<Part, S>;
|
|
234
|
+
export type ValidateWherePart<Part extends string, S extends DatabaseSchema> =
|
|
235
|
+
ValidateClausePart<Part, S>;
|
|
236
|
+
export type ValidateHavingPart<Part extends string, S extends DatabaseSchema> =
|
|
237
|
+
ValidateClausePart<Part, S>;
|
|
238
|
+
export type ValidateGroupByPart<Part extends string, S extends DatabaseSchema> =
|
|
239
|
+
ValidateClausePart<Part, S>;
|
|
240
|
+
export type ValidateOrderByPart<Part extends string, S extends DatabaseSchema> =
|
|
241
|
+
ValidateClausePart<Part, S>;
|
package/src/schema.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { AnyTrue, Simplify, UnionToIntersection } from "./utils.js";
|
|
2
|
+
|
|
3
|
+
export type DatabaseSchema = {
|
|
4
|
+
defaultSchema: string;
|
|
5
|
+
schemas: Record<string, Record<string, Record<string, any>>>;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type StringKeys<T> = Extract<keyof T, string>;
|
|
9
|
+
|
|
10
|
+
export type MatchKeyCaseInsensitive<Obj, Name extends string> =
|
|
11
|
+
string extends Name
|
|
12
|
+
? never
|
|
13
|
+
: StringKeys<Obj> extends infer K extends string
|
|
14
|
+
? K extends any
|
|
15
|
+
? Lowercase<K> extends Lowercase<Name>
|
|
16
|
+
? K
|
|
17
|
+
: never
|
|
18
|
+
: never
|
|
19
|
+
: never;
|
|
20
|
+
|
|
21
|
+
export type ResolveSchemaName<S extends DatabaseSchema, Schema extends string> =
|
|
22
|
+
MatchKeyCaseInsensitive<S["schemas"], Schema>;
|
|
23
|
+
|
|
24
|
+
export type ResolveTableName<
|
|
25
|
+
S extends DatabaseSchema,
|
|
26
|
+
Schema extends string,
|
|
27
|
+
Table extends string
|
|
28
|
+
> =
|
|
29
|
+
Schema extends keyof S["schemas"]
|
|
30
|
+
? MatchKeyCaseInsensitive<S["schemas"][Schema], Table>
|
|
31
|
+
: never;
|
|
32
|
+
|
|
33
|
+
export type NormalizeTableKey<TableKey extends string, S extends DatabaseSchema> =
|
|
34
|
+
TableKey extends `${infer Schema}.${infer Table}`
|
|
35
|
+
? ResolveSchemaName<S, Schema> extends infer RS extends string
|
|
36
|
+
? ResolveTableName<S, RS, Table> extends infer RT extends string
|
|
37
|
+
? `${RS}.${RT}`
|
|
38
|
+
: never
|
|
39
|
+
: never
|
|
40
|
+
: never;
|
|
41
|
+
|
|
42
|
+
export type RowTypeFromNormalizedTableKey<
|
|
43
|
+
TableKey extends string,
|
|
44
|
+
S extends DatabaseSchema
|
|
45
|
+
> =
|
|
46
|
+
TableKey extends `${infer Schema}.${infer Table}`
|
|
47
|
+
? Schema extends keyof S["schemas"]
|
|
48
|
+
? Table extends keyof S["schemas"][Schema]
|
|
49
|
+
? S["schemas"][Schema][Table]
|
|
50
|
+
: never
|
|
51
|
+
: never
|
|
52
|
+
: never;
|
|
53
|
+
|
|
54
|
+
export type RowTypeForResolvedTableKey<TableKey extends string, S extends DatabaseSchema> =
|
|
55
|
+
NormalizeTableKey<TableKey, S> extends infer Normalized extends string
|
|
56
|
+
? RowTypeFromNormalizedTableKey<Normalized, S>
|
|
57
|
+
: never;
|
|
58
|
+
|
|
59
|
+
export type ResolveColumnName<
|
|
60
|
+
TableKey extends string,
|
|
61
|
+
Column extends string,
|
|
62
|
+
S extends DatabaseSchema
|
|
63
|
+
> =
|
|
64
|
+
RowTypeForResolvedTableKey<TableKey, S> extends infer Row extends Record<string, any>
|
|
65
|
+
? MatchKeyCaseInsensitive<Row, Column>
|
|
66
|
+
: never;
|
|
67
|
+
|
|
68
|
+
export type TableExists<S extends DatabaseSchema, Schema extends string, Table extends string> =
|
|
69
|
+
NormalizeTableKey<`${Schema}.${Table}`, S> extends never ? false : true;
|
|
70
|
+
|
|
71
|
+
export type ColumnExists<TableKey extends string, Column extends string, S extends DatabaseSchema> =
|
|
72
|
+
ColumnTypeFromTableKey<TableKey, Column, S> extends never ? false : true;
|
|
73
|
+
|
|
74
|
+
export type ColumnTypeFromTableKey<TableKey extends string, Column extends string, S extends DatabaseSchema> =
|
|
75
|
+
ResolveColumnName<TableKey, Column, S> extends infer ResolvedColumn extends string
|
|
76
|
+
? RowTypeForResolvedTableKey<TableKey, S> extends infer Row extends Record<string, any>
|
|
77
|
+
? ResolvedColumn extends keyof Row
|
|
78
|
+
? Row[ResolvedColumn]
|
|
79
|
+
: never
|
|
80
|
+
: never
|
|
81
|
+
: never;
|
|
82
|
+
|
|
83
|
+
export type ColumnTypeFromSchemaTable<
|
|
84
|
+
Schema extends string,
|
|
85
|
+
Table extends string,
|
|
86
|
+
Column extends string,
|
|
87
|
+
S extends DatabaseSchema
|
|
88
|
+
> =
|
|
89
|
+
ColumnTypeFromTableKey<`${Schema}.${Table}`, Column, S>;
|
|
90
|
+
|
|
91
|
+
export type RowTypeForTables<Tables extends string, S extends DatabaseSchema> =
|
|
92
|
+
MergeRowUnion<
|
|
93
|
+
Tables extends string
|
|
94
|
+
? RowTypeForResolvedTableKey<Tables, S>
|
|
95
|
+
: never
|
|
96
|
+
>;
|
|
97
|
+
|
|
98
|
+
// Merge a union of row types into one row. Unlike UnionToIntersection, a column
|
|
99
|
+
// present in several joined tables keeps the UNION of its types (not their
|
|
100
|
+
// intersection, which collapses differing same-named columns to `never`).
|
|
101
|
+
export type MergeRowUnion<Rows> =
|
|
102
|
+
[Rows] extends [never]
|
|
103
|
+
? {}
|
|
104
|
+
: Simplify<{
|
|
105
|
+
[K in (Rows extends any ? keyof Rows : never) & PropertyKey]:
|
|
106
|
+
Rows extends any ? (K extends keyof Rows ? Rows[K] : never) : never;
|
|
107
|
+
}>;
|
|
108
|
+
|
|
109
|
+
export type RowTypeForTable<TableKey extends string, S extends DatabaseSchema> =
|
|
110
|
+
RowTypeForResolvedTableKey<TableKey, S>;
|
|
111
|
+
|
|
112
|
+
export type AllTableKeys<S extends DatabaseSchema> =
|
|
113
|
+
keyof S["schemas"] extends infer Schema
|
|
114
|
+
? Schema extends string
|
|
115
|
+
? keyof S["schemas"][Schema] extends infer Table
|
|
116
|
+
? Table extends string
|
|
117
|
+
? `${Schema}.${Table}`
|
|
118
|
+
: never
|
|
119
|
+
: never
|
|
120
|
+
: never
|
|
121
|
+
: never;
|
|
122
|
+
|
|
123
|
+
export type ColumnExistsInAnyTable<Column extends string, S extends DatabaseSchema> =
|
|
124
|
+
AnyTrue<ColumnExistsInTableUnion<AllTableKeys<S>, Column, S>>;
|
|
125
|
+
|
|
126
|
+
export type ColumnExistsInTableUnion<
|
|
127
|
+
Tables extends string,
|
|
128
|
+
Column extends string,
|
|
129
|
+
S extends DatabaseSchema
|
|
130
|
+
> = Tables extends any ? ColumnExists<Tables, Column, S> : false;
|
package/src/tables.ts
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import type { DatabaseSchema, NormalizeTableKey, TableExists } from "./schema.js";
|
|
2
|
+
import type { CleanIdent, CommaSep, SplitOnDotClean, SqlKeyword, Tokenize, TokenizeTables } from "./parsing.js";
|
|
3
|
+
|
|
4
|
+
// Table and alias extraction
|
|
5
|
+
|
|
6
|
+
export type TablesInQuery<N extends string, S extends DatabaseSchema> =
|
|
7
|
+
CollectTables<TokenizeTables<N>, S, never, false, N extends `delete ${string}` ? true : false>;
|
|
8
|
+
|
|
9
|
+
export type AliasesInQuery<N extends string, S extends DatabaseSchema> =
|
|
10
|
+
CollectAliases<TokenizeTables<N>, S, never, false, N extends `delete ${string}` ? true : false>;
|
|
11
|
+
|
|
12
|
+
export type TableKeyValid<Key extends string, S extends DatabaseSchema> =
|
|
13
|
+
Key extends `${infer Schema}.${infer Table}`
|
|
14
|
+
? TableExists<S, Schema, Table>
|
|
15
|
+
: false;
|
|
16
|
+
|
|
17
|
+
export type PreferNormalizedTableKey<Raw extends string, Normalized> =
|
|
18
|
+
[Normalized] extends [never] ? Raw : Extract<Normalized, string>;
|
|
19
|
+
|
|
20
|
+
export type TableKeyFromToken<Token extends string, S extends DatabaseSchema> =
|
|
21
|
+
ParseTableToken<Token, S> extends infer Parsed
|
|
22
|
+
? Parsed extends { schema: infer Schema extends string; table: infer Table extends string }
|
|
23
|
+
? PreferNormalizedTableKey<`${Schema}.${Table}`, NormalizeTableKey<`${Schema}.${Table}`, S>>
|
|
24
|
+
: never
|
|
25
|
+
: never;
|
|
26
|
+
|
|
27
|
+
export type ParseTableToken<Token extends string, S extends DatabaseSchema> =
|
|
28
|
+
SplitOnDotClean<Token> extends [infer A extends string, infer B extends string]
|
|
29
|
+
? { schema: A; table: B }
|
|
30
|
+
: SplitOnDotClean<Token> extends [infer A extends string]
|
|
31
|
+
? { schema: S["defaultSchema"]; table: A }
|
|
32
|
+
: never;
|
|
33
|
+
|
|
34
|
+
// Insert/update/delete target table
|
|
35
|
+
|
|
36
|
+
export type InsertTargetTable<N extends string, S extends DatabaseSchema> =
|
|
37
|
+
TableAfter<Tokenize<N>, "into", S>;
|
|
38
|
+
|
|
39
|
+
export type UpdateTargetTable<N extends string, S extends DatabaseSchema> =
|
|
40
|
+
TableAfter<Tokenize<N>, "update", S>;
|
|
41
|
+
|
|
42
|
+
export type DeleteTargetTable<N extends string, S extends DatabaseSchema> =
|
|
43
|
+
TableAfter<Tokenize<N>, "from", S>;
|
|
44
|
+
|
|
45
|
+
// Collect tables by keyword
|
|
46
|
+
|
|
47
|
+
// `InList` tracks whether we are inside a FROM-source list, so a TOP-LEVEL comma
|
|
48
|
+
// (preserved as a `,` token by `TokenizeTables`) introduces ANOTHER table source
|
|
49
|
+
// — the ANSI comma cross-join `from a, b`. The flag is turned on after a
|
|
50
|
+
// `from`/`join`/`into`/`update` source and off at the next clause keyword, so
|
|
51
|
+
// commas in the SELECT list / GROUP BY / ORDER BY / value tuples are ignored.
|
|
52
|
+
//
|
|
53
|
+
// `InDelete` marks that we are inside a DELETE statement, where `USING` is a
|
|
54
|
+
// table-source clause (`DELETE FROM a USING b, c`) — collected like FROM/JOIN.
|
|
55
|
+
// `USING` in a SELECT (the JOIN ... USING (cols) join condition) is NOT a table
|
|
56
|
+
// source, so the branch is gated: outside a DELETE, `using` is skipped as before.
|
|
57
|
+
export type CollectTables<
|
|
58
|
+
Tokens extends string[],
|
|
59
|
+
S extends DatabaseSchema,
|
|
60
|
+
Acc extends string = never,
|
|
61
|
+
InList extends boolean = false,
|
|
62
|
+
InDelete extends boolean = false
|
|
63
|
+
> =
|
|
64
|
+
Tokens extends [infer T extends string, infer Next extends string, ...infer Rest extends string[]]
|
|
65
|
+
? T extends "from" | "join" | "into"
|
|
66
|
+
// `JOIN LATERAL (subquery|func(...)) alias` — `LATERAL` is a source
|
|
67
|
+
// modifier, not a relation. Skip it: the subquery body's own `from`
|
|
68
|
+
// re-establishes collection of its real tables, and a function-call
|
|
69
|
+
// source token is never reached by a from/join/comma branch, so neither
|
|
70
|
+
// the bare `lateral` nor the function name is mistaken for a table.
|
|
71
|
+
? Next extends "lateral"
|
|
72
|
+
? CollectTables<Rest, S, Acc, true, InDelete>
|
|
73
|
+
: CollectTables<Rest, S, Acc | TableKeyFromToken<Next, S>, true, InDelete>
|
|
74
|
+
: T extends "update"
|
|
75
|
+
? Next extends "set"
|
|
76
|
+
? CollectTables<Rest, S, Acc, false, InDelete>
|
|
77
|
+
: CollectTables<Rest, S, Acc | TableKeyFromToken<Next, S>, true, InDelete>
|
|
78
|
+
: T extends "delete"
|
|
79
|
+
? Next extends "from"
|
|
80
|
+
? Rest extends [infer DelTable extends string, ...infer Rest2 extends string[]]
|
|
81
|
+
? CollectTables<Rest2, S, Acc | TableKeyFromToken<DelTable, S>, false, true>
|
|
82
|
+
: Acc
|
|
83
|
+
: CollectTables<[Next, ...Rest], S, Acc, false, true>
|
|
84
|
+
: T extends "using"
|
|
85
|
+
? InDelete extends true
|
|
86
|
+
? CollectTables<Rest, S, Acc | TableKeyFromToken<Next, S>, true, InDelete>
|
|
87
|
+
: CollectTables<[Next, ...Rest], S, Acc, InList, InDelete>
|
|
88
|
+
: T extends CommaSep
|
|
89
|
+
? InList extends true
|
|
90
|
+
? CollectTables<Rest, S, Acc | TableKeyFromToken<Next, S>, true, InDelete>
|
|
91
|
+
: CollectTables<[Next, ...Rest], S, Acc, false, InDelete>
|
|
92
|
+
: T extends "as"
|
|
93
|
+
? CollectTables<[Next, ...Rest], S, Acc, InList, InDelete>
|
|
94
|
+
// `IS [NOT] DISTINCT FROM` is a comparison
|
|
95
|
+
// operator, not a FROM clause: the `from` after
|
|
96
|
+
// `distinct` must NOT be collected as a table
|
|
97
|
+
// source. Drop the operator `from` (process
|
|
98
|
+
// `Rest`) so its RHS isn't mistaken for a table.
|
|
99
|
+
: T extends "distinct"
|
|
100
|
+
? Next extends "from"
|
|
101
|
+
? CollectTables<Rest, S, Acc, false, InDelete>
|
|
102
|
+
: CollectTables<[Next, ...Rest], S, Acc, false, InDelete>
|
|
103
|
+
: T extends SqlKeyword
|
|
104
|
+
? CollectTables<[Next, ...Rest], S, Acc, false, InDelete>
|
|
105
|
+
: CollectTables<[Next, ...Rest], S, Acc, InList, InDelete>
|
|
106
|
+
: Acc;
|
|
107
|
+
|
|
108
|
+
// Collect aliases for tables in FROM/JOIN/UPDATE
|
|
109
|
+
|
|
110
|
+
// `InList` mirrors `CollectTables`: after a `from`/`join`/`update` source, a
|
|
111
|
+
// top-level `,` introduces another aliased source (`from users u, orders o`).
|
|
112
|
+
// `InDelete` likewise mirrors `CollectTables`: inside a DELETE, `USING` opens an
|
|
113
|
+
// aliased table source (`DELETE FROM a USING users u`); outside one it is left
|
|
114
|
+
// alone (the JOIN ... USING (cols) join condition is not a source).
|
|
115
|
+
export type CollectAliases<
|
|
116
|
+
Tokens extends string[],
|
|
117
|
+
S extends DatabaseSchema,
|
|
118
|
+
Acc extends string = never,
|
|
119
|
+
InList extends boolean = false,
|
|
120
|
+
InDelete extends boolean = false
|
|
121
|
+
> =
|
|
122
|
+
Tokens extends [infer T extends string, infer Next extends string, ...infer Rest extends string[]]
|
|
123
|
+
? T extends "from" | "join" | "update"
|
|
124
|
+
// `JOIN LATERAL (...)` — mirror `CollectTables`: skip the `lateral`
|
|
125
|
+
// modifier so it is never parsed as an aliased table source.
|
|
126
|
+
? Next extends "lateral"
|
|
127
|
+
? CollectAliases<Rest, S, Acc, true, InDelete>
|
|
128
|
+
: ParseAliasSource<Next, Rest, S, Acc, InDelete>
|
|
129
|
+
: T extends "using"
|
|
130
|
+
? InDelete extends true
|
|
131
|
+
? ParseAliasSource<Next, Rest, S, Acc, InDelete>
|
|
132
|
+
: CollectAliases<[Next, ...Rest], S, Acc, InList, InDelete>
|
|
133
|
+
: T extends CommaSep
|
|
134
|
+
? InList extends true
|
|
135
|
+
? ParseAliasSource<Next, Rest, S, Acc, InDelete>
|
|
136
|
+
: CollectAliases<[Next, ...Rest], S, Acc, false, InDelete>
|
|
137
|
+
: T extends "as"
|
|
138
|
+
? CollectAliases<[Next, ...Rest], S, Acc, InList, InDelete>
|
|
139
|
+
// `IS [NOT] DISTINCT FROM`: the operator `from` is not a
|
|
140
|
+
// table source, so it must not open an aliased source.
|
|
141
|
+
: T extends "distinct"
|
|
142
|
+
? Next extends "from"
|
|
143
|
+
? CollectAliases<Rest, S, Acc, false, InDelete>
|
|
144
|
+
: CollectAliases<[Next, ...Rest], S, Acc, false, InDelete>
|
|
145
|
+
: T extends SqlKeyword
|
|
146
|
+
? CollectAliases<[Next, ...Rest], S, Acc, false, InDelete>
|
|
147
|
+
: CollectAliases<[Next, ...Rest], S, Acc, InList, InDelete>
|
|
148
|
+
: Acc;
|
|
149
|
+
|
|
150
|
+
// Parse a single table source (`Next`) plus its optional alias from the tokens
|
|
151
|
+
// that follow it (`Rest`), record the alias, then continue collecting with
|
|
152
|
+
// `InList=true` so a subsequent top-level comma is recognized as another source.
|
|
153
|
+
// An immediate `,` after the table (no alias) is handed back to `CollectAliases`
|
|
154
|
+
// rather than mistaken for an alias.
|
|
155
|
+
export type ParseAliasSource<
|
|
156
|
+
Next extends string,
|
|
157
|
+
Rest extends string[],
|
|
158
|
+
S extends DatabaseSchema,
|
|
159
|
+
Acc extends string,
|
|
160
|
+
InDelete extends boolean = false
|
|
161
|
+
> =
|
|
162
|
+
TableKeyFromToken<Next, S> extends infer TableKey extends string
|
|
163
|
+
? Rest extends [infer MaybeAlias extends string, ...infer Rest2 extends string[]]
|
|
164
|
+
? MaybeAlias extends "as"
|
|
165
|
+
? Rest2 extends [infer Alias extends string, ...infer Rest3 extends string[]]
|
|
166
|
+
? CollectAliases<Rest3, S, Acc | AliasEntry<Alias, TableKey>, true, InDelete>
|
|
167
|
+
: Acc
|
|
168
|
+
: MaybeAlias extends CommaSep
|
|
169
|
+
? CollectAliases<Rest, S, Acc, true, InDelete>
|
|
170
|
+
// Inside a DELETE, a following `using` is not this source's
|
|
171
|
+
// alias — it opens the next table source. Hand it back so
|
|
172
|
+
// `CollectAliases` processes the USING clause.
|
|
173
|
+
: InDelete extends true
|
|
174
|
+
? MaybeAlias extends "using"
|
|
175
|
+
? CollectAliases<Rest, S, Acc, true, InDelete>
|
|
176
|
+
: IsAliasCandidate<MaybeAlias> extends true
|
|
177
|
+
? CollectAliases<Rest2, S, Acc | AliasEntry<MaybeAlias, TableKey>, true, InDelete>
|
|
178
|
+
: CollectAliases<Rest, S, Acc, true, InDelete>
|
|
179
|
+
: IsAliasCandidate<MaybeAlias> extends true
|
|
180
|
+
? CollectAliases<Rest2, S, Acc | AliasEntry<MaybeAlias, TableKey>, true, InDelete>
|
|
181
|
+
: CollectAliases<Rest, S, Acc, true, InDelete>
|
|
182
|
+
: Acc
|
|
183
|
+
: CollectAliases<Rest, S, Acc, true, InDelete>;
|
|
184
|
+
|
|
185
|
+
// Outer-join nullability
|
|
186
|
+
//
|
|
187
|
+
// A column drawn from a relation on the OUTER side of a join can be NULL even
|
|
188
|
+
// when its declared schema type is non-nullable: a LEFT JOIN's right table may
|
|
189
|
+
// have no matching row, a RIGHT JOIN's left tables may have none, and a FULL
|
|
190
|
+
// JOIN's rows may be missing on either side. `NullableRelations` returns the set
|
|
191
|
+
// of *reference qualifiers* (the alias each relation is referenced by, or its
|
|
192
|
+
// table name when unaliased) that are nullable for this reason; the projection
|
|
193
|
+
// path unions `| null` onto any directly-projected column qualified by one of
|
|
194
|
+
// them. Keying by qualifier (not table key) is what lets a self-join
|
|
195
|
+
// (`users u ... left join users m`) nullablize only the outer alias `m`, not the
|
|
196
|
+
// base `u`. INNER/CROSS joins and the leading FROM source contribute nothing.
|
|
197
|
+
// (Limitation: an UNqualified projected column from an outer-joined relation is
|
|
198
|
+
// not nullablized, since it carries no qualifier to match.)
|
|
199
|
+
export type NullableRelations<N extends string, S extends DatabaseSchema> =
|
|
200
|
+
CollectNullable<TokenizeTables<N>, "none", never, never>;
|
|
201
|
+
|
|
202
|
+
// `Mod` is the pending join modifier ("left"/"right"/"full"/"none"); `Left` is
|
|
203
|
+
// the set of qualifiers accumulated so far (the left side of any later join);
|
|
204
|
+
// `Acc` is the nullable-qualifier accumulator. `outer` is noise (keeps `Mod`);
|
|
205
|
+
// `inner`/`cross` reset `Mod` to "none". On a `join <table>`: LEFT adds the
|
|
206
|
+
// joined relation; RIGHT adds the accumulated left side; FULL adds both.
|
|
207
|
+
export type CollectNullable<
|
|
208
|
+
Tokens extends string[],
|
|
209
|
+
Mod extends string,
|
|
210
|
+
Left extends string,
|
|
211
|
+
Acc extends string
|
|
212
|
+
> =
|
|
213
|
+
Tokens extends [infer T extends string, ...infer Rest extends string[]]
|
|
214
|
+
? T extends "left"
|
|
215
|
+
? CollectNullable<Rest, "left", Left, Acc>
|
|
216
|
+
: T extends "right"
|
|
217
|
+
? CollectNullable<Rest, "right", Left, Acc>
|
|
218
|
+
: T extends "full"
|
|
219
|
+
? CollectNullable<Rest, "full", Left, Acc>
|
|
220
|
+
: T extends "inner" | "cross"
|
|
221
|
+
? CollectNullable<Rest, "none", Left, Acc>
|
|
222
|
+
: T extends "outer"
|
|
223
|
+
? CollectNullable<Rest, Mod, Left, Acc>
|
|
224
|
+
: T extends "from" | "into"
|
|
225
|
+
? Rest extends [infer Tbl extends string, ...infer R2 extends string[]]
|
|
226
|
+
? CollectNullable<R2, "none", Left | SourceQualifier<Tbl, R2>, Acc>
|
|
227
|
+
: Acc
|
|
228
|
+
: T extends "join"
|
|
229
|
+
? Rest extends [infer Tbl extends string, ...infer R2 extends string[]]
|
|
230
|
+
? SourceQualifier<Tbl, R2> extends infer Q extends string
|
|
231
|
+
? Mod extends "left"
|
|
232
|
+
? CollectNullable<R2, "none", Left | Q, Acc | Q>
|
|
233
|
+
: Mod extends "right"
|
|
234
|
+
? CollectNullable<R2, "none", Left | Q, Acc | Left>
|
|
235
|
+
: Mod extends "full"
|
|
236
|
+
? CollectNullable<R2, "none", Left | Q, Acc | Left | Q>
|
|
237
|
+
: CollectNullable<R2, "none", Left | Q, Acc>
|
|
238
|
+
: Acc
|
|
239
|
+
: Acc
|
|
240
|
+
: CollectNullable<Rest, Mod, Left, Acc>
|
|
241
|
+
: Acc;
|
|
242
|
+
|
|
243
|
+
// The qualifier a relation is referenced by in projections: its alias when one
|
|
244
|
+
// is present (`... t`/`... as t`), else the (cleaned) table name. `Rest` are the
|
|
245
|
+
// tokens following the table token. A following keyword (`on`, `where`, another
|
|
246
|
+
// `join`, ...) is not an alias, so the table name is used.
|
|
247
|
+
export type SourceQualifier<Tbl extends string, Rest extends string[]> =
|
|
248
|
+
Rest extends ["as", infer A extends string, ...string[]]
|
|
249
|
+
? CleanIdent<A>
|
|
250
|
+
: Rest extends [infer Maybe extends string, ...string[]]
|
|
251
|
+
? IsAliasCandidate<Maybe> extends true
|
|
252
|
+
? CleanIdent<Maybe>
|
|
253
|
+
: CleanIdent<Tbl>
|
|
254
|
+
: CleanIdent<Tbl>;
|
|
255
|
+
|
|
256
|
+
// Table lookup after a keyword
|
|
257
|
+
|
|
258
|
+
export type TableAfter<Tokens extends string[], Keyword extends string, S extends DatabaseSchema> =
|
|
259
|
+
Tokens extends [infer T extends string, infer Next extends string, ...infer Rest extends string[]]
|
|
260
|
+
? T extends Keyword
|
|
261
|
+
? TableKeyFromToken<Next, S>
|
|
262
|
+
: TableAfter<[Next, ...Rest], Keyword, S>
|
|
263
|
+
: never;
|
|
264
|
+
|
|
265
|
+
// Aliases
|
|
266
|
+
|
|
267
|
+
export type AliasEntry<Alias extends string, TableKey extends string> = `${CleanIdent<Alias>}=>${TableKey}`;
|
|
268
|
+
|
|
269
|
+
export type IsAliasCandidate<Token extends string> =
|
|
270
|
+
Token extends "" ? false :
|
|
271
|
+
Token extends SqlKeyword ? false :
|
|
272
|
+
true;
|
|
273
|
+
|
|
274
|
+
export type AliasNames<Aliases extends string> =
|
|
275
|
+
Aliases extends `${infer A}=>${string}` ? A : never;
|
|
276
|
+
|
|
277
|
+
export type IsAliasName<Token extends string, Aliases extends string> =
|
|
278
|
+
Token extends AliasNames<Aliases> ? true : false;
|