@kuindji/typed-sql 0.3.0 → 0.4.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.
Files changed (41) hide show
  1. package/README.md +11 -3
  2. package/dist/columns.d.ts +11 -3
  3. package/dist/columns.d.ts.map +1 -1
  4. package/dist/expressions.d.ts +73 -8
  5. package/dist/expressions.d.ts.map +1 -1
  6. package/dist/parsing/extract.d.ts +13 -9
  7. package/dist/parsing/extract.d.ts.map +1 -1
  8. package/dist/parsing/normalize.d.ts +3 -1
  9. package/dist/parsing/normalize.d.ts.map +1 -1
  10. package/dist/parsing/pg-literals.d.ts +10 -2
  11. package/dist/parsing/pg-literals.d.ts.map +1 -1
  12. package/dist/parsing/split.d.ts +27 -3
  13. package/dist/parsing/split.d.ts.map +1 -1
  14. package/dist/parsing/string-utils.d.ts +2 -4
  15. package/dist/parsing/string-utils.d.ts.map +1 -1
  16. package/dist/parsing/tokenize.d.ts +27 -17
  17. package/dist/parsing/tokenize.d.ts.map +1 -1
  18. package/dist/partial.d.ts +6 -6
  19. package/dist/partial.d.ts.map +1 -1
  20. package/dist/tables.d.ts +58 -13
  21. package/dist/tables.d.ts.map +1 -1
  22. package/dist/validation/dispatch.d.ts +7 -5
  23. package/dist/validation/dispatch.d.ts.map +1 -1
  24. package/dist/validation/joins.d.ts +3 -3
  25. package/dist/validation/joins.d.ts.map +1 -1
  26. package/dist/validation/validate-columns.d.ts +14 -14
  27. package/dist/validation/validate-columns.d.ts.map +1 -1
  28. package/package.json +1 -1
  29. package/src/columns.ts +168 -32
  30. package/src/expressions.ts +550 -57
  31. package/src/parsing/extract.ts +72 -32
  32. package/src/parsing/normalize.ts +54 -8
  33. package/src/parsing/pg-literals.ts +32 -10
  34. package/src/parsing/split.ts +236 -72
  35. package/src/parsing/string-utils.ts +15 -15
  36. package/src/parsing/tokenize.ts +224 -146
  37. package/src/partial.ts +9 -15
  38. package/src/tables.ts +546 -214
  39. package/src/validation/dispatch.ts +58 -52
  40. package/src/validation/joins.ts +15 -19
  41. package/src/validation/validate-columns.ts +54 -64
package/src/tables.ts CHANGED
@@ -1,13 +1,34 @@
1
1
  import type { DatabaseSchema, NormalizeTableKey, TableExists } from "./schema.js";
2
- import type { CleanIdent, CommaSep, SplitOnDotClean, SqlKeyword, Tokenize, TokenizeTables } from "./parsing.js";
2
+ import type { CleanIdent, CollectorScanView, CollectorToken, CommaSep, SplitOnDotClean, SqlKeyword } from "./parsing.js";
3
3
 
4
4
  // Table and alias extraction
5
+ //
6
+ // The collectors walk the `CollectorScanView` STRING directly, word by word —
7
+ // replacing the old `SplitCollectorTokens` token-array build plus array-walking
8
+ // state machines (round-10): every array build/destructure step minted a
9
+ // unique-content tuple and its apparent-`Array` types, while a word-jump string
10
+ // walk interns its substrings and `[any, ...Steps]` counter tuples. The old
11
+ // walkers branched on the token at the current position plus 1–3 tokens of
12
+ // lookahead; here the lookahead is a `Mode` register — a keyword arms a mode and
13
+ // the decision fires when the NEXT kept token materializes (the round-9
14
+ // deferred-decision pattern). A word whose `CollectorToken` is `""` never
15
+ // occupied an array position, so it updates NO register (it does consume the
16
+ // step cap, exactly like the old split's per-word budget).
17
+ //
18
+ // Each walker is CHUNKED (the chunked-driver pattern): the worker walks 100
19
+ // words per evaluation and yields `{ __c: [state...] }`, and the driver
20
+ // re-invokes it with a fresh step counter — a whole-query walk in one tail
21
+ // evaluation exceeds TypeScript's conditional-evaluation budget on report-scale
22
+ // queries (TS2589). 20 chunks × 100 words = the old split's 2000-word cap: on
23
+ // the final chunk boundary the remainder is blobbed into ONE trailing token and
24
+ // dispatched in whatever state the walk had reached (the `*Final` arms), exactly
25
+ // like the old capped split + collector tail.
5
26
 
6
27
  export type TablesInQuery<N extends string, S extends DatabaseSchema> =
7
- CollectTables<TokenizeTables<N>, S, never, false, N extends `delete ${string}` ? true : false>;
28
+ CtDrive<CtWalk<CollectorScanView<N>, S, never, false, N extends `delete ${string}` ? true : false>, S>;
8
29
 
9
30
  export type AliasesInQuery<N extends string, S extends DatabaseSchema> =
10
- CollectAliases<TokenizeTables<N>, S, never, false, N extends `delete ${string}` ? true : false>;
31
+ CaDrive<CaWalk<CollectorScanView<N>, S, never, false, N extends `delete ${string}` ? true : false>, S>;
11
32
 
12
33
  export type TableKeyValid<Key extends string, S extends DatabaseSchema> =
13
34
  Key extends `${infer Schema}.${infer Table}`
@@ -33,185 +54,374 @@ export type ParseTableToken<Token extends string, S extends DatabaseSchema> =
33
54
 
34
55
  // Insert/update/delete target table
35
56
 
57
+ // The target scan walks the RAW normalized query (never comma-marked — same as
58
+ // the old `SplitCollectorTokens<N>` path) and STOPS at the token after the
59
+ // keyword: `insert into orders ...` resolves in two words instead of first
60
+ // splitting the whole query into a token array.
36
61
  export type InsertTargetTable<N extends string, S extends DatabaseSchema> =
37
- TableAfter<Tokenize<N>, "into", S>;
62
+ TableAfterScan<N, "into", S>;
38
63
 
