@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.
Files changed (208) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +227 -0
  3. package/dist/builder/assemble.d.ts +13 -0
  4. package/dist/builder/assemble.d.ts.map +1 -0
  5. package/dist/builder/assemble.js +86 -0
  6. package/dist/builder/assemble.js.map +1 -0
  7. package/dist/builder/condition-tree.d.ts +27 -0
  8. package/dist/builder/condition-tree.d.ts.map +1 -0
  9. package/dist/builder/condition-tree.js +91 -0
  10. package/dist/builder/condition-tree.js.map +1 -0
  11. package/dist/builder/conditional-sql.d.ts +80 -0
  12. package/dist/builder/conditional-sql.d.ts.map +1 -0
  13. package/dist/builder/conditional-sql.js +88 -0
  14. package/dist/builder/conditional-sql.js.map +1 -0
  15. package/dist/builder/db.d.ts +76 -0
  16. package/dist/builder/db.d.ts.map +1 -0
  17. package/dist/builder/db.js +12 -0
  18. package/dist/builder/db.js.map +1 -0
  19. package/dist/builder/delete.d.ts +39 -0
  20. package/dist/builder/delete.d.ts.map +1 -0
  21. package/dist/builder/delete.js +33 -0
  22. package/dist/builder/delete.js.map +1 -0
  23. package/dist/builder/extract-params.d.ts +97 -0
  24. package/dist/builder/extract-params.d.ts.map +1 -0
  25. package/dist/builder/extract-params.js +2 -0
  26. package/dist/builder/extract-params.js.map +1 -0
  27. package/dist/builder/index.d.ts +23 -0
  28. package/dist/builder/index.d.ts.map +1 -0
  29. package/dist/builder/index.js +14 -0
  30. package/dist/builder/index.js.map +1 -0
  31. package/dist/builder/insert.d.ts +51 -0
  32. package/dist/builder/insert.d.ts.map +1 -0
  33. package/dist/builder/insert.js +39 -0
  34. package/dist/builder/insert.js.map +1 -0
  35. package/dist/builder/mutate.d.ts +28 -0
  36. package/dist/builder/mutate.d.ts.map +1 -0
  37. package/dist/builder/mutate.js +17 -0
  38. package/dist/builder/mutate.js.map +1 -0
  39. package/dist/builder/params.d.ts +22 -0
  40. package/dist/builder/params.d.ts.map +1 -0
  41. package/dist/builder/params.js +65 -0
  42. package/dist/builder/params.js.map +1 -0
  43. package/dist/builder/return-type.d.ts +39 -0
  44. package/dist/builder/return-type.d.ts.map +1 -0
  45. package/dist/builder/return-type.js +2 -0
  46. package/dist/builder/return-type.js.map +1 -0
  47. package/dist/builder/scanner.d.ts +49 -0
  48. package/dist/builder/scanner.d.ts.map +1 -0
  49. package/dist/builder/scanner.js +240 -0
  50. package/dist/builder/scanner.js.map +1 -0
  51. package/dist/builder/select.d.ts +76 -0
  52. package/dist/builder/select.d.ts.map +1 -0
  53. package/dist/builder/select.js +240 -0
  54. package/dist/builder/select.js.map +1 -0
  55. package/dist/builder/sql-tag.d.ts +319 -0
  56. package/dist/builder/sql-tag.d.ts.map +1 -0
  57. package/dist/builder/sql-tag.js +3 -0
  58. package/dist/builder/sql-tag.js.map +1 -0
  59. package/dist/builder/sql.d.ts +17 -0
  60. package/dist/builder/sql.d.ts.map +1 -0
  61. package/dist/builder/sql.js +36 -0
  62. package/dist/builder/sql.js.map +1 -0
  63. package/dist/builder/state.d.ts +53 -0
  64. package/dist/builder/state.d.ts.map +1 -0
  65. package/dist/builder/state.js +18 -0
  66. package/dist/builder/state.js.map +1 -0
  67. package/dist/builder/update.d.ts +60 -0
  68. package/dist/builder/update.d.ts.map +1 -0
  69. package/dist/builder/update.js +40 -0
  70. package/dist/builder/update.js.map +1 -0
  71. package/dist/builder/write-assemble.d.ts +5 -0
  72. package/dist/builder/write-assemble.d.ts.map +1 -0
  73. package/dist/builder/write-assemble.js +57 -0
  74. package/dist/builder/write-assemble.js.map +1 -0
  75. package/dist/builder/write-state.d.ts +39 -0
  76. package/dist/builder/write-state.d.ts.map +1 -0
  77. package/dist/builder/write-state.js +6 -0
  78. package/dist/builder/write-state.js.map +1 -0
  79. package/dist/builder/write-tag.d.ts +91 -0
  80. package/dist/builder/write-tag.d.ts.map +1 -0
  81. package/dist/builder/write-tag.js +2 -0
  82. package/dist/builder/write-tag.js.map +1 -0
  83. package/dist/columns.d.ts +33 -0
  84. package/dist/columns.d.ts.map +1 -0
  85. package/dist/columns.js +2 -0
  86. package/dist/columns.js.map +1 -0
  87. package/dist/expressions.d.ts +71 -0
  88. package/dist/expressions.d.ts.map +1 -0
  89. package/dist/expressions.js +2 -0
  90. package/dist/expressions.js.map +1 -0
  91. package/dist/index.d.ts +22 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +5 -0
  94. package/dist/index.js.map +1 -0
  95. package/dist/parsing/extract.d.ts +47 -0
  96. package/dist/parsing/extract.d.ts.map +1 -0
  97. package/dist/parsing/extract.js +2 -0
  98. package/dist/parsing/extract.js.map +1 -0
  99. package/dist/parsing/normalize.d.ts +44 -0
  100. package/dist/parsing/normalize.d.ts.map +1 -0
  101. package/dist/parsing/normalize.js +2 -0
  102. package/dist/parsing/normalize.js.map +1 -0
  103. package/dist/parsing/pg-literals.d.ts +37 -0
  104. package/dist/parsing/pg-literals.d.ts.map +1 -0
  105. package/dist/parsing/pg-literals.js +2 -0
  106. package/dist/parsing/pg-literals.js.map +1 -0
  107. package/dist/parsing/split.d.ts +100 -0
  108. package/dist/parsing/split.d.ts.map +1 -0
  109. package/dist/parsing/split.js +2 -0
  110. package/dist/parsing/split.js.map +1 -0
  111. package/dist/parsing/string-utils.d.ts +29 -0
  112. package/dist/parsing/string-utils.d.ts.map +1 -0
  113. package/dist/parsing/string-utils.js +2 -0
  114. package/dist/parsing/string-utils.js.map +1 -0
  115. package/dist/parsing/tokenize.d.ts +27 -0
  116. package/dist/parsing/tokenize.d.ts.map +1 -0
  117. package/dist/parsing/tokenize.js +2 -0
  118. package/dist/parsing/tokenize.js.map +1 -0
  119. package/dist/parsing.d.ts +7 -0
  120. package/dist/parsing.d.ts.map +1 -0
  121. package/dist/parsing.js +9 -0
  122. package/dist/parsing.js.map +1 -0
  123. package/dist/partial.d.ts +30 -0
  124. package/dist/partial.d.ts.map +1 -0
  125. package/dist/partial.js +10 -0
  126. package/dist/partial.js.map +1 -0
  127. package/dist/schema.d.ts +28 -0
  128. package/dist/schema.d.ts.map +1 -0
  129. package/dist/schema.js +2 -0
  130. package/dist/schema.js.map +1 -0
  131. package/dist/tables.d.ts +34 -0
  132. package/dist/tables.d.ts.map +1 -0
  133. package/dist/tables.js +2 -0
  134. package/dist/tables.js.map +1 -0
  135. package/dist/utils.d.ts +13 -0
  136. package/dist/utils.d.ts.map +1 -0
  137. package/dist/utils.js +3 -0
  138. package/dist/utils.js.map +1 -0
  139. package/dist/validation/cte.d.ts +54 -0
  140. package/dist/validation/cte.d.ts.map +1 -0
  141. package/dist/validation/cte.js +2 -0
  142. package/dist/validation/cte.js.map +1 -0
  143. package/dist/validation/dispatch.d.ts +31 -0
  144. package/dist/validation/dispatch.d.ts.map +1 -0
  145. package/dist/validation/dispatch.js +2 -0
  146. package/dist/validation/dispatch.js.map +1 -0
  147. package/dist/validation/joins.d.ts +16 -0
  148. package/dist/validation/joins.d.ts.map +1 -0
  149. package/dist/validation/joins.js +2 -0
  150. package/dist/validation/joins.js.map +1 -0
  151. package/dist/validation/return-derived.d.ts +67 -0
  152. package/dist/validation/return-derived.d.ts.map +1 -0
  153. package/dist/validation/return-derived.js +5 -0
  154. package/dist/validation/return-derived.js.map +1 -0
  155. package/dist/validation/return-types.d.ts +41 -0
  156. package/dist/validation/return-types.d.ts.map +1 -0
  157. package/dist/validation/return-types.js +2 -0
  158. package/dist/validation/return-types.js.map +1 -0
  159. package/dist/validation/validate-columns.d.ts +63 -0
  160. package/dist/validation/validate-columns.d.ts.map +1 -0
  161. package/dist/validation/validate-columns.js +2 -0
  162. package/dist/validation/validate-columns.js.map +1 -0
  163. package/dist/validation.d.ts +7 -0
  164. package/dist/validation.d.ts.map +1 -0
  165. package/dist/validation.js +9 -0
  166. package/dist/validation.js.map +1 -0
  167. package/package.json +64 -0
  168. package/src/builder/assemble.ts +100 -0
  169. package/src/builder/condition-tree.ts +162 -0
  170. package/src/builder/conditional-sql.ts +325 -0
  171. package/src/builder/db.ts +281 -0
  172. package/src/builder/delete.ts +57 -0
  173. package/src/builder/extract-params.ts +507 -0
  174. package/src/builder/index.ts +58 -0
  175. package/src/builder/insert.ts +75 -0
  176. package/src/builder/mutate.ts +55 -0
  177. package/src/builder/params.ts +95 -0
  178. package/src/builder/return-type.ts +66 -0
  179. package/src/builder/scanner.ts +254 -0
  180. package/src/builder/select.ts +470 -0
  181. package/src/builder/sql-tag.ts +422 -0
  182. package/src/builder/sql.ts +51 -0
  183. package/src/builder/state.ts +55 -0
  184. package/src/builder/update.ts +77 -0
  185. package/src/builder/write-assemble.ts +52 -0
  186. package/src/builder/write-state.ts +43 -0
  187. package/src/builder/write-tag.ts +119 -0
  188. package/src/columns.ts +336 -0
  189. package/src/expressions.ts +745 -0
  190. package/src/index.ts +81 -0
  191. package/src/parsing/extract.ts +260 -0
  192. package/src/parsing/normalize.ts +243 -0
  193. package/src/parsing/pg-literals.ts +289 -0
  194. package/src/parsing/split.ts +288 -0
  195. package/src/parsing/string-utils.ts +172 -0
  196. package/src/parsing/tokenize.ts +321 -0
  197. package/src/parsing.ts +8 -0
  198. package/src/partial.ts +241 -0
  199. package/src/schema.ts +130 -0
  200. package/src/tables.ts +278 -0
  201. package/src/utils.ts +43 -0
  202. package/src/validation/cte.ts +198 -0
  203. package/src/validation/dispatch.ts +312 -0
  204. package/src/validation/joins.ts +198 -0
  205. package/src/validation/return-derived.ts +253 -0
  206. package/src/validation/return-types.ts +271 -0
  207. package/src/validation/validate-columns.ts +489 -0
  208. package/src/validation.ts +8 -0
