@kuindji/typed-sql 0.2.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 (48) 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 +84 -13
  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 +9 -3
  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/return-derived.d.ts +8 -4
  27. package/dist/validation/return-derived.d.ts.map +1 -1
  28. package/dist/validation/return-derived.js.map +1 -1
  29. package/dist/validation/return-types.d.ts +1 -1
  30. package/dist/validation/return-types.d.ts.map +1 -1
  31. package/dist/validation/validate-columns.d.ts +27 -16
  32. package/dist/validation/validate-columns.d.ts.map +1 -1
  33. package/package.json +1 -1
  34. package/src/columns.ts +168 -32
  35. package/src/expressions.ts +589 -63
  36. package/src/parsing/extract.ts +72 -32
  37. package/src/parsing/normalize.ts +114 -10
  38. package/src/parsing/pg-literals.ts +32 -10
  39. package/src/parsing/split.ts +236 -72
  40. package/src/parsing/string-utils.ts +15 -15
  41. package/src/parsing/tokenize.ts +224 -146
  42. package/src/partial.ts +9 -15
  43. package/src/tables.ts +546 -183
  44. package/src/validation/dispatch.ts +58 -52
  45. package/src/validation/joins.ts +15 -19
  46. package/src/validation/return-derived.ts +60 -4
  47. package/src/validation/return-types.ts +9 -3
  48. package/src/validation/validate-columns.ts +161 -67
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,154 +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
- : 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;
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>;
107
124
 
108
- // 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>;
109
176
 
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[],
177
+ type CtNorm<
178
+ M extends string,
179
+ R extends string,
117
180
  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"
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"
130
192
  ? InDelete extends true
131
- ? ParseAliasSource<Next, Rest, S, Acc, InDelete>
132
- : CollectAliases<[Next, ...Rest], S, Acc, InList, InDelete>
133
- : 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
134
196
  ? 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;
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>;
149
359
 
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[],
360
+ type CaNorm<
361
+ M extends string,
362
+ R extends string,
158
363
  S extends DatabaseSchema,
159
364
  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>
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
167
410
  : 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>;
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;
184
425
 
185
426
  // Outer-join nullability
186
427
  //
@@ -197,69 +438,191 @@ export type ParseAliasSource<
197
438
  // (Limitation: an UNqualified projected column from an outer-joined relation is
198
439
  // not nullablized, since it carries no qualifier to match.)
199
440
  export type NullableRelations<N extends string, S extends DatabaseSchema> =
200
- CollectNullable<TokenizeTables<N>, "none", never, never>;
441
+ CnDrive<CnWalk<CollectorScanView<N>, "none", never, never>>;
201
442
 
202
443
  // `Mod` is the pending join modifier ("left"/"right"/"full"/"none"); `Left` is
203
444
  // the set of qualifiers accumulated so far (the left side of any later join);
204
445
  // `Acc` is the nullable-qualifier accumulator. `outer` is noise (keeps `Mod`);
205
446
  // `inner`/`cross` reset `Mod` to "none". On a `join <table>`: LEFT adds the
206
447
  // joined relation; RIGHT adds the accumulated left side; FULL adds both.
207
- export type CollectNullable<
208
- 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,
209
466
  Mod extends string,
210
467
  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;
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;
242
517
 
243
518
  // 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>;
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;
255
582
 
256
583
  // Table lookup after a keyword
257
584
 
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>
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
263
626
  : never;
264
627
 
265
628
  // Aliases