39
64
  export type UpdateTargetTable<N extends string, S extends DatabaseSchema> =
40
- TableAfter<Tokenize<N>, "update", S>;
65
+ TableAfterScan<N, "update", S>;
41
66
 
42
67
  export type DeleteTargetTable<N extends string, S extends DatabaseSchema> =
43
- TableAfter<Tokenize<N>, "from", S>;
68
+ TableAfterScan<N, "from", S>;
44
69
 
45
70
  // Collect tables by keyword
46
71
 
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[],
72
+ // State registers (mirroring the old array walker exactly):
73
+ // - `InList` tracks whether we are inside a FROM-source list, so a TOP-LEVEL
74
+ // comma (preserved as a `CommaSep` token by `CollectorScanView`) introduces
75
+ // ANOTHER table source the ANSI comma cross-join `from a, b`. The flag is
76
+ // turned on after a `from`/`join`/`into`/`update` source and off at the next
77
+ // clause keyword, so commas in the SELECT list / GROUP BY / ORDER BY / value
78
+ // tuples are ignored.
79
+ // - `InDelete` marks that we are inside a DELETE statement, where `USING` is a
80
+ // table-source clause (`DELETE FROM a USING b, c`) collected like FROM/JOIN.
81
+ // `USING` in a SELECT (the JOIN ... USING (cols) join condition) is NOT a
82
+ // table source, so the branch is gated: outside a DELETE, `using` is skipped.
83
+ // - `Mode` is the armed-keyword state: "src" = saw from|join|into (next token is
84
+ // the source), "usingsrc" = saw DELETE-using (next token collected with NO
85
+ // keyword/lateral skip — the old branch collected unconditionally), "upd" =
86
+ // saw update, "del"/"delfrom" = DELETE prefix, "comma" = top-level comma in a
87
+ // source list (next token is the candidate), "commaeq" = comma candidate seen
88
+ // (held in `Pend`; an `=` after it marks an UPDATE SET-list separator, not a
89
+ // source — `UPDATE t SET a = (select ... from x), b = ...`), "dist" = saw
90
+ // `distinct` (a following `from` is the `IS [NOT] DISTINCT FROM` operator
91
+ // tail, not a FROM clause).
92
+ // The never-guard matters: a completed walk returns `never` for a query with no
93
+ // sources, and `[never]` matches ANY wrapped pattern — without the guard the
94
+ // `__c` infers fall back to their `string` constraints and the driver re-walks a
95
+ // wide string, returning `string` (which poisons every downstream qualifier
96
+ // match).
97
+ type CtDrive<R, S extends DatabaseSchema, C extends any[] = []> =
98
+ [R] extends [never]
99
+ ? never
100
+ : [R] extends [{ __c: [infer V extends string, infer Acc extends string, infer IL extends boolean, infer ID extends boolean, infer Mode extends string, infer Pend extends string] }]
101
+ ? C["length"] extends 19
102
+ ? CtFinal<V, S, Acc, Mode, Pend>
103
+ : CtDrive<CtWalk<V, S, Acc, IL, ID, Mode, Pend>, S, [any, ...C]>
104
+ : R;
105
+
106
+ type CtWalk<
107
+ V extends string,
59
108
  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
- // A parenthesised FROM/JOIN source — a subquery (`from (select ...)`)
74
- // or VALUES list (`from (values ...)`) — has its `(` stripped by
75
- // `TokenizeTables`, leaving a leading SQL keyword (`select`/`values`)
76
- // as the source token. That keyword is NOT a base table; collecting it
77
- // fabricates a bogus `public.select`/`public.values` key that fails the
78
- // existence check. A real (unquoted) table is never a SQL keyword, so
79
- // skipping keyword sources here is safe; the source's `) AS alias` is
80
- // handled leniently in qualified-ref validation.
81
- : Next extends SqlKeyword
82
- ? CollectTables<Rest, S, Acc, true, InDelete>
83
- : CollectTables<Rest, S, Acc | TableKeyFromToken<Next, S>, true, InDelete>
84
- : T extends "update"
85
- ? Next extends "set"
86
- ? CollectTables<Rest, S, Acc, false, InDelete>
87
- : CollectTables<Rest, S, Acc | TableKeyFromToken<Next, S>, true, InDelete>
88
- : T extends "delete"
89
- ? Next extends "from"
90
- ? Rest extends [infer DelTable extends string, ...infer Rest2 extends string[]]
91
- ? CollectTables<Rest2, S, Acc | TableKeyFromToken<DelTable, S>, false, true>
92
- : Acc
93
- : CollectTables<[Next, ...Rest], S, Acc, false, true>
94
- : T extends "using"
95
- ? InDelete extends true
96
- ? CollectTables<Rest, S, Acc | TableKeyFromToken<Next, S>, true, InDelete>
97
- : CollectTables<[Next, ...Rest], S, Acc, InList, InDelete>
98
- : T extends CommaSep
99
- ? InList extends true
100
- // `UPDATE t SET a = (select ... from x), b = ...` — the
101
- // subquery's parens are stripped by tokenization, so its
102
- // `from x` leaves `InList` on and the TOP-LEVEL SET comma
103
- // would collect the next SET column (`b`) as a table. A
104
- // FROM-source name is never followed by `=`, so a comma
105
- // whose candidate source is followed by `=` is a SET-list
106
- // separator: skip it and leave source-list mode.
107
- ? Rest extends ["=", ...infer Rest2 extends string[]]
108
- ? CollectTables<Rest2, S, Acc, false, InDelete>
109
- : CollectTables<Rest, S, Acc | TableKeyFromToken<Next, S>, true, InDelete>
110
- : CollectTables<[Next, ...Rest], S, Acc, false, InDelete>
111
- : T extends "as"
112
- ? CollectTables<[Next, ...Rest], S, Acc, InList, InDelete>
113
- // `IS [NOT] DISTINCT FROM` is a comparison
114
- // operator, not a FROM clause: the `from` after
115
- // `distinct` must NOT be collected as a table
116
- // source. Drop the operator `from` (process
117
- // `Rest`) so its RHS isn't mistaken for a table.
118
- : T extends "distinct"
119
- ? Next extends "from"
120
- ? CollectTables<Rest, S, Acc, false, InDelete>
121
- : CollectTables<[Next, ...Rest], S, Acc, false, InDelete>
122
- : T extends SqlKeyword
123
- ? CollectTables<[Next, ...Rest], S, Acc, false, InDelete>
124
- : CollectTables<[Next, ...Rest], S, Acc, InList, InDelete>
125
- : Acc;
109
+ Acc extends string,
110
+ InList extends boolean,
111
+ InDelete extends boolean,
112
+ Mode extends string = "",
113
+ Pend extends string = "",
114
+ Steps extends any[] = []
115
+ > = Steps["length"] extends 100
116
+ ? { __c: [V, Acc, InList, InDelete, Mode, Pend] }
117
+ : V extends `${infer H} ${infer R}`
118
+ ? CollectorToken<H> extends infer M extends string
119
+ ? M extends ""
120
+ ? CtWalk<R, S, Acc, InList, InDelete, Mode, Pend, [any, ...Steps]>
121
+ : CtTok<M, R, S, Acc, InList, InDelete, Mode, Pend, Steps>
122
+ : never
123
+ : CtFinal<V, S, Acc, Mode, Pend>;
126
124
 