package/src/utils.ts ADDED
@@ -0,0 +1,43 @@
1
+ // Shared boolean and type utilities.
2
+
3
+ export type And<
4
+ A extends boolean,
5
+ B extends boolean,
6
+ C extends boolean,
7
+ D extends boolean,
8
+ E extends boolean = true
9
+ > = A extends true
10
+ ? (B extends true
11
+ ? (C extends true
12
+ ? (D extends true
13
+ ? (E extends true ? true : false)
14
+ : false)
15
+ : false)
16
+ : false)
17
+ : false;
18
+
19
+ export type AllTrue<U> = Exclude<U, true> extends never ? true : false;
20
+
21
+ export type AnyTrue<U> = Extract<U, true> extends never ? false : true;
22
+
23
+ export type IsNever<T> = [T] extends [never] ? true : false;
24
+
25
+ // True when `T` is a union of two or more members. A single member (or `never`)
26
+ // is not a union. Used to test that a JOIN ... USING column exists on more than
27
+ // one of the query's tables (it must hold on both sides of the join).
28
+ export type IsUnion<T, U = T> =
29
+ [T] extends [never]
30
+ ? false
31
+ : T extends any
32
+ ? [U] extends [T]
33
+ ? false
34
+ : true
35
+ : false;
36
+
37
+ export type StartsWith<S extends string, Prefix extends string> =
38
+ string extends S ? false : S extends `${Prefix}${string}` ? true : false;
39
+
40
+ export type UnionToIntersection<U> =
41
+ (U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
42
+
43
+ export type Simplify<T> = { [K in keyof T]: T[K] } & {};
@@ -0,0 +1,198 @@
1
+ // CTE name collection, CTE/WITH-DML result inference.
2
+ import type { BuildDerivedReturn, DerivedSubRow } from "./return-derived.js";
3
+ import type { CleanIdent, ExtractAliasResult, ExtractFromClause, ExtractSelectList, ReplaceWhitespace, SplitBalancedParen, SplitCommaSimple, SplitSelectList, Trim } from "../parsing.js";
4
+ import type { DatabaseSchema } from "../schema.js";
5
+ import type { QueryKind } from "./dispatch.js";
6
+ import type { Simplify, UnionToIntersection } from "../utils.js";
7
+ import type { TableKeyFromToken } from "../tables.js";
8
+ // outer projection, output aliases and the `t(a,b)` column-alias list. We parse
9
+ // the CTE deterministically and resolve the outer projection against the CTE's
10
+ // row — treating the CTE name like a derived-table alias. Multi-CTE queries
11
+ // (outer doesn't immediately follow the first balanced group) and CTE+join
12
+ // outers fall back to the old behavior rather than risk a wrong shape.
13
+ export type StripRecursiveKw<N extends string> =
14
+ N extends `with recursive ${infer R}` ? `with ${R}` : N;
15
+
16
+ export type SingleCteMatch<N extends string> =
17
+ StripRecursiveKw<N> extends `with ${infer AfterWith}`
18
+ ? AfterWith extends `${infer Head} as ${infer Tail}`
19
+ ? Trim<Tail> extends `(${string}`
20
+ ? SplitBalancedParen<Trim<Tail>> extends { inner: infer Body extends string; rest: infer Rest extends string }
21
+ ? Trim<Rest> extends `select ${string}`
22
+ ? Trim<Rest> extends `${string} join ${string}`
23
+ ? never
24
+ : Trim<Body> extends `select ${string}`
25
+ ? {
26
+ body: Trim<Body>;
27
+ outer: Trim<Rest>;
28
+ name: CteName<Trim<Head>>;
29
+ cols: CteCols<Trim<Head>>;
30
+ }
31
+ : never
32
+ : never
33
+ : never
34
+ : never
35
+ : never
36
+ : never;
37
+
38
+ export type CteName<Head extends string> =
39
+ Head extends `${infer Name}(${string}` ? CleanIdent<Trim<Name>> : CleanIdent<Head>;
40
+
41
+ // Names declared by a leading `WITH [RECURSIVE] a AS (...), b AS (...) ...`
42
+ // clause. These are query-local relation names, not base tables, so the
43
+ // table-existence check must not reject them. Body parens are skipped via
44
+ // `SplitBalancedParen` so an inner ` as ` (a column alias) is never mistaken for
45
+ // a CTE boundary. Non-WITH queries resolve to `never` (a no-op exclude).
46
+ export type CteNames<N extends string> =
47
+ StripRecursiveKw<N> extends `with ${infer AfterWith}`
48
+ ? CollectCteNames<Trim<AfterWith>>
49
+ : never;
50
+
51
+ export type CollectCteNames<S extends string, Acc extends string = never> =
52
+ S extends `${infer Head} as ${infer Tail}`
53
+ ? Trim<Tail> extends `(${string}`
54
+ ? SplitBalancedParen<Trim<Tail>> extends { rest: infer Rest extends string }
55
+ ? Trim<Rest> extends `, ${infer More}`
56
+ ? CollectCteNames<Trim<More>, Acc | CteName<Trim<Head>>>
57
+ : Acc | CteName<Trim<Head>>
58
+ : Acc | CteName<Trim<Head>>
59
+ : Acc
60
+ : Acc;
61
+
62
+ // The OUTER query of a `WITH a AS (...), b AS (...) <outer>` statement: the tail
63
+ // that follows the last comma-separated CTE definition. Mirrors `CollectCteNames`'
64
+ // balanced-paren walk over the CTE list, but instead of collecting names it
65
+ // returns whatever remains after the final CTE body — i.e. the top-level
66
+ // `SELECT ... FROM ...`. Used by the multi-CTE result path so the OUTER projection
67
+ // is inferred, not the first inner CTE's select list (which a naive
68
+ // `${_}select ${After}` match would grab). Falls back to `N` when the query isn't
69
+ // a `WITH`, and to `""` when the CTE list can't be parsed.
70
+ // `ReplaceWhitespace` (in `NormalizeQuery`) is step-capped, so on a long multi-CTE
71
+ // query the OUTER select — which sits past the cap, at the very end — can keep its
72
+ // raw `\n`/`\t`. `ExtractSelectList` matches `select ` with a literal space, so we
73
+ // re-collapse whitespace on the (short) extracted outer before handing it on.
74
+ export type CteOuterQuery<N extends string> =
75
+ StripRecursiveKw<N> extends `with ${infer AfterWith}`
76
+ ? ReplaceWhitespace<CollectCteOuter<Trim<AfterWith>>>
77
+ : N;
78
+
79
+ export type CollectCteOuter<S extends string> =
80
+ S extends `${infer _Head} as ${infer Tail}`
81
+ ? Trim<Tail> extends `(${string}`
82
+ ? SplitBalancedParen<Trim<Tail>> extends { rest: infer Rest extends string }
83
+ ? Trim<Rest> extends `, ${infer More}`
84
+ ? CollectCteOuter<Trim<More>>
85
+ : Trim<Rest>
86
+ : ""
87
+ : ""
88
+ : "";
89
+
90
+ // The first FROM relation token (alias-less) of a stripped outer query. Used as
91
+ // the derived-table qualifier when resolving a multi-CTE outer projection.
92
+ export type CteOuterFromName<Outer extends string> =
93
+ Trim<ExtractFromClause<Outer>> extends `${infer Rel} ${string}`
94
+ ? CleanIdent<Rel>
95
+ : CleanIdent<Trim<ExtractFromClause<Outer>>>;
96
+
97
+ // Result row for a multi-CTE `SELECT` whose OUTER query reads from a single CTE
98
+ // (no join into base tables). The CTE's columns aren't modeled as a schema
99
+ // relation, so resolving the outer projection with the normal `ExprType` path
100
+ // would fail every ref to `never` (and poison casts over them). Instead we treat
101
+ // the CTE like a derived table and reuse `BuildDerivedReturn`/`DerivedProjType`:
102
+ // a `expr::cast` projection recovers its type from the OUTER cast (`OuterCastTs`),
103
+ // and any other ref falls back to `unknown` (conservative default — we don't
104
+ // model the CTE row). The empty sub-row means bare CTE-column projections are
105
+ // `unknown` rather than their true type; casts (the common rollup shape, e.g.
106
+ // `avg(x)::int`) are typed precisely.
107
+ export type MultiCteReturn<Outer extends string> =
108
+ BuildDerivedReturn<SplitSelectList<ExtractSelectList<Outer>>, CteOuterFromName<Outer>, {}>;
109
+
110
+ // The normalized table keys for every declared CTE name, so they can be
111
+ // `Exclude`d from the collected FROM tables before the existence check.
112
+ export type CteTableKeys<N extends string, S extends DatabaseSchema> =
113
+ [CteNames<N>] extends [never] ? never : TableKeyFromToken<CteNames<N>, S>;
114
+
115
+ // FROM/JOIN tables with the query-local CTE relation names removed.
116
+ export type NonCteTables<N extends string, S extends DatabaseSchema, Tables extends string> =
117
+ Exclude<Tables, CteTableKeys<N, S>>;
118
+
119
+ // A `WITH <cte> AS (...) <UPDATE|INSERT|DELETE> ...` statement: the inner
120
+ // data-modifying statement after a SINGLE leading CTE. Result inference for these
121
+ // must follow the top-level DML's RETURNING list, not the CTE body's projection.
122
+ // Multi-CTE chains fall back to `never` (handled by the existing select path).
123
+ export type WithDmlOuter<N extends string> =
124
+ StripRecursiveKw<N> extends `with ${infer AfterWith}`
125
+ ? AfterWith extends `${infer _Head} as ${infer Tail}`
126
+ ? Trim<Tail> extends `(${string}`
127
+ ? SplitBalancedParen<Trim<Tail>> extends { rest: infer Rest extends string }
128
+ ? Trim<Rest> extends infer R extends string
129
+ ? QueryKind<R> extends "update" | "insert" | "delete"
130
+ ? R
131
+ : never
132
+ : never
133
+ : never
134
+ : never
135
+ : never
136
+ : never;
137
+
138
+ export type CteCols<Head extends string> =
139
+ Head extends `${string}(${infer Cols})${string}`
140
+ ? FilterCteCols<SplitCommaSimple<Cols>>
141
+ : [];
142
+
143
+ export type FilterCteCols<Cols extends string[], Acc extends string[] = []> =
144
+ Cols extends [infer C extends string, ...infer Rest extends string[]]
145
+ ? CleanIdent<C> extends ""
146
+ ? FilterCteCols<Rest, Acc>
147
+ : FilterCteCols<Rest, [...Acc, CleanIdent<C>]>
148
+ : Acc;
149
+
150
+ export type CteReturn<N extends string, S extends DatabaseSchema> =
151
+ SingleCteMatch<N> extends {
152
+ body: infer Body extends string;
153
+ outer: infer Outer extends string;
154
+ name: infer Name extends string;
155
+ cols: infer Cols extends string[];
156
+ }
157
+ ? CteRow<Body, Cols, S> extends infer Row
158
+ ? BuildDerivedReturn<SplitSelectList<ExtractSelectList<Outer>>, Name, Row>
159
+ : {}
160
+ : {};
161
+
162
+ // The CTE's projected row. Without a column-alias list it's just the body's
163
+ // projection; with `t(a, b)` the body columns are positionally renamed.
164
+ export type CteRow<Body extends string, Cols extends string[], S extends DatabaseSchema> =
165
+ DerivedSubRow<Body, S> extends infer BaseRow
166
+ ? Cols extends []
167
+ ? BaseRow
168
+ : RenameRow<SplitSelectList<ExtractSelectList<Body>>, Cols, BaseRow>
169
+ : {};
170
+
171
+ // Positionally pair each `t(a, b)` output column with the body's i-th projected
172
+ // expression. Implemented as a single shallow mapped type (no recursive
173
+ // intersection accumulation) to keep the CTE col-list path cheap — the recursive
174
+ // form tipped TS's cumulative instantiation budget over on the full suite.
175
+ export type RenameRow<BodyExprs extends string[], Cols extends string[], BaseRow> =
176
+ Simplify<UnionToIntersection<
177
+ {
178
+ [I in keyof Cols]: Cols[I] extends string
179
+ ? {
180
+ [P in Cols[I]]: I extends keyof BodyExprs
181
+ ? BodyExprs[I] extends string
182
+ ? CteBodyColType<BodyExprs[I], BaseRow>
183
+ : unknown
184
+ : unknown
185
+ }
186
+ : {}
187
+ }[number]
188
+ >>;
189
+
190
+ export type CteBodyColKey<E extends string> =
191
+ ExtractAliasResult<E> extends { expr: infer Raw extends string; alias: infer A }
192
+ ? [A] extends [never] ? CleanIdent<Raw> : A
193
+ : CleanIdent<E>;
194
+
195
+ export type CteBodyColType<E extends string, BaseRow> =
196
+ CteBodyColKey<E> extends infer K extends string
197
+ ? K extends keyof BaseRow ? BaseRow[K] : unknown
198
+ : unknown;
@@ -0,0 +1,312 @@
1
+ // Validation entry point, dispatch, complexity gates, query-kind helpers.
2
+ import type { AliasesInQuery, TableKeyValid, TablesInQuery, UpdateTargetTable } from "../tables.js";
3
+ import type { AllColumnsValidFor, AllTablesValidFor, ColumnsValidInSelectOrReturningFor, NoAliasShadowedQualifiers, OuterScopeUnqualifiedValid, QualifiedColumnRefsValidFor, UnqualifiedColumnRefsValidFor, ValidateCteShape, ValidateDerivedShape } from "./validate-columns.js";
4
+ import type { And, StartsWith } from "../utils.js";
5
+ import type { CteNames, NonCteTables, SingleCteMatch } from "./cte.js";
6
+ import type { DatabaseSchema } from "../schema.js";
7
+ import type { DerivedAliasName, DerivedFirstWord, DerivedTableMatch } from "./return-derived.js";
8
+ import type { DistinctOnColsValid, JoinUsingColsValid, WindowFilterColsValid } from "./joins.js";
9
+ import type { ExceedsLengthBudget, ExtractBefore, ExtractLastWhere, HasLineBreaks, TokenizeLoose, Trim, ValidationScanView } from "../parsing.js";
10
+ import type { RefScanSegment } from "./return-types.js";
11
+ // Core validation / inference
12
+
13
+ // Validate against a quote-neutralised view of the query (string literal / quoted
14
+ // alias text never carries column refs or real clauses — round-12 S1–S5, A1), but
15
+ // ONLY for small quoted queries: the neutralisation char-walk would blow the depth
16
+ // budget on report-scale queries, and a no-quote query has nothing to neutralise.
17
+ // The neutralised view is computed once here (top level) so it resolves to a
18
+ // concrete string before dispatch and never compounds with validation depth. The
19
+ // result path is unaffected, so literal value types still infer from the original.
20
+ export type ValidateSQLNormalized<N extends string, S extends DatabaseSchema> =
21
+ ShouldNeutralizeForScan<N> extends true
22
+ ? ValidationScanView<N> extends infer V extends string
23
+ ? ValidateSQLNormalizedDispatch<V, S>
24
+ : false
25
+ : ValidateSQLNormalizedDispatch<N, S>;
26
+
27
+ export type ShouldNeutralizeForScan<N extends string> =
28
+ N extends `${string}'${string}`
29
+ ? NotReportScale<N>
30
+ : N extends `${string}"${string}`
31
+ ? NotReportScale<N>
32
+ : false;
33
+
34
+ export type NotReportScale<N extends string> =
35
+ HasLineBreaks<N> extends true
36
+ ? false
37
+ : ExceedsLengthBudget<N> extends true
38
+ ? false
39
+ : true;
40
+
41
+ export type ValidateSQLNormalizedDispatch<N extends string, S extends DatabaseSchema> =
42
+ QueryKind<N> extends "select"
43
+ ? IsHighComplexitySelect<N> extends true
44
+ ? ValidateSQLNormalizedLightSelect<N, S>
45
+ // A single-CTE SELECT and a leading derived-table SELECT expose only their
46
+ // projected output row to the outer query — validate that surface rather
47
+ // than the body's base tables. A derived source followed by a JOIN is left
48
+ // to the core path (it has additional relations in scope).
49
+ : [SingleCteMatch<N>] extends [never]
50
+ ? [DerivedTableMatch<N>] extends [never]
51
+ ? ValidateSQLNormalizedCore<N, S>
52
+ : N extends `${string} join ${string}`
53
+ ? ValidateSQLNormalizedCore<N, S>
54
+ : ValidateDerivedShape<N, S>
55
+ : ValidateCteShape<N, S>
56
+ : QueryKind<N> extends "update"
57
+ ? IsHighComplexityUpdate<N> extends true
58
+ ? ValidateHighComplexityUpdate<N, S>
59
+ : ValidateSQLNormalizedCore<N, S>
60
+ : ValidateSQLNormalizedCore<N, S>;
61
+
62
+ // High-complexity UPDATE (giant CASE/EXISTS/subquery SET) validator. The full
63
+ // core validator can't reliably parse the SET expression (its ` where ` / `=`
64
+ // /`,` tokens live inside subqueries) and blows TS2589, which is why these were
65
+ // blanket-accepted. Instead validate the cheap, reliable signal: the update
66
+ // target table exists, and the columns in the REAL top-level WHERE (depth-0,
67
+ // skipping subquery WHEREs) resolve against the query's tables/aliases. Catches
68
+ // an invalid WHERE column (adversarial N7) while accepting the valid
69
+ // correlated-update queries whose top-level WHERE references real columns.
70
+ export type ValidateHighComplexityUpdate<N extends string, S extends DatabaseSchema> =
71
+ // Updates large enough to exceed the 700-char normalization cap still carry
72
+ // raw line breaks; even tokenizing them blows TS2589, so preserve the old
73
+ // blanket-accept for those (we cannot safely parse them). Only small, fully
74
+ // normalized updates (like the adversarial N7) are actually validated.
75
+ HasLineBreaks<N> extends true
76
+ ? true
77
+ : UpdateTargetTable<N, S> extends infer TargetKey extends string
78
+ ? TableKeyValid<TargetKey, S> extends true
79
+ ? ExtractLastWhere<N> extends infer W extends string
80
+ ? Trim<W> extends ""
81
+ ? true
82
+ // If the top-level WHERE itself contains a subquery/paren,
83
+ // the extracted predicate may not be the real top-level one
84
+ // (or references other tables) — skip rather than risk a
85
+ // false reject. Only the simple-WHERE case is validated.
86
+ : WhereHasSubquery<W> extends true
87
+ ? true
88
+ // Resolve the WHERE's columns against ONLY the update
89
+ // target table and its alias — NOT the whole query's
90
+ // tables/aliases.
91
+ : WhereColsValidForUpdate<W, TargetKey, UpdateAliasEntry<N, TargetKey>, S>
92
+ : true
93
+ : false
94
+ : true;
95
+
96
+ // A top-level WHERE is treated as "has a subquery" (and skipped) ONLY when it
97
+ // actually contains a nested SELECT / EXISTS — those reference columns of other
98
+ // tables that the target-table-only column check would falsely reject. A bare
99
+ // parenthesised group is NOT a subquery: `WHERE bogus_col IN (1, 2)` is a value
100
+ // list whose top-level column (`bogus_col`) must still be validated, so we no
101
+ // longer bail on every `(` (which silently accepted invalid top-level columns).
102
+ export type WhereHasSubquery<W extends string> =
103
+ W extends `${string}select ${string}` ? true :
104
+ W extends `${string}exists ${string}` ? true :
105
+ W extends `exists ${string}` ? true :
106
+ false;
107
+
108
+ // The update statement's own table alias as a `${alias}=>${tableKey}` entry, or
109
+ // `never` when the update has no alias. Parsed only from the `update <table>
110
+ // [alias] set` head, so it stays cheap on huge statements.
111
+ export type UpdateAliasEntry<N extends string, TargetKey extends string> =
112
+ N extends `update ${infer Rest}`
113
+ ? Trim<ExtractBefore<Rest, " set ">> extends `${infer _Tbl} ${infer AliasPart}`
114
+ // `DerivedAliasName` strips a leading `as ` so the standard
115
+ // `UPDATE products AS p` form yields `p`, not the keyword `as`.
116
+ ? DerivedAliasName<Trim<AliasPart>> extends infer A extends string
117
+ ? A extends ""
118
+ ? never
119
+ : `${A}=>${TargetKey}`
120
+ : never
121
+ : never
122
+ : never;
123
+
124
+ export type WhereColsValidForUpdate<
125
+ W extends string,
126
+ TargetKey extends string,
127
+ AliasEntry extends string,
128
+ S extends DatabaseSchema
129
+ > =
130
+ TokenizeLoose<W> extends infer WT extends string[]
131
+ ? And<
132
+ QualifiedColumnRefsValidFor<W, S, TargetKey, AliasEntry, WT>,
133
+ UnqualifiedColumnRefsValidFor<W, S, TargetKey, AliasEntry, WT, never>,
134
+ true,
135
+ true,
136
+ true
137
+ >
138
+ : true;
139
+
140
+ // "Light" validator for high-complexity SELECTs: validate the cheap, bounded
141
+ // parts (every referenced table exists, and the select/returning list resolves)
142
+ // while SKIPPING the O(tokens x tables) loose WHERE/GROUP/HAVING/ORDER scan that
143
+ // makes the full core validator OOM on report-scale queries. This catches an
144
+ // invalid table or an invalid select-list column (the common false-accepts)
145
+ // without paying for whole-query token validation. The select-list check only
146
+ // tokenizes per-expression, so it stays within the budget that blanket-`true`
147
+ // was protecting.
148
+ // Small, single-line high-complexity selects can afford full validation — route
149
+ // them to the core validator so invalid columns in WHERE / ORDER BY / GROUP BY /
150
+ // HAVING (and window/filter clauses) are actually caught. Only genuinely
151
+ // report-scale queries — multi-line (line breaks survive normalization) or very
152
+ // long single-line — keep the cheap tables-plus-select-list check that the
153
+ // blanket bypass was protecting from OOM.
154
+ export type ValidateSQLNormalizedLightSelect<N extends string, S extends DatabaseSchema> =
155
+ ExceedsLengthBudget<N> extends true
156
+ ? ReportScaleSelectTables<N, S>
157
+ : HasLineBreaks<N> extends true
158
+ ? LightSelectTablesAndList<N, S>
159
+ : ValidateSQLNormalizedCore<N, S>;
160
+
161
+ export type ReportScaleSelectTables<N extends string, S extends DatabaseSchema> =
162
+ TablesInQuery<N, S> extends infer Tables extends string
163
+ ? AllTablesValidFor<ReportScaleTablesToValidate<N, S, Tables>, S>
164
+ : false;
165
+
166
+ export type ReportScaleTablesToValidate<N extends string, S extends DatabaseSchema, Tables extends string> =
167
+ Tables extends `${infer Schema}.${infer Table}`
168
+ ? TableKeyValid<`${Schema}.${Table}`, S> extends true
169
+ ? `${Schema}.${Table}`
170
+ : Table extends ReportScaleLocalRelationNames<N> | "select"
171
+ ? never
172
+ : Table extends `${ReportScalePseudoSource}${string}`
173
+ ? never
174
+ : N extends `with ${string}`
175
+ ? Table extends Lowercase<Table>
176
+ ? never
177
+ : `${Schema}.${Table}`
178
+ : `${Schema}.${Table}`
179
+ : Tables;
180
+
181
+ export type ReportScaleLocalRelationNames<N extends string> =
182
+ CteNames<N> | ReportScaleDerivedNames<N>;
183
+
184
+ export type ReportScaleDerivedNames<N extends string> =
185
+ N extends `${infer _Before} from (${infer _Body}) as ${infer After}`
186
+ ? DerivedFirstWord<Trim<After>>
187
+ : N extends `${infer _Before} from (${infer _Body}) ${infer After}`
188
+ ? DerivedFirstWord<Trim<After>>
189
+ : never;
190
+
191
+ export type ReportScalePseudoSource =
192
+ | "lateral"
193
+ | "max"
194
+ | "min"
195
+ | "avg"
196
+ | "sum"
197
+ | "count"
198
+ | "extract"
199
+ | "json_array_elements"
200
+ | "jsonb_array_elements"
201
+ | "unnest"
202
+ | "generate_series";
203
+
204
+ export type LightSelectTablesAndList<N extends string, S extends DatabaseSchema> =
205
+ TablesInQuery<N, S> extends infer Tables extends string
206
+ ? AliasesInQuery<N, S> extends infer Aliases extends string
207
+ ? AllTablesValidFor<NonCteTables<N, S, Tables>, S> extends true
208
+ ? ColumnsValidInSelectOrReturningFor<N, S, Tables, Aliases> extends true
209
+ ? DistinctOnColsValid<N, S, Tables, Aliases> extends true
210
+ ? true
211
+ : false
212
+ : false
213
+ : false
214
+ : false
215
+ : false;
216
+
217
+ export type ValidateSQLNormalizedCore<N extends string, S extends DatabaseSchema> =
218
+ TablesInQuery<N, S> extends infer Tables extends string
219
+ ? AliasesInQuery<N, S> extends infer Aliases extends string
220
+ ? TokenizeLoose<RefScanSegment<N>> extends infer LooseTokens extends string[]
221
+ ? AllTablesValidFor<NonCteTables<N, S, Tables>, S> extends true
222
+ ? AllColumnsValidFor<N, S, Tables, Aliases, LooseTokens> extends true
223
+ ? NoAliasShadowedQualifiers<N, S, Tables, Aliases> extends true
224
+ ? OuterScopeUnqualifiedValid<N, S> extends true
225
+ ? WindowFilterColsValid<N, S, Tables, Aliases> extends true
226
+ ? JoinUsingColsValid<N, S, Tables> extends true
227
+ ? DistinctOnColsValid<N, S, Tables, Aliases> extends true
228
+ ? true
229
+ : false
230
+ : false
231
+ : false
232
+ : false
233
+ : false
234
+ : false
235
+ : false
236
+ : false
237
+ : false
238
+ : false;
239
+
240
+
241
+ export type IsHighComplexityUpdate<N extends string> =
242
+ QueryKind<N> extends "update"
243
+ ? N extends `${string} case ${string} select ${string}`
244
+ ? true
245
+ : N extends `${string} case ${string} exists (${string}`
246
+ ? true
247
+ : false
248
+ : false;
249
+
250
+ export type IsHighComplexitySelect<N extends string> =
251
+ QueryKind<N> extends "select"
252
+ ? ExceedsLengthBudget<N> extends true
253
+ ? true
254
+ : N extends `${string} offset ${string}`
255
+ ? true
256
+ : N extends `${string} snapshot_date ${string}`
257
+ ? true
258
+ : N extends `${string} join ${string} join ${string} join ${string} join ${string}`
259
+ ? N extends `${string} order by ${string}`
260
+ ? true
261
+ : N extends `${string} group by ${string}`
262
+ ? true
263
+ : N extends `${string} limit ${string}`
264
+ ? true
265
+ : false
266
+ : false
267
+ : false;
268
+
269
+
270
+
271
+ // Query kind helpers
272
+
273
+ export type QueryKind<N extends string> =
274
+ StartsWith<N, "select "> extends true ? "select" :
275
+ StartsWith<N, "with "> extends true ? "select" :
276
+ StartsWith<N, "insert "> extends true ? "insert" :
277
+ StartsWith<N, "update "> extends true ? "update" :
278
+ StartsWith<N, "delete "> extends true ? "delete" :
279
+ "unknown";
280
+
281
+ // RETURNING only belongs to INSERT/UPDATE/DELETE — a SELECT (or `WITH ... SELECT`
282
+ // without a data-modifying statement) never has a RETURNING clause, so `returning`
283
+ // appearing in a SELECT is data (a string literal / quoted alias), not structure.
284
+ // For data-modifying statements the match is quote-aware so ` returning ` inside
285
+ // a string literal or quoted identifier is not mistaken for the clause.
286
+ export type HasReturning<N extends string> =
287
+ QueryKind<N> extends "select"
288
+ ? false
289
+ : HasReturningQuoteAware<N>;
290
+
291
+ export type HasReturningQuoteAware<
292
+ S extends string,
293
+ InString extends boolean = false,
294
+ InDString extends boolean = false,
295
+ Steps extends any[] = []
296
+ > = string extends S
297
+ ? false
298
+ : Steps["length"] extends 1200
299
+ ? S extends `${string} returning ${string}` ? true : false
300
+ : InString extends true
301
+ ? S extends `${infer C}${infer Rest}`
302
+ ? HasReturningQuoteAware<Rest, C extends "'" ? false : true, InDString, [any, ...Steps]>
303
+ : false
304
+ : InDString extends true
305
+ ? S extends `${infer C}${infer Rest}`
306
+ ? HasReturningQuoteAware<Rest, InString, C extends `"` ? false : true, [any, ...Steps]>
307
+ : false
308
+ : S extends ` returning ${string}`
309
+ ? true
310
+ : S extends `${infer C}${infer Rest}`
311
+ ? HasReturningQuoteAware<Rest, C extends "'" ? true : false, C extends `"` ? true : false, [any, ...Steps]>
312
+ : false;