@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
@@ -0,0 +1,43 @@
1
+ // src/builder/write-state.ts
2
+ import type { DriverParamValue } from "./scanner.js";
3
+
4
+ export interface InsertValueEntry { readonly col: string; readonly text: string; }
5
+ export interface RuntimeInsertState {
6
+ readonly table: string;
7
+ readonly values: ReadonlyArray<InsertValueEntry>;
8
+ // INSERT...SELECT form: explicit column list and a free-text SELECT body that
9
+ // replaces the VALUES clause. When `fromSelect` is set, the value fragments are
10
+ // ignored and the SELECT form is emitted instead (see assembleInsertSQL).
11
+ readonly columns?: string;
12
+ readonly fromSelect?: string;
13
+ readonly conflict?: string;
14
+ readonly returning?: string;
15
+ readonly namedParams: Record<string, DriverParamValue>;
16
+ }
17
+ export interface RuntimeUpdateState {
18
+ readonly table: string;
19
+ // Optional table alias, e.g. `update orders o set ...`. Emitted after the
20
+ // table name when present so aliased WHERE/SET references resolve.
21
+ readonly alias?: string;
22
+ // Optional CTEs prepended as a `with ... ` clause before `update`. The body
23
+ // text may contain `:params`; because it precedes the UPDATE in the final
24
+ // SQL, those params get the lowest positional numbers.
25
+ readonly ctes?: ReadonlyArray<{ name: string; body: string; materialized: boolean }>;
26
+ readonly sets: ReadonlyArray<string>;
27
+ readonly froms: ReadonlyArray<string>;
28
+ readonly wheres: ReadonlyArray<string>;
29
+ readonly returning?: string;
30
+ readonly namedParams: Record<string, DriverParamValue>;
31
+ }
32
+ export interface RuntimeDeleteState {
33
+ readonly table: string;
34
+ readonly usings: ReadonlyArray<string>;
35
+ readonly wheres: ReadonlyArray<string>;
36
+ readonly returning?: string;
37
+ readonly namedParams: Record<string, DriverParamValue>;
38
+ }
39
+ // `columns: ""` mirrors the type-level EmptyInsertTag default so a `.fromSelect(...)`
40
+ // reached without `.columns(...)` emits an honest `()` rather than `(undefined)`.
41
+ export const EMPTY_INSERT_STATE: RuntimeInsertState = { table: "", values: [], columns: "", namedParams: {} };
42
+ export const EMPTY_UPDATE_STATE: RuntimeUpdateState = { table: "", sets: [], froms: [], wheres: [], namedParams: {} };
43
+ export const EMPTY_DELETE_STATE: RuntimeDeleteState = { table: "", usings: [], wheres: [], namedParams: {} };
@@ -0,0 +1,119 @@
1
+ // src/builder/write-tag.ts
2
+ import type { DatabaseSchema } from "../schema.js";
3
+ import type { ExtractParams, ExtractReturning } from "./extract-params.js";
4
+
5
+ export type WriteMode = "max" | "req";
6
+
7
+ export interface ValueFrag { readonly col: string; readonly text: string; readonly cond: boolean; }
8
+ export interface ClauseFrag { readonly text: string; readonly cond: boolean; }
9
+
10
+ export interface InsertTag {
11
+ readonly kind: "insert";
12
+ readonly table: string;
13
+ readonly values: readonly ValueFrag[];
14
+ // INSERT...SELECT: explicit column list + free-text SELECT body. Empty string
15
+ // ("") means "not set" — the VALUES form is rendered instead.
16
+ readonly columns: string;
17
+ readonly fromSelect: string;
18
+ readonly conflict: string | null;
19
+ readonly wheres: readonly ClauseFrag[]; // unused for insert; kept uniform
20
+ readonly using: readonly ClauseFrag[];
21
+ readonly from: readonly ClauseFrag[];
22
+ readonly returning: string | null;
23
+ }
24
+ export interface CteFrag { readonly name: string; readonly body: string; readonly materialized: boolean; }
25
+ export interface UpdateTag {
26
+ readonly kind: "update";
27
+ readonly table: string;
28
+ readonly alias: string;
29
+ readonly ctes: readonly CteFrag[];
30
+ readonly sets: readonly ClauseFrag[];
31
+ readonly from: readonly ClauseFrag[];
32
+ readonly wheres: readonly ClauseFrag[];
33
+ readonly returning: string | null;
34
+ }
35
+ export interface DeleteTag {
36
+ readonly kind: "delete";
37
+ readonly table: string;
38
+ readonly using: readonly ClauseFrag[];
39
+ readonly wheres: readonly ClauseFrag[];
40
+ readonly returning: string | null;
41
+ }
42
+
43
+ // keep only the fragments live for a mode ("req" drops cond=true).
44
+ type ForMode<List extends readonly { cond: boolean }[], Mode extends WriteMode> =
45
+ Mode extends "max" ? List : DropCond<List>;
46
+ type DropCond<List extends readonly { cond: boolean }[]> =
47
+ List extends readonly [infer H extends { cond: boolean }, ...infer R extends readonly { cond: boolean }[]]
48
+ ? H["cond"] extends true ? DropCond<R> : readonly [H, ...DropCond<R>]
49
+ : readonly [];
50
+
51
+ type ColList<List extends readonly ValueFrag[], Acc extends string = ""> =
52
+ List extends readonly [infer H extends ValueFrag, ...infer R extends readonly ValueFrag[]]
53
+ ? ColList<R, Acc extends "" ? H["col"] : `${Acc}, ${H["col"]}`> : Acc;
54
+ type ValList<List extends readonly ValueFrag[], Acc extends string = ""> =
55
+ List extends readonly [infer H extends ValueFrag, ...infer R extends readonly ValueFrag[]]
56
+ ? ValList<R, Acc extends "" ? H["text"] : `${Acc}, ${H["text"]}`> : Acc;
57
+ type JoinText<List extends readonly { text: string }[], Sep extends string, Acc extends string = ""> =
58
+ List extends readonly [infer H extends { text: string }, ...infer R extends readonly { text: string }[]]
59
+ ? JoinText<R, Sep, Acc extends "" ? H["text"] : `${Acc}${Sep}${H["text"]}`> : Acc;
60
+
61
+ type Conflict<C extends string | null> = C extends string ? ` on conflict ${C}` : "";
62
+ type Returning<R extends string | null> = R extends string ? ` returning ${R}` : "";
63
+ type FromClause<L extends readonly ClauseFrag[]> = L extends readonly [] ? "" : ` from ${JoinText<L, ", ">}`;
64
+ type UsingClause<L extends readonly ClauseFrag[]> = L extends readonly [] ? "" : ` using ${JoinText<L, ", ">}`;
65
+ type WhereClause<L extends readonly ClauseFrag[]> = L extends readonly [] ? "" : ` where ${JoinText<L, " and ">}`;
66
+
67
+ // INSERT...SELECT form: `insert into <table> (<columns>) <fromSelect>[ on conflict
68
+ // <c>][ returning <r>]`. The fromSelect text carries its own `:params`, which
69
+ // ExtractParams scans positionally from the built string.
70
+ export type BuildInsertSelectSQL<T extends InsertTag> =
71
+ `insert into ${T["table"]} (${T["columns"]}) ${T["fromSelect"]}${Conflict<T["conflict"]>}${Returning<T["returning"]>}`;
72
+
73
+ export type BuildInsertSQL<T extends InsertTag, Mode extends WriteMode> =
74
+ // Branch on whether a SELECT body was supplied (non-empty fromSelect).
75
+ T["fromSelect"] extends "" ? BuildInsertValuesSQL<T, Mode> : BuildInsertSelectSQL<T>;
76
+
77
+ // Original VALUES rendering, used when no fromSelect is present.
78
+ type BuildInsertValuesSQL<T extends InsertTag, Mode extends WriteMode> =
79
+ ForMode<T["values"], Mode> extends infer V extends readonly ValueFrag[]
80
+ ? `insert into ${T["table"]} (${ColList<V>}) values (${ValList<V>})${Conflict<T["conflict"]>}${Returning<T["returning"]>}`
81
+ : never;
82
+
83
+ // Head is `table` alone, or `table alias` when an alias was supplied.
84
+ type UpdateHead<T extends UpdateTag> = T["alias"] extends "" ? T["table"] : `${T["table"]} ${T["alias"]}`;
85
+
86
+ // Render one CTE: `name as [materialized ](body)`.
87
+ type RenderCte<C extends CteFrag> = `${C["name"]} as ${C["materialized"] extends true ? "materialized " : ""}(${C["body"]})`;
88
+ // Join CTEs with ", " (comma-separated), mirroring JoinText but per-CTE rendered.
89
+ type JoinCtes<L extends readonly CteFrag[], Acc extends string = ""> =
90
+ L extends readonly [infer H extends CteFrag, ...infer R extends readonly CteFrag[]]
91
+ ? JoinCtes<R, Acc extends "" ? RenderCte<H> : `${Acc}, ${RenderCte<H>}`> : Acc;
92
+ // Leading `with ... ` clause (trailing space) when CTEs exist, else empty. The
93
+ // body text is embedded so ExtractParams picks up its `:params` ahead of the rest.
94
+ type WithClause<L extends readonly CteFrag[]> = L extends readonly [] ? "" : `with ${JoinCtes<L>} `;
95
+
96
+ export type BuildUpdateSQL<T extends UpdateTag, Mode extends WriteMode> =
97
+ `${WithClause<T["ctes"]>}update ${UpdateHead<T>} set ${JoinText<ForMode<T["sets"], Mode>, ", ">}${FromClause<ForMode<T["from"], Mode>>}${WhereClause<ForMode<T["wheres"], Mode>>}${Returning<T["returning"]>}`;
98
+
99
+ export type BuildDeleteSQL<T extends DeleteTag, Mode extends WriteMode> =
100
+ `delete from ${T["table"]}${UsingClause<ForMode<T["using"], Mode>>}${WhereClause<ForMode<T["wheres"], Mode>>}${Returning<T["returning"]>}`;
101
+
102
+ type BuildSQL<T, Mode extends WriteMode> =
103
+ T extends InsertTag ? BuildInsertSQL<T, Mode>
104
+ : T extends UpdateTag ? BuildUpdateSQL<T, Mode>
105
+ : T extends DeleteTag ? BuildDeleteSQL<T, Mode>
106
+ : never;
107
+
108
+ // Required iff present in the req-mode params; value type taken from max-mode
109
+ // (full §6.2 intersection over all occurrences). Mirrors return-type.ts Partition.
110
+ type Partition<Max, Req> =
111
+ & { [K in keyof Max as K extends keyof Req ? K : never]: Max[K] }
112
+ & { [K in keyof Max as K extends keyof Req ? never : K]?: Max[K] };
113
+
114
+ export type WriteParamsFor<T, S extends DatabaseSchema> =
115
+ Partition<ExtractParams<BuildSQL<T, "max">, S>, ExtractParams<BuildSQL<T, "req">, S>> extends infer P
116
+ ? { [K in keyof P]: P[K] } : never;
117
+
118
+ export type WriteReturnFor<T, S extends DatabaseSchema> =
119
+ ExtractReturning<BuildSQL<T, "max"> & string, S>;
package/src/columns.ts ADDED
@@ -0,0 +1,336 @@
1
+ import type {
2
+ DatabaseSchema,
3
+ ColumnExists,
4
+ ColumnExistsInAnyTable,
5
+ ResolveColumnName,
6
+ TableExists
7
+ } from "./schema.js";
8
+ import type { AliasesInQuery, AliasNames, IsAliasName, TableKeyFromToken, TableKeyValid, TablesInQuery } from "./tables.js";
9
+ import type {
10
+ CanPrecedeColumn,
11
+ CleanExpr,
12
+ CleanIdent,
13
+ HasSpecial,
14
+ IsParamPlaceholder,
15
+ IsQualifiedRefCandidate,
16
+ IsRuntimeStringFragment,
17
+ IsSqlConstant,
18
+ OperatorToken,
19
+ ReplaceAll,
20
+ SqlReserved,
21
+ SplitOnDotClean
22
+ } from "./parsing.js";
23
+ import type { IsNever } from "./utils.js";
24
+
25
+ // Column references
26
+
27
+ export type ColumnRef<TableKey extends string, Column extends string> = {
28
+ tableKey: TableKey;
29
+ column: Column;
30
+ };
31
+
32
+ export type BuildColumnRef<
33
+ TableKey extends string,
34
+ Column extends string,
35
+ S extends DatabaseSchema
36
+ > =
37
+ ResolveColumnName<TableKey, Column, S> extends infer Resolved extends string
38
+ ? ColumnRef<TableKey, Resolved>
39
+ : never;
40
+
41
+ export type StripDoubleQuotes<S extends string> = ReplaceAll<S, `"`, "">;
42
+
43
+ // Parse column references from expressions
44
+ // - supports schema.table.column, table.column, and column
45
+
46
+ // A qualified ref whose table/alias prefix is a double-quoted identifier
47
+ // containing operator punctuation (`"u-1".id`) breaks the generic path: stripping
48
+ // the quotes yields `u-1`, which `IsSimpleRefPart`/`HasSpecial` reject as an
49
+ // arithmetic-looking token, so the ref collapses to `never` and the projected key
50
+ // is lost. A double-quoted identifier is by definition a single opaque name, so
51
+ // when the prefix carries punctuation we resolve it directly (alias/table lookup)
52
+ // without the special-char gate. The no-punctuation case (`"u1".id`) stays on the
53
+ // proven generic path; the column part must be a plain name (no dot/quote/star).
54
+ export type ParseColumnRef<
55
+ Expr extends string,
56
+ Tables extends string,
57
+ Aliases extends string,
58
+ S extends DatabaseSchema
59
+ > =
60
+ CleanExpr<Expr> extends `"${infer P}".${infer Col}`
61
+ ? IsQuotedPunctPrefix<P, Col> extends true
62
+ ? ResolveQuotedPrefixRef<CleanIdent<P>, CleanIdent<Col>, Tables, Aliases, S>
63
+ : ParseColumnRefGeneric<Expr, Tables, Aliases, S>
64
+ : ParseColumnRefGeneric<Expr, Tables, Aliases, S>;
65
+
66
+ export type IsQuotedPunctPrefix<P extends string, Col extends string> =
67
+ Col extends `${string}.${string}` ? false :
68
+ Col extends `${string}"${string}` ? false :
69
+ Col extends `${string}*${string}` ? false :
70
+ HasSpecial<P> extends true ? true :
71
+ false;
72
+
73
+ export type ResolveQuotedPrefixRef<
74
+ P extends string,
75
+ Col extends string,
76
+ Tables extends string,
77
+ Aliases extends string,
78
+ S extends DatabaseSchema
79
+ > =
80
+ [ResolveTableKey<P, Tables, Aliases, S>] extends [infer TK extends string]
81
+ ? [TK] extends [never]
82
+ ? [ResolveTableKeyForUnqualified<Tables, Aliases, S, Col>] extends [infer TKF extends string]
83
+ ? [TKF] extends [never]
84
+ ? never
85
+ : BuildColumnRef<TKF, Col, S>
86
+ : never
87
+ : BuildColumnRef<TK, Col, S>
88
+ : never;
89
+
90
+ export type ParseColumnRefGeneric<
91
+ Expr extends string,
92
+ Tables extends string,
93
+ Aliases extends string,
94
+ S extends DatabaseSchema
95
+ > =
96
+ SplitOnDotClean<StripDoubleQuotes<CleanExpr<Expr>>> extends [infer A extends string, infer B extends string, infer C extends string]
97
+ ? IsSimpleRefPart<A> extends true
98
+ ? IsSimpleRefPart<B> extends true
99
+ ? IsSimpleRefPart<C> extends true
100
+ ? BuildColumnRef<`${A}.${B}`, C, S>
101
+ : never
102
+ : never
103
+ : never
104
+ : SplitOnDotClean<StripDoubleQuotes<CleanExpr<Expr>>> extends [infer A extends string, infer B extends string]
105
+ ? IsSimpleRefPart<A> extends true
106
+ ? IsSimpleRefPart<B> extends true
107
+ ? IsRuntimeStringFragment<A> extends true
108
+ ? [ResolveTableKeyForUnqualified<Tables, Aliases, S, B>] extends [infer TKRuntime extends string]
109
+ ? [TKRuntime] extends [never]
110
+ ? never
111
+ : BuildColumnRef<TKRuntime, B, S>
112
+ : never
113
+ : CleanIdent<A> extends "excluded"
114
+ ? [ResolveTableKeyForUnqualified<Tables, Aliases, S, B>] extends [infer TKExcluded extends string]
115
+ ? [TKExcluded] extends [never]
116
+ ? never
117
+ : BuildColumnRef<TKExcluded, B, S>
118
+ : never
119
+ : [ResolveTableKey<A, Tables, Aliases, S>] extends [infer TK extends string]
120
+ ? [TK] extends [never]
121
+ ? [ResolveTableKeyForUnqualified<Tables, Aliases, S, B>] extends [infer TKFallback extends string]
122
+ ? [TKFallback] extends [never]
123
+ ? never
124
+ : BuildColumnRef<TKFallback, B, S>
125
+ : never
126
+ : BuildColumnRef<TK, B, S>
127
+ : never
128
+ : never
129
+ : never
130
+ : SplitOnDotClean<StripDoubleQuotes<CleanExpr<Expr>>> extends [infer A extends string]
131
+ ? IsSimpleRefPart<A> extends true
132
+ ? [ResolveTableKeyForUnqualified<Tables, Aliases, S, A>] extends [infer TK2 extends string]
133
+ ? [TK2] extends [never]
134
+ ? never
135
+ : BuildColumnRef<TK2, A, S>
136
+ : never
137
+ : never
138
+ : never;
139
+
140
+ export type IsSimpleRefPart<S extends string> =
141
+ S extends "" ? false :
142
+ HasSpecial<S> extends true ? false :
143
+ true;
144
+
145
+ // Validate a column reference string like a.b or a.b.c
146
+
147
+ export type ColumnRefValid<ColRef extends string, N extends string, S extends DatabaseSchema> =
148
+ TablesInQuery<N, S> extends infer Tables extends string
149
+ ? AliasesInQuery<N, S> extends infer Aliases extends string
150
+ ? ColumnRefValidWith<ColRef, Tables, Aliases, S>
151
+ : true
152
+ : true;
153
+
154
+ export type ColumnRefValidWith<
155
+ ColRef extends string,
156
+ Tables extends string,
157
+ Aliases extends string,
158
+ S extends DatabaseSchema
159
+ > =
160
+ ColRef extends `${infer Prefix}.*`
161
+ ? [ResolveAlias<CleanIdent<Prefix>, Aliases>] extends [infer AliasKey extends string]
162
+ ? [AliasKey] extends [never]
163
+ ? TableKeysByName<CleanIdent<Prefix>, Tables> extends infer TableKey extends string
164
+ ? [TableKey] extends [never]
165
+ ? CleanIdent<Prefix> extends `${infer Schema}.${infer Table}`
166
+ ? TableExists<S, Schema, Table> extends true
167
+ ? TableKeyValid<`${Schema}.${Table}`, S>
168
+ : false
169
+ : false
170
+ : TableKeyValid<TableKey, S>
171
+ : false
172
+ : TableKeyValid<AliasKey, S>
173
+ : false
174
+ : ParseColumnRef<ColRef, Tables, Aliases, S> extends infer Ref
175
+ ? [Ref] extends [never]
176
+ ? true
177
+ : [Ref] extends [ColumnRef<infer TableKey extends string, infer Column extends string>]
178
+ ? ColumnExists<TableKey, Column, S>
179
+ : true
180
+ : true;
181
+
182
+ export type ColumnRefValidLoose<ColRef extends string, N extends string, S extends DatabaseSchema> =
183
+ TablesInQuery<N, S> extends infer Tables extends string
184
+ ? AliasesInQuery<N, S> extends infer Aliases extends string
185
+ ? ColumnRefValidLooseWith<ColRef, Tables, Aliases, S>
186
+ : ColumnRefValid<ColRef, N, S>
187
+ : ColumnRefValid<ColRef, N, S>;
188
+
189
+ export type ColumnRefValidLooseWith<
190
+ ColRef extends string,
191
+ Tables extends string,
192
+ Aliases extends string,
193
+ S extends DatabaseSchema
194
+ > = IsNever<Tables> extends true
195
+ ? ColumnRefValidNoTables<ColRef, S>
196
+ : ColumnRefValidWith<ColRef, Tables, Aliases, S>;
197
+
198
+ export type ColumnRefValidNoTables<ColRef extends string, S extends DatabaseSchema> =
199
+ SplitOnDotClean<ColRef> extends [infer A extends string, infer B extends string, infer C extends string]
200
+ ? TableExists<S, A, B> extends true
201
+ ? ColumnExists<`${A}.${B}`, C, S>
202
+ : false
203
+ : SplitOnDotClean<ColRef> extends [infer A extends string, infer B extends string]
204
+ ? TableExists<S, S["defaultSchema"], A> extends true
205
+ ? ColumnExists<`${S["defaultSchema"]}.${A}`, B, S>
206
+ : ColumnExistsInAnyTable<B, S>
207
+ : ColumnExistsInAnyTable<CleanIdent<ColRef>, S>;
208
+
209
+ // Best-effort qualified column references across the query
210
+
211
+ export type QualifiedColumnRefs<
212
+ Tokens extends string[],
213
+ S extends DatabaseSchema,
214
+ Tables extends string,
215
+ Aliases extends string,
216
+ Acc extends string = never,
217
+ Prev extends string = "",
218
+ Steps extends any[] = []
219
+ > = Steps["length"] extends 900
220
+ ? Acc
221
+ : Tokens extends [infer T extends string, ...infer Rest extends string[]]
222
+ ? T extends `${string}.${string}`
223
+ ? Prev extends "from" | "join" | "update" | "into" | "delete"
224
+ ? QualifiedColumnRefs<Rest, S, Tables, Aliases, Acc, T, [any, ...Steps]>
225
+ : IsQualifiedRefCandidate<T> extends true
226
+ ? QualifiedColumnRefs<Rest, S, Tables, Aliases, Acc | T, T, [any, ...Steps]>
227
+ : QualifiedColumnRefs<Rest, S, Tables, Aliases, Acc, T, [any, ...Steps]>
228
+ : QualifiedColumnRefs<Rest, S, Tables, Aliases, Acc, T, [any, ...Steps]>
229
+ : Acc;
230
+
231
+ // Best-effort unqualified column references across the query
232
+
233
+ export type UnqualifiedColumnRefs<
234
+ Tokens extends string[],
235
+ S extends DatabaseSchema,
236
+ Tables extends string,
237
+ Aliases extends string,
238
+ Acc extends string = never,
239
+ Prev extends string = "",
240
+ Steps extends any[] = []
241
+ > = Steps["length"] extends 900
242
+ ? Acc
243
+ : Tokens extends [infer T extends string, infer Next extends string, ...infer Rest extends string[]]
244
+ ? IsUnqualifiedColumnCandidate<T, Prev, Next, Tables, Aliases, S> extends true
245
+ ? UnqualifiedColumnRefs<[Next, ...Rest], S, Tables, Aliases, Acc | T, T, [any, ...Steps]>
246
+ : UnqualifiedColumnRefs<[Next, ...Rest], S, Tables, Aliases, Acc, T, [any, ...Steps]>
247
+ : Tokens extends [infer T extends string]
248
+ ? IsUnqualifiedColumnCandidate<T, Prev, "", Tables, Aliases, S> extends true
249
+ ? Acc | T
250
+ : Acc
251
+ : Acc;
252
+
253
+ export type UnqualifiedColumnValid<
254
+ Col extends string,
255
+ Tables extends string,
256
+ Aliases extends string,
257
+ S extends DatabaseSchema
258
+ > = IsNever<Tables> extends true
259
+ ? ColumnExistsInAnyTable<CleanIdent<Col>, S>
260
+ : [ResolveTableKeyForUnqualified<Tables, Aliases, S, CleanIdent<Col>>] extends [never]
261
+ ? false
262
+ : true;
263
+
264
+ export type IsUnqualifiedColumnCandidate<
265
+ Token extends string,
266
+ Prev extends string,
267
+ Next extends string,
268
+ Tables extends string,
269
+ Aliases extends string,
270
+ S extends DatabaseSchema
271
+ > =
272
+ Token extends "" ? false :
273
+ IsRuntimeStringFragment<Token> extends true ? false :
274
+ Token extends OperatorToken ? false :
275
+ Token extends `${string}.${string}` ? false :
276
+ Token extends `'${string}'` ? false :
277
+ Token extends `${string}'${string}` ? false :
278
+ Token extends `${number}` ? false :
279
+ IsParamPlaceholder<Token> extends true ? false :
280
+ IsSqlConstant<Token> extends true ? false :
281
+ HasSpecial<Token> extends true ? false :
282
+ CleanIdent<Token> extends SqlReserved ? false :
283
+ IsAliasName<Token, Aliases> extends true ? false :
284
+ Prev extends "as" ? false :
285
+ Prev extends "from" | "join" | "update" | "into" | "delete" ? false :
286
+ Next extends "(" ? false :
287
+ // A bareword immediately followed by a string literal is a PostgreSQL typed
288
+ // string literal's type prefix (`DATE '...'`, `TIMESTAMP '...'`), never a
289
+ // column — a real column is never directly adjacent to a quote (round-12 L1/L2).
290
+ Next extends `'${string}` ? false :
291
+ // A table name following a comma is another FROM source in the comma
292
+ // cross-join `from a, b` (the `,` plays the role `join` does), not a column.
293
+ Prev extends "," ? (IsTableName<Token, S> extends true ? false : CanPrecedeColumn<Prev>) :
294
+ IsNever<Tables> extends true
295
+ ? IsTableName<Token, S> extends true
296
+ ? false
297
+ : CanPrecedeColumn<Prev>
298
+ : CanPrecedeColumn<Prev>;
299
+
300
+ export type IsTableName<Token extends string, S extends DatabaseSchema> =
301
+ TableKeyFromToken<Token, S> extends infer TableKey extends string
302
+ ? TableKeyValid<TableKey, S>
303
+ : false;
304
+
305
+ // Utilities for table/column resolution
306
+
307
+ export type ResolveTableKey<Name extends string, Tables extends string, Aliases extends string, S extends DatabaseSchema> =
308
+ ResolveAlias<Name, Aliases> | TableKeysByName<Name, Tables> | `${S["defaultSchema"]}.${Name}`;
309
+
310
+ export type ResolveTableKeyForUnqualified<
311
+ Tables extends string,
312
+ Aliases extends string,
313
+ S extends DatabaseSchema,
314
+ Column extends string
315
+ > = TablesWithColumn<Tables, Column, S> extends infer TK extends string ? TK : never;
316
+
317
+ export type ResolveAlias<Name extends string, Aliases extends string> =
318
+ Aliases extends `${infer A}=>${infer T}`
319
+ ? A extends Name
320
+ ? T
321
+ : never
322
+ : never;
323
+
324
+ export type TableKeysByName<Name extends string, Tables extends string> =
325
+ Tables extends `${infer Schema}.${infer Table}`
326
+ ? Table extends Name
327
+ ? `${Schema}.${Table}`
328
+ : never
329
+ : never;
330
+
331
+ export type TablesWithColumn<Tables extends string, Column extends string, S extends DatabaseSchema> =
332
+ Tables extends `${infer Schema}.${infer Table}`
333
+ ? ColumnExists<`${Schema}.${Table}`, Column, S> extends true
334
+ ? `${Schema}.${Table}`
335
+ : never
336
+ : never;