127
- // Collect aliases for tables in FROM/JOIN/UPDATE
125
+ type CtTok<
126
+ M extends string,
127
+ R extends string,
128
+ S extends DatabaseSchema,
129
+ Acc extends string,
130
+ InList extends boolean,
131
+ InDelete extends boolean,
132
+ Mode extends string,
133
+ Pend extends string,
134
+ Steps extends any[]
135
+ > = Mode extends ""
136
+ ? CtNorm<M, R, S, Acc, InList, InDelete, Steps>
137
+ : Mode extends "src"
138
+ // `JOIN LATERAL (subquery|func(...)) alias` — `LATERAL` is a source
139
+ // modifier, not a relation. A parenthesised FROM/JOIN source — a
140
+ // subquery (`from (select ...)`) or VALUES list — has its `(` stripped
141
+ // by tokenization, leaving a leading SQL keyword as the source token;
142
+ // collecting it would fabricate a bogus `public.select`/`public.values`
143
+ // key. A real (unquoted) table is never a SQL keyword, so both skip.
144
+ ? M extends "lateral" | SqlKeyword
145
+ ? CtWalk<R, S, Acc, true, InDelete, "", "", [any, ...Steps]>
146
+ : CtWalk<R, S, Acc | TableKeyFromToken<M, S>, true, InDelete, "", "", [any, ...Steps]>
147
+ : Mode extends "usingsrc"
148
+ ? CtWalk<R, S, Acc | TableKeyFromToken<M, S>, true, InDelete, "", "", [any, ...Steps]>
149
+ : Mode extends "upd"
150
+ ? M extends "set"
151
+ ? CtWalk<R, S, Acc, false, InDelete, "", "", [any, ...Steps]>
152
+ : CtWalk<R, S, Acc | TableKeyFromToken<M, S>, true, InDelete, "", "", [any, ...Steps]>
153
+ : Mode extends "del"
154
+ ? M extends "from"
155
+ ? CtWalk<R, S, Acc, false, true, "delfrom", "", [any, ...Steps]>
156
+ : CtNorm<M, R, S, Acc, false, true, Steps>
157
+ : Mode extends "delfrom"
158
+ ? CtWalk<R, S, Acc | TableKeyFromToken<M, S>, false, true, "", "", [any, ...Steps]>
159
+ : Mode extends "comma"
160
+ ? CtWalk<R, S, Acc, InList, InDelete, "commaeq", M, [any, ...Steps]>
161
+ : Mode extends "commaeq"
162
+ // A FROM-source name is never followed by `=`, so a
163
+ // comma whose candidate is followed by `=` is a
164
+ // SET-list separator: drop it and leave source-list
165
+ // mode. Otherwise collect the candidate and
166
+ // re-dispatch the current token in normal mode.
167
+ ? M extends "="
168
+ ? CtWalk<R, S, Acc, false, InDelete, "", "", [any, ...Steps]>
169
+ : CtNorm<M, R, S, Acc | TableKeyFromToken<Pend, S>, true, InDelete, Steps>
170
+ : // "dist": `IS [NOT] DISTINCT FROM` — the `from`
171
+ // after `distinct` is operator text; drop it so
172
+ // its RHS isn't mistaken for a table.
173
+ M extends "from"
174
+ ? CtWalk<R, S, Acc, false, InDelete, "", "", [any, ...Steps]>
175
+ : CtNorm<M, R, S, Acc, false, InDelete, Steps>;
128
176
 
129
- // `InList` mirrors `CollectTables`: after a `from`/`join`/`update` source, a
130
- // top-level `,` introduces another aliased source (`from users u, orders o`).
131
- // `InDelete` likewise mirrors `CollectTables`: inside a DELETE, `USING` opens an
132
- // aliased table source (`DELETE FROM a USING users u`); outside one it is left
133
- // alone (the JOIN ... USING (cols) join condition is not a source).
134
- export type CollectAliases<
135
- Tokens extends string[],
177
+ type CtNorm<
178
+ M extends string,
179
+ R extends string,
136
180
  S extends DatabaseSchema,
137
- Acc extends string = never,
138
- InList extends boolean = false,
139
- InDelete extends boolean = false
140
- > =
141
- Tokens extends [infer T extends string, infer Next extends string, ...infer Rest extends string[]]
142
- ? T extends "from" | "join" | "update"
143
- // `JOIN LATERAL (...)` — mirror `CollectTables`: skip the `lateral`
144
- // modifier so it is never parsed as an aliased table source.
145
- ? Next extends "lateral"
146
- ? CollectAliases<Rest, S, Acc, true, InDelete>
147
- // Mirror `CollectTables`: a parenthesised subquery/VALUES source has
148
- // its `(` stripped, leaving a leading SQL keyword token. It is not a
149
- // base-table source, so skip it rather than register a garbage alias
150
- // from the keyword + the next token.
151
- : Next extends SqlKeyword
152
- ? CollectAliases<Rest, S, Acc, true, InDelete>
153
- : ParseAliasSource<Next, Rest, S, Acc, InDelete>
154
- : T extends "using"
181
+ Acc extends string,
182
+ InList extends boolean,
183
+ InDelete extends boolean,
184
+ Steps extends any[]
185
+ > = M extends "from" | "join" | "into"
186
+ ? CtWalk<R, S, Acc, InList, InDelete, "src", "", [any, ...Steps]>
187
+ : M extends "update"
188
+ ? CtWalk<R, S, Acc, InList, InDelete, "upd", "", [any, ...Steps]>
189
+ : M extends "delete"
190
+ ? CtWalk<R, S, Acc, false, true, "del", "", [any, ...Steps]>
191
+ : M extends "using"
155
192
  ? InDelete extends true
156
- ? ParseAliasSource<Next, Rest, S, Acc, InDelete>
157
- : CollectAliases<[Next, ...Rest], S, Acc, InList, InDelete>
158
- : T extends CommaSep
193
+ ? CtWalk<R, S, Acc, InList, InDelete, "usingsrc", "", [any, ...Steps]>
194
+ : CtWalk<R, S, Acc, InList, InDelete, "", "", [any, ...Steps]>
195
+ : M extends CommaSep
159
196
  ? InList extends true
160
- // Mirror `CollectTables`: a comma whose candidate source is
161
- // followed by `=` is an UPDATE SET-list separator (the SET
162
- // subquery's `from` left `InList` on), not another aliased
163
- // FROM source.
164
- ? Rest extends ["=", ...infer Rest2 extends string[]]
165
- ? CollectAliases<Rest2, S, Acc, false, InDelete>
166
- : ParseAliasSource<Next, Rest, S, Acc, InDelete>
167
- : CollectAliases<[Next, ...Rest], S, Acc, false, InDelete>
168
- : T extends "as"
169
- ? CollectAliases<[Next, ...Rest], S, Acc, InList, InDelete>
170
- // `IS [NOT] DISTINCT FROM`: the operator `from` is not a
171
- // table source, so it must not open an aliased source.
172
- : T extends "distinct"
173
- ? Next extends "from"
174
- ? CollectAliases<Rest, S, Acc, false, InDelete>
175
- : CollectAliases<[Next, ...Rest], S, Acc, false, InDelete>
176
- : T extends SqlKeyword
177
- ? CollectAliases<[Next, ...Rest], S, Acc, false, InDelete>
178
- : CollectAliases<[Next, ...Rest], S, Acc, InList, InDelete>
179
- : Acc;
197
+ ? CtWalk<R, S, Acc, InList, InDelete, "comma", "", [any, ...Steps]>
198
+ : CtWalk<R, S, Acc, false, InDelete, "", "", [any, ...Steps]>
199
+ : M extends "as"
200
+ ? CtWalk<R, S, Acc, InList, InDelete, "", "", [any, ...Steps]>
201
+ : M extends "distinct"
202
+ ? CtWalk<R, S, Acc, false, InDelete, "dist", "", [any, ...Steps]>
203
+ : M extends SqlKeyword
204
+ ? CtWalk<R, S, Acc, false, InDelete, "", "", [any, ...Steps]>
205
+ : CtWalk<R, S, Acc, InList, InDelete, "", "", [any, ...Steps]>;
206
+
207
+ // Final word (or capped remainder) as one token. Modes that would consume it as
208
+ // their armed lookahead apply their collection effect; everything else has no
209
+ // effect at end of stream (a trailing keyword never had a `Next` to act on in
210
+ // the old pairwise array match either).
211
+ type CtFinal<
212
+ H extends string,
213
+ S extends DatabaseSchema,
214
+ Acc extends string,
215
+ Mode extends string,
216
+ Pend extends string
217
+ > = CollectorToken<H> extends infer M extends string
218
+ ? M extends ""
219
+ ? CtEnd<S, Acc, Mode, Pend>
220
+ : Mode extends "src"
221
+ ? M extends "lateral" | SqlKeyword
222
+ ? Acc
223
+ : Acc | TableKeyFromToken<M, S>
224
+ : Mode extends "usingsrc" | "delfrom" | "comma"
225
+ ? Acc | TableKeyFromToken<M, S>
226
+ : Mode extends "upd"
227
+ ? M extends "set"
228
+ ? Acc
229
+ : Acc | TableKeyFromToken<M, S>
230
+ : Mode extends "commaeq"
231
+ ? M extends "="
232
+ ? Acc
233
+ : Acc | TableKeyFromToken<Pend, S>
234
+ : Acc
235
+ : never;
236
+
237
+ // Stream ended on a dropped word: only a pending comma candidate still owes its
238
+ // collection (the old `[..., comma, cand]` tail collected `cand`).
239
+ type CtEnd<S extends DatabaseSchema, Acc extends string, Mode extends string, Pend extends string> =
240
+ Mode extends "commaeq" ? Acc | TableKeyFromToken<Pend, S> : Acc;
241
+
242
+ // Collect aliases for tables in FROM/JOIN/UPDATE
243
+
244
+ // `InList`/`InDelete` mirror the tables walker. Additional registers: `TK` holds
245
+ // the table key of the source whose alias position we are in ("alias" mode = the
246
+ // old `ParseAliasSource` MaybeAlias position; "aliasname" = after its `as`), and
247
+ // `Pend` holds a comma candidate awaiting the `=` SET-list check.
248
+ type CaDrive<R, S extends DatabaseSchema, C extends any[] = []> =
249
+ [R] extends [never]
250
+ ? never
251
+ : [R] extends [{ __c: [infer V extends string, infer Acc extends string, infer IL extends boolean, infer ID extends boolean, infer Mode extends string, infer TK extends string, infer Pend extends string] }]
252
+ ? C["length"] extends 19
253
+ ? CaFinal<V, S, Acc, ID, Mode, TK, Pend>
254
+ : CaDrive<CaWalk<V, S, Acc, IL, ID, Mode, TK, Pend>, S, [any, ...C]>
255
+ : R;
256
+
257
+ type CaWalk<
258
+ V extends string,
259
+ S extends DatabaseSchema,
260
+ Acc extends string,
261
+ InList extends boolean,
262
+ InDelete extends boolean,
263
+ Mode extends string = "",
264
+ TK extends string = never,
265
+ Pend extends string = "",
266
+ Steps extends any[] = []
267
+ > = Steps["length"] extends 100
268
+ ? { __c: [V, Acc, InList, InDelete, Mode, TK, Pend] }
269
+ : V extends `${infer H} ${infer R}`
270
+ ? CollectorToken<H> extends infer M extends string
271
+ ? M extends ""
272
+ ? CaWalk<R, S, Acc, InList, InDelete, Mode, TK, Pend, [any, ...Steps]>
273
+ : CaTok<M, R, S, Acc, InList, InDelete, Mode, TK, Pend, Steps>
274
+ : never
275
+ : CaFinal<V, S, Acc, InDelete, Mode, TK, Pend>;
276
+
277
+ type CaTok<
278
+ M extends string,
279
+ R extends string,
280
+ S extends DatabaseSchema,
281
+ Acc extends string,
282
+ InList extends boolean,
283
+ InDelete extends boolean,
284
+ Mode extends string,
285
+ TK extends string,
286
+ Pend extends string,
287
+ Steps extends any[]
288
+ > = Mode extends ""
289
+ ? CaNorm<M, R, S, Acc, InList, InDelete, Steps>
290
+ : Mode extends "src"
291
+ // Mirror the tables walker: skip `lateral` and keyword sources (a
292
+ // parenthesised subquery/VALUES source must not register a garbage
293
+ // alias from the keyword + the next token).
294
+ ? M extends "lateral" | SqlKeyword
295
+ ? CaWalk<R, S, Acc, true, InDelete, "", never, "", [any, ...Steps]>
296
+ : CaEnterAlias<M, R, S, Acc, InDelete, Steps>
297
+ : Mode extends "usingsrc"
298
+ ? CaEnterAlias<M, R, S, Acc, InDelete, Steps>
299
+ : Mode extends "alias"
300
+ ? CaAliasTok<M, R, S, Acc, InDelete, TK, Steps>
301
+ : Mode extends "aliasname"
302
+ ? CaWalk<R, S, Acc | AliasEntry<M, TK>, true, InDelete, "", never, "", [any, ...Steps]>
303
+ : Mode extends "comma"
304
+ ? CaWalk<R, S, Acc, InList, InDelete, "commaeq", never, M, [any, ...Steps]>
305
+ : Mode extends "commaeq"
306
+ // Mirror the tables walker: a comma whose candidate is
307
+ // followed by `=` is an UPDATE SET-list separator, not
308
+ // another aliased FROM source.
309
+ ? M extends "="
310
+ ? CaWalk<R, S, Acc, false, InDelete, "", never, "", [any, ...Steps]>
311
+ : TableKeyFromToken<Pend, S> extends infer TK2 extends string
312
+ ? CaAliasTok<M, R, S, Acc, InDelete, TK2, Steps>
313
+ : CaNorm<M, R, S, Acc, true, InDelete, Steps>
314
+ : // "dist": the `IS [NOT] DISTINCT FROM` operator
315
+ // `from` must not open an aliased source.
316
+ M extends "from"
317
+ ? CaWalk<R, S, Acc, false, InDelete, "", never, "", [any, ...Steps]>
318
+ : CaNorm<M, R, S, Acc, false, InDelete, Steps>;
319
+
320
+ // Enter the alias position for source token `M` (the old `ParseAliasSource`
321
+ // head: resolve the table key, then judge the next token as MaybeAlias).
322
+ type CaEnterAlias<
323
+ M extends string,
324
+ R extends string,
325
+ S extends DatabaseSchema,
326
+ Acc extends string,
327
+ InDelete extends boolean,
328
+ Steps extends any[]
329
+ > = TableKeyFromToken<M, S> extends infer TK extends string
330
+ ? CaWalk<R, S, Acc, true, InDelete, "alias", TK, "", [any, ...Steps]>
331
+ : CaWalk<R, S, Acc, true, InDelete, "", never, "", [any, ...Steps]>;
332
+
333
+ // The MaybeAlias judgment (old `ParseAliasSource` body): `as` arms the explicit
334
+ // alias name; an immediate comma is the next source (no alias); inside a DELETE
335
+ // a following `using` opens the next table source rather than naming this one;
336
+ // a bare candidate (non-keyword) is the alias; any other keyword re-dispatches
337
+ // in normal mode with `InList` on.
338
+ type CaAliasTok<
339
+ M extends string,
340
+ R extends string,
341
+ S extends DatabaseSchema,
342
+ Acc extends string,
343
+ InDelete extends boolean,
344
+ TK extends string,
345
+ Steps extends any[]
346
+ > = M extends "as"
347
+ ? CaWalk<R, S, Acc, true, InDelete, "aliasname", TK, "", [any, ...Steps]>
348
+ : M extends CommaSep
349
+ ? CaWalk<R, S, Acc, true, InDelete, "comma", never, "", [any, ...Steps]>
350
+ : InDelete extends true
351
+ ? M extends "using"
352
+ ? CaWalk<R, S, Acc, true, InDelete, "usingsrc", never, "", [any, ...Steps]>
353
+ : IsAliasCandidate<M> extends true
354
+ ? CaWalk<R, S, Acc | AliasEntry<M, TK>, true, InDelete, "", never, "", [any, ...Steps]>
355
+ : CaNorm<M, R, S, Acc, true, InDelete, Steps>
356
+ : IsAliasCandidate<M> extends true
357
+ ? CaWalk<R, S, Acc | AliasEntry<M, TK>, true, InDelete, "", never, "", [any, ...Steps]>
358
+ : CaNorm<M, R, S, Acc, true, InDelete, Steps>;
180
359
 
181
- // Parse a single table source (`Next`) plus its optional alias from the tokens
182
- // that follow it (`Rest`), record the alias, then continue collecting with
183
- // `InList=true` so a subsequent top-level comma is recognized as another source.
184
- // An immediate `,` after the table (no alias) is handed back to `CollectAliases`
185
- // rather than mistaken for an alias.
186
- export type ParseAliasSource<
187
- Next extends string,
188
- Rest extends string[],
360
+ type CaNorm<
361
+ M extends string,
362
+ R extends string,
189
363
  S extends DatabaseSchema,
190
364
  Acc extends string,
191
- InDelete extends boolean = false
192
- > =
193
- TableKeyFromToken<Next, S> extends infer TableKey extends string
194
- ? Rest extends [infer MaybeAlias extends string, ...infer Rest2 extends string[]]
195
- ? MaybeAlias extends "as"
196
- ? Rest2 extends [infer Alias extends string, ...infer Rest3 extends string[]]
197
- ? CollectAliases<Rest3, S, Acc | AliasEntry<Alias, TableKey>, true, InDelete>
365
+ InList extends boolean,
366
+ InDelete extends boolean,
367
+ Steps extends any[]
368
+ > = M extends "from" | "join" | "update"
369
+ ? CaWalk<R, S, Acc, InList, InDelete, "src", never, "", [any, ...Steps]>
370
+ : M extends "using"
371
+ ? InDelete extends true
372
+ ? CaWalk<R, S, Acc, InList, InDelete, "usingsrc", never, "", [any, ...Steps]>
373
+ : CaWalk<R, S, Acc, InList, InDelete, "", never, "", [any, ...Steps]>
374
+ : M extends CommaSep
375
+ ? InList extends true
376
+ ? CaWalk<R, S, Acc, InList, InDelete, "comma", never, "", [any, ...Steps]>
377
+ : CaWalk<R, S, Acc, false, InDelete, "", never, "", [any, ...Steps]>
378
+ : M extends "as"
379
+ ? CaWalk<R, S, Acc, InList, InDelete, "", never, "", [any, ...Steps]>
380
+ : M extends "distinct"
381
+ ? CaWalk<R, S, Acc, false, InDelete, "dist", never, "", [any, ...Steps]>
382
+ : M extends SqlKeyword
383
+ ? CaWalk<R, S, Acc, false, InDelete, "", never, "", [any, ...Steps]>
384
+ : CaWalk<R, S, Acc, InList, InDelete, "", never, "", [any, ...Steps]>;
385
+
386
+ // Final word as one token. Only the alias-name positions still owe a recording;
387
+ // a bare source at end of stream has no alias to record (old `ParseAliasSource`
388
+ // with an empty Rest returned `Acc`).
389
+ type CaFinal<
390
+ H extends string,
391
+ S extends DatabaseSchema,
392
+ Acc extends string,
393
+ InDelete extends boolean,
394
+ Mode extends string,
395
+ TK extends string,
396
+ Pend extends string
397
+ > = CollectorToken<H> extends infer M extends string
398
+ ? M extends ""
399
+ ? Acc
400
+ : Mode extends "alias"
401
+ ? CaAliasFinal<M, Acc, InDelete, TK>
402
+ : Mode extends "aliasname"
403
+ ? Acc | AliasEntry<M, TK>
404
+ : Mode extends "commaeq"
405
+ ? M extends "="
406
+ ? Acc
407
+ : TableKeyFromToken<Pend, S> extends infer TK2 extends string
408
+ ? CaAliasFinal<M, Acc, InDelete, TK2>
409
+ : Acc
198
410
  : Acc
199
- : MaybeAlias extends CommaSep
200
- ? CollectAliases<Rest, S, Acc, true, InDelete>
201
- // Inside a DELETE, a following `using` is not this source's
202
- // alias it opens the next table source. Hand it back so
203
- // `CollectAliases` processes the USING clause.
204
- : InDelete extends true
205
- ? MaybeAlias extends "using"
206
- ? CollectAliases<Rest, S, Acc, true, InDelete>
207
- : IsAliasCandidate<MaybeAlias> extends true
208
- ? CollectAliases<Rest2, S, Acc | AliasEntry<MaybeAlias, TableKey>, true, InDelete>
209
- : CollectAliases<Rest, S, Acc, true, InDelete>
210
- : IsAliasCandidate<MaybeAlias> extends true
211
- ? CollectAliases<Rest2, S, Acc | AliasEntry<MaybeAlias, TableKey>, true, InDelete>
212
- : CollectAliases<Rest, S, Acc, true, InDelete>
213
- : Acc
214
- : CollectAliases<Rest, S, Acc, true, InDelete>;
411
+ : never;
412
+
413
+ type CaAliasFinal<M extends string, Acc extends string, InDelete extends boolean, TK extends string> =
414
+ M extends "as" | CommaSep
415
+ ? Acc
416
+ : InDelete extends true
417
+ ? M extends "using"
418
+ ? Acc
419
+ : IsAliasCandidate<M> extends true
420
+ ? Acc | AliasEntry<M, TK>
421
+ : Acc
422
+ : IsAliasCandidate<M> extends true
423
+ ? Acc | AliasEntry<M, TK>
424
+ : Acc;
215
425
 
216
426
  // Outer-join nullability
217
427
  //
@@ -228,69 +438,191 @@ export type ParseAliasSource<
228
438
  // (Limitation: an UNqualified projected column from an outer-joined relation is
229
439
  // not nullablized, since it carries no qualifier to match.)
230
440
  export type NullableRelations<N extends string, S extends DatabaseSchema> =
231
- CollectNullable<TokenizeTables<N>, "none", never, never>;
441
+ CnDrive<CnWalk<CollectorScanView<N>, "none", never, never>>;
232
442
 
233
443
  // `Mod` is the pending join modifier ("left"/"right"/"full"/"none"); `Left` is
234
444
  // the set of qualifiers accumulated so far (the left side of any later join);
235
445
  // `Acc` is the nullable-qualifier accumulator. `outer` is noise (keeps `Mod`);
236
446
  // `inner`/`cross` reset `Mod` to "none". On a `join <table>`: LEFT adds the
237
447
  // joined relation; RIGHT adds the accumulated left side; FULL adds both.
238
- export type CollectNullable<
239
- Tokens extends string[],
448
+ //
449
+ // Modes: "nsrc-f"/"nsrc-j" = saw from|into / join (next token is the relation,
450
+ // stored in `Tbl`); "qual-f"/"qual-j" = peeking at the token after the relation
451
+ // to pick its qualifier (alias if a candidate follows, else the table name —
452
+ // the old `SourceQualifier` lookahead, which did NOT consume those tokens: the
453
+ // peeked token is re-dispatched in normal mode after the qualifier applies);
454
+ // "qualas-f"/"qualas-j" = the peek saw `as`, the next token is the alias name.
455
+ type CnDrive<R, C extends any[] = []> =
456
+ [R] extends [never]
457
+ ? never
458
+ : [R] extends [{ __c: [infer V extends string, infer Mod extends string, infer Left extends string, infer Acc extends string, infer Mode extends string, infer Tbl extends string] }]
459
+ ? C["length"] extends 19
460
+ ? CnFinal<V, Mod, Left, Acc, Mode, Tbl>
461
+ : CnDrive<CnWalk<V, Mod, Left, Acc, Mode, Tbl>, [any, ...C]>
462
+ : R;
463
+
464
+ type CnWalk<
465
+ V extends string,
240
466
  Mod extends string,
241
467
  Left extends string,
242
- Acc extends string
243
- > =
244
- Tokens extends [infer T extends string, ...infer Rest extends string[]]
245
- ? T extends "left"
246
- ? CollectNullable<Rest, "left", Left, Acc>
247
- : T extends "right"
248
- ? CollectNullable<Rest, "right", Left, Acc>
249
- : T extends "full"
250
- ? CollectNullable<Rest, "full", Left, Acc>
251
- : T extends "inner" | "cross"
252
- ? CollectNullable<Rest, "none", Left, Acc>
253
- : T extends "outer"
254
- ? CollectNullable<Rest, Mod, Left, Acc>
255
- : T extends "from" | "into"
256
- ? Rest extends [infer Tbl extends string, ...infer R2 extends string[]]
257
- ? CollectNullable<R2, "none", Left | SourceQualifier<Tbl, R2>, Acc>
258
- : Acc
259
- : T extends "join"
260
- ? Rest extends [infer Tbl extends string, ...infer R2 extends string[]]
261
- ? SourceQualifier<Tbl, R2> extends infer Q extends string
262
- ? Mod extends "left"
263
- ? CollectNullable<R2, "none", Left | Q, Acc | Q>
264
- : Mod extends "right"
265
- ? CollectNullable<R2, "none", Left | Q, Acc | Left>
266
- : Mod extends "full"
267
- ? CollectNullable<R2, "none", Left | Q, Acc | Left | Q>
268
- : CollectNullable<R2, "none", Left | Q, Acc>
269
- : Acc
270
- : Acc
271
- : CollectNullable<Rest, Mod, Left, Acc>
272
- : Acc;
468
+ Acc extends string,
469
+ Mode extends string = "",
470
+ Tbl extends string = "",
471
+ Steps extends any[] = []
472
+ > = Steps["length"] extends 100
473
+ ? { __c: [V, Mod, Left, Acc, Mode, Tbl] }
474
+ : V extends `${infer H} ${infer R}`
475
+ ? CollectorToken<H> extends infer M extends string
476
+ ? M extends ""
477
+ ? CnWalk<R, Mod, Left, Acc, Mode, Tbl, [any, ...Steps]>
478
+ : CnTok<M, R, Mod, Left, Acc, Mode, Tbl, Steps>
479
+ : never
480
+ : CnFinal<V, Mod, Left, Acc, Mode, Tbl>;
481
+
482
+ type CnTok<
483
+ M extends string,
484
+ R extends string,
485
+ Mod extends string,
486
+ Left extends string,
487
+ Acc extends string,
488
+ Mode extends string,
489
+ Tbl extends string,
490
+ Steps extends any[]
491
+ > = Mode extends ""
492
+ ? CnNorm<M, R, Mod, Left, Acc, Steps>
493
+ : Mode extends "nsrc-f"
494
+ ? CnWalk<R, Mod, Left, Acc, "qual-f", M, [any, ...Steps]>
495
+ : Mode extends "nsrc-j"
496
+ ? CnWalk<R, Mod, Left, Acc, "qual-j", M, [any, ...Steps]>
497
+ : Mode extends "qual-f"
498
+ ? M extends "as"
499
+ ? CnWalk<R, Mod, Left, Acc, "qualas-f", Tbl, [any, ...Steps]>
500
+ : CnQualPick<M, Tbl> extends infer Q extends string
501
+ ? CnNorm<M, R, "none", Left | Q, Acc, Steps>
502
+ : never
503
+ : Mode extends "qual-j"
504
+ ? M extends "as"
505
+ ? CnWalk<R, Mod, Left, Acc, "qualas-j", Tbl, [any, ...Steps]>
506
+ : CnQualPick<M, Tbl> extends infer Q extends string
507
+ ? CnNorm<M, R, "none", Left | Q, CnJoinAcc<Mod, Left, Acc, Q>, Steps>
508
+ : never
509
+ : Mode extends "qualas-f"
510
+ ? CleanIdent<M> extends infer Q extends string
511
+ ? CnNorm<M, R, "none", Left | Q, Acc, Steps>
512
+ : never
513
+ : // "qualas-j"
514
+ CleanIdent<M> extends infer Q extends string
515
+ ? CnNorm<M, R, "none", Left | Q, CnJoinAcc<Mod, Left, Acc, Q>, Steps>
516
+ : never;
273
517
 
274
518
  // The qualifier a relation is referenced by in projections: its alias when one
275
- // is present (`... t`/`... as t`), else the (cleaned) table name. `Rest` are the
276
- // tokens following the table token. A following keyword (`on`, `where`, another
277
- // `join`, ...) is not an alias, so the table name is used.
278
- export type SourceQualifier<Tbl extends string, Rest extends string[]> =
279
- Rest extends ["as", infer A extends string, ...string[]]
280
- ? CleanIdent<A>
281
- : Rest extends [infer Maybe extends string, ...string[]]
282
- ? IsAliasCandidate<Maybe> extends true
283
- ? CleanIdent<Maybe>
284
- : CleanIdent<Tbl>
285
- : CleanIdent<Tbl>;
519
+ // is present, else the (cleaned) table name. A following keyword (`on`,
520
+ // `where`, another `join`, ...) is not an alias.
521
+ type CnQualPick<M extends string, Tbl extends string> =
522
+ IsAliasCandidate<M> extends true ? CleanIdent<M> : CleanIdent<Tbl>;
523
+
524
+ // `Acc | Left` for RIGHT uses the PRE-join `Left` (the joined relation itself
525
+ // is not nullablized by its own RIGHT join), exactly like the old arms.
526
+ type CnJoinAcc<Mod extends string, Left extends string, Acc extends string, Q extends string> =
527
+ Mod extends "left"
528
+ ? Acc | Q
529
+ : Mod extends "right"
530
+ ? Acc | Left
531
+ : Mod extends "full"
532
+ ? Acc | Left | Q
533
+ : Acc;
534
+
535
+ type CnNorm<
536
+ M extends string,
537
+ R extends string,
538
+ Mod extends string,
539
+ Left extends string,
540
+ Acc extends string,
541
+ Steps extends any[]
542
+ > = M extends "left" | "right" | "full"
543
+ ? CnWalk<R, M, Left, Acc, "", "", [any, ...Steps]>
544
+ : M extends "inner" | "cross"
545
+ ? CnWalk<R, "none", Left, Acc, "", "", [any, ...Steps]>
546
+ : M extends "outer"
547
+ ? CnWalk<R, Mod, Left, Acc, "", "", [any, ...Steps]>
548
+ : M extends "from" | "into"
549
+ ? CnWalk<R, Mod, Left, Acc, "nsrc-f", "", [any, ...Steps]>
550
+ : M extends "join"
551
+ ? CnWalk<R, Mod, Left, Acc, "nsrc-j", "", [any, ...Steps]>
552
+ : CnWalk<R, Mod, Left, Acc, "", "", [any, ...Steps]>;
553
+
554
+ // Final word as one token: a join relation (or its qualifier peek) at end of
555
+ // stream still applies its nullability effect; from-kind effects only update
556
+ // `Left`, which is dead at end of stream.
557
+ type CnFinal<
558
+ H extends string,
559
+ Mod extends string,
560
+ Left extends string,
561
+ Acc extends string,
562
+ Mode extends string,
563
+ Tbl extends string
564
+ > = CollectorToken<H> extends infer M extends string
565
+ ? M extends ""
566
+ ? CnEnd<Mod, Left, Acc, Mode, Tbl>
567
+ : Mode extends "nsrc-j"
568
+ ? CnJoinAcc<Mod, Left, Acc, CleanIdent<M>>
569
+ : Mode extends "qual-j"
570
+ ? M extends "as"
571
+ ? CnJoinAcc<Mod, Left, Acc, CleanIdent<Tbl>>
572
+ : CnJoinAcc<Mod, Left, Acc, CnQualPick<M, Tbl>>
573
+ : Mode extends "qualas-j"
574
+ ? CnJoinAcc<Mod, Left, Acc, CleanIdent<M>>
575
+ : Acc
576
+ : never;
577
+
578
+ type CnEnd<Mod extends string, Left extends string, Acc extends string, Mode extends string, Tbl extends string> =
579
+ Mode extends "qual-j" | "qualas-j"
580
+ ? CnJoinAcc<Mod, Left, Acc, CleanIdent<Tbl>>
581
+ : Acc;
286
582
 
287
583
  // Table lookup after a keyword
288
584
 
289
- export type TableAfter<Tokens extends string[], Keyword extends string, S extends DatabaseSchema> =
290
- Tokens extends [infer T extends string, infer Next extends string, ...infer Rest extends string[]]
291
- ? T extends Keyword
292
- ? TableKeyFromToken<Next, S>
293
- : TableAfter<[Next, ...Rest], Keyword, S>
585
+ // Early-terminating word scan: resolves the token after the FIRST `Keyword`
586
+ // token and stops it never walks the rest of the query.
587
+ type TableAfterScan<V extends string, Keyword extends string, S extends DatabaseSchema> =
588
+ TaDrive<TaWalk<V, Keyword, S>, Keyword, S>;
589
+
590
+ type TaDrive<R, Keyword extends string, S extends DatabaseSchema, C extends any[] = []> =
591
+ [R] extends [never]
592
+ ? never
593
+ : [R] extends [{ __c: [infer V extends string, infer Found extends boolean] }]
594
+ ? C["length"] extends 19
595
+ ? TaFinal<V, S, Found>
596
+ : TaDrive<TaWalk<V, Keyword, S, Found>, Keyword, S, [any, ...C]>
597
+ : R;
598
+
599
+ type TaWalk<
600
+ V extends string,
601
+ Keyword extends string,
602
+ S extends DatabaseSchema,
603
+ Found extends boolean = false,
604
+ Steps extends any[] = []
605
+ > = Steps["length"] extends 100
606
+ ? { __c: [V, Found] }
607
+ : V extends `${infer H} ${infer R}`
608
+ ? CollectorToken<H> extends infer M extends string
609
+ ? M extends ""
610
+ ? TaWalk<R, Keyword, S, Found, [any, ...Steps]>
611
+ : Found extends true
612
+ ? TableKeyFromToken<M, S>
613
+ : M extends Keyword
614
+ ? TaWalk<R, Keyword, S, true, [any, ...Steps]>
615
+ : TaWalk<R, Keyword, S, false, [any, ...Steps]>
616
+ : never
617
+ : TaFinal<V, S, Found>;
618
+
619
+ type TaFinal<H extends string, S extends DatabaseSchema, Found extends boolean> =
620
+ Found extends true
621
+ ? CollectorToken<H> extends infer M extends string
622
+ ? M extends ""
623
+ ? never
624
+ : TableKeyFromToken<M, S>
625
+ : never
294
626
  : never;
295
627
 
296
628
  // Aliases