@kuindji/typed-sql 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +227 -0
- package/dist/builder/assemble.d.ts +13 -0
- package/dist/builder/assemble.d.ts.map +1 -0
- package/dist/builder/assemble.js +86 -0
- package/dist/builder/assemble.js.map +1 -0
- package/dist/builder/condition-tree.d.ts +27 -0
- package/dist/builder/condition-tree.d.ts.map +1 -0
- package/dist/builder/condition-tree.js +91 -0
- package/dist/builder/condition-tree.js.map +1 -0
- package/dist/builder/conditional-sql.d.ts +80 -0
- package/dist/builder/conditional-sql.d.ts.map +1 -0
- package/dist/builder/conditional-sql.js +88 -0
- package/dist/builder/conditional-sql.js.map +1 -0
- package/dist/builder/db.d.ts +76 -0
- package/dist/builder/db.d.ts.map +1 -0
- package/dist/builder/db.js +12 -0
- package/dist/builder/db.js.map +1 -0
- package/dist/builder/delete.d.ts +39 -0
- package/dist/builder/delete.d.ts.map +1 -0
- package/dist/builder/delete.js +33 -0
- package/dist/builder/delete.js.map +1 -0
- package/dist/builder/extract-params.d.ts +97 -0
- package/dist/builder/extract-params.d.ts.map +1 -0
- package/dist/builder/extract-params.js +2 -0
- package/dist/builder/extract-params.js.map +1 -0
- package/dist/builder/index.d.ts +23 -0
- package/dist/builder/index.d.ts.map +1 -0
- package/dist/builder/index.js +14 -0
- package/dist/builder/index.js.map +1 -0
- package/dist/builder/insert.d.ts +51 -0
- package/dist/builder/insert.d.ts.map +1 -0
- package/dist/builder/insert.js +39 -0
- package/dist/builder/insert.js.map +1 -0
- package/dist/builder/mutate.d.ts +28 -0
- package/dist/builder/mutate.d.ts.map +1 -0
- package/dist/builder/mutate.js +17 -0
- package/dist/builder/mutate.js.map +1 -0
- package/dist/builder/params.d.ts +22 -0
- package/dist/builder/params.d.ts.map +1 -0
- package/dist/builder/params.js +65 -0
- package/dist/builder/params.js.map +1 -0
- package/dist/builder/return-type.d.ts +39 -0
- package/dist/builder/return-type.d.ts.map +1 -0
- package/dist/builder/return-type.js +2 -0
- package/dist/builder/return-type.js.map +1 -0
- package/dist/builder/scanner.d.ts +49 -0
- package/dist/builder/scanner.d.ts.map +1 -0
- package/dist/builder/scanner.js +240 -0
- package/dist/builder/scanner.js.map +1 -0
- package/dist/builder/select.d.ts +76 -0
- package/dist/builder/select.d.ts.map +1 -0
- package/dist/builder/select.js +240 -0
- package/dist/builder/select.js.map +1 -0
- package/dist/builder/sql-tag.d.ts +319 -0
- package/dist/builder/sql-tag.d.ts.map +1 -0
- package/dist/builder/sql-tag.js +3 -0
- package/dist/builder/sql-tag.js.map +1 -0
- package/dist/builder/sql.d.ts +17 -0
- package/dist/builder/sql.d.ts.map +1 -0
- package/dist/builder/sql.js +36 -0
- package/dist/builder/sql.js.map +1 -0
- package/dist/builder/state.d.ts +53 -0
- package/dist/builder/state.d.ts.map +1 -0
- package/dist/builder/state.js +18 -0
- package/dist/builder/state.js.map +1 -0
- package/dist/builder/update.d.ts +60 -0
- package/dist/builder/update.d.ts.map +1 -0
- package/dist/builder/update.js +40 -0
- package/dist/builder/update.js.map +1 -0
- package/dist/builder/write-assemble.d.ts +5 -0
- package/dist/builder/write-assemble.d.ts.map +1 -0
- package/dist/builder/write-assemble.js +57 -0
- package/dist/builder/write-assemble.js.map +1 -0
- package/dist/builder/write-state.d.ts +39 -0
- package/dist/builder/write-state.d.ts.map +1 -0
- package/dist/builder/write-state.js +6 -0
- package/dist/builder/write-state.js.map +1 -0
- package/dist/builder/write-tag.d.ts +91 -0
- package/dist/builder/write-tag.d.ts.map +1 -0
- package/dist/builder/write-tag.js +2 -0
- package/dist/builder/write-tag.js.map +1 -0
- package/dist/columns.d.ts +33 -0
- package/dist/columns.d.ts.map +1 -0
- package/dist/columns.js +2 -0
- package/dist/columns.js.map +1 -0
- package/dist/expressions.d.ts +71 -0
- package/dist/expressions.d.ts.map +1 -0
- package/dist/expressions.js +2 -0
- package/dist/expressions.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/parsing/extract.d.ts +47 -0
- package/dist/parsing/extract.d.ts.map +1 -0
- package/dist/parsing/extract.js +2 -0
- package/dist/parsing/extract.js.map +1 -0
- package/dist/parsing/normalize.d.ts +44 -0
- package/dist/parsing/normalize.d.ts.map +1 -0
- package/dist/parsing/normalize.js +2 -0
- package/dist/parsing/normalize.js.map +1 -0
- package/dist/parsing/pg-literals.d.ts +37 -0
- package/dist/parsing/pg-literals.d.ts.map +1 -0
- package/dist/parsing/pg-literals.js +2 -0
- package/dist/parsing/pg-literals.js.map +1 -0
- package/dist/parsing/split.d.ts +100 -0
- package/dist/parsing/split.d.ts.map +1 -0
- package/dist/parsing/split.js +2 -0
- package/dist/parsing/split.js.map +1 -0
- package/dist/parsing/string-utils.d.ts +29 -0
- package/dist/parsing/string-utils.d.ts.map +1 -0
- package/dist/parsing/string-utils.js +2 -0
- package/dist/parsing/string-utils.js.map +1 -0
- package/dist/parsing/tokenize.d.ts +27 -0
- package/dist/parsing/tokenize.d.ts.map +1 -0
- package/dist/parsing/tokenize.js +2 -0
- package/dist/parsing/tokenize.js.map +1 -0
- package/dist/parsing.d.ts +7 -0
- package/dist/parsing.d.ts.map +1 -0
- package/dist/parsing.js +9 -0
- package/dist/parsing.js.map +1 -0
- package/dist/partial.d.ts +30 -0
- package/dist/partial.d.ts.map +1 -0
- package/dist/partial.js +10 -0
- package/dist/partial.js.map +1 -0
- package/dist/schema.d.ts +28 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +2 -0
- package/dist/schema.js.map +1 -0
- package/dist/tables.d.ts +34 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +2 -0
- package/dist/tables.js.map +1 -0
- package/dist/utils.d.ts +13 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +3 -0
- package/dist/utils.js.map +1 -0
- package/dist/validation/cte.d.ts +54 -0
- package/dist/validation/cte.d.ts.map +1 -0
- package/dist/validation/cte.js +2 -0
- package/dist/validation/cte.js.map +1 -0
- package/dist/validation/dispatch.d.ts +31 -0
- package/dist/validation/dispatch.d.ts.map +1 -0
- package/dist/validation/dispatch.js +2 -0
- package/dist/validation/dispatch.js.map +1 -0
- package/dist/validation/joins.d.ts +16 -0
- package/dist/validation/joins.d.ts.map +1 -0
- package/dist/validation/joins.js +2 -0
- package/dist/validation/joins.js.map +1 -0
- package/dist/validation/return-derived.d.ts +67 -0
- package/dist/validation/return-derived.d.ts.map +1 -0
- package/dist/validation/return-derived.js +5 -0
- package/dist/validation/return-derived.js.map +1 -0
- package/dist/validation/return-types.d.ts +41 -0
- package/dist/validation/return-types.d.ts.map +1 -0
- package/dist/validation/return-types.js +2 -0
- package/dist/validation/return-types.js.map +1 -0
- package/dist/validation/validate-columns.d.ts +63 -0
- package/dist/validation/validate-columns.d.ts.map +1 -0
- package/dist/validation/validate-columns.js +2 -0
- package/dist/validation/validate-columns.js.map +1 -0
- package/dist/validation.d.ts +7 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +9 -0
- package/dist/validation.js.map +1 -0
- package/package.json +64 -0
- package/src/builder/assemble.ts +100 -0
- package/src/builder/condition-tree.ts +162 -0
- package/src/builder/conditional-sql.ts +325 -0
- package/src/builder/db.ts +281 -0
- package/src/builder/delete.ts +57 -0
- package/src/builder/extract-params.ts +507 -0
- package/src/builder/index.ts +58 -0
- package/src/builder/insert.ts +75 -0
- package/src/builder/mutate.ts +55 -0
- package/src/builder/params.ts +95 -0
- package/src/builder/return-type.ts +66 -0
- package/src/builder/scanner.ts +254 -0
- package/src/builder/select.ts +470 -0
- package/src/builder/sql-tag.ts +422 -0
- package/src/builder/sql.ts +51 -0
- package/src/builder/state.ts +55 -0
- package/src/builder/update.ts +77 -0
- package/src/builder/write-assemble.ts +52 -0
- package/src/builder/write-state.ts +43 -0
- package/src/builder/write-tag.ts +119 -0
- package/src/columns.ts +336 -0
- package/src/expressions.ts +745 -0
- package/src/index.ts +81 -0
- package/src/parsing/extract.ts +260 -0
- package/src/parsing/normalize.ts +243 -0
- package/src/parsing/pg-literals.ts +289 -0
- package/src/parsing/split.ts +288 -0
- package/src/parsing/string-utils.ts +172 -0
- package/src/parsing/tokenize.ts +321 -0
- package/src/parsing.ts +8 -0
- package/src/partial.ts +241 -0
- package/src/schema.ts +130 -0
- package/src/tables.ts +278 -0
- package/src/utils.ts +43 -0
- package/src/validation/cte.ts +198 -0
- package/src/validation/dispatch.ts +312 -0
- package/src/validation/joins.ts +198 -0
- package/src/validation/return-derived.ts +253 -0
- package/src/validation/return-types.ts +271 -0
- package/src/validation/validate-columns.ts +489 -0
- package/src/validation.ts +8 -0
|
@@ -0,0 +1,745 @@
|
|
|
1
|
+
import type { DatabaseSchema, ColumnTypeFromTableKey, RowTypeForTable, RowTypeForTables } from "./schema.js";
|
|
2
|
+
import type {
|
|
3
|
+
ColumnRef,
|
|
4
|
+
ColumnRefValidLooseWith,
|
|
5
|
+
ParseColumnRef,
|
|
6
|
+
QualifiedColumnRefs,
|
|
7
|
+
ResolveTableKey,
|
|
8
|
+
UnqualifiedColumnRefs,
|
|
9
|
+
UnqualifiedColumnValid
|
|
10
|
+
} from "./columns.js";
|
|
11
|
+
import type { AliasesInQuery, TablesInQuery } from "./tables.js";
|
|
12
|
+
import type {
|
|
13
|
+
CleanExpr,
|
|
14
|
+
CleanIdent,
|
|
15
|
+
ExtractAlias,
|
|
16
|
+
ExtractAliasResult,
|
|
17
|
+
ExtractBefore,
|
|
18
|
+
ExtractBeforeFromTopLevel,
|
|
19
|
+
IsIdentifier,
|
|
20
|
+
IsParamPlaceholder,
|
|
21
|
+
IsRuntimeStringFragment,
|
|
22
|
+
IsSqlConstant,
|
|
23
|
+
SqlConstantType,
|
|
24
|
+
SplitBalancedParen,
|
|
25
|
+
SplitTopLevel,
|
|
26
|
+
TokenizeLoose,
|
|
27
|
+
Trim
|
|
28
|
+
} from "./parsing.js";
|
|
29
|
+
import type { AllTrue } from "./utils.js";
|
|
30
|
+
|
|
31
|
+
// Expression parsing & types
|
|
32
|
+
|
|
33
|
+
export type IsIgnorableRuntimeExpr<E extends string> =
|
|
34
|
+
IsRuntimeStringFragment<E> extends true
|
|
35
|
+
? true
|
|
36
|
+
: [E] extends [" "]
|
|
37
|
+
? true
|
|
38
|
+
: false;
|
|
39
|
+
|
|
40
|
+
export type ExprToObject<
|
|
41
|
+
E extends string,
|
|
42
|
+
Tables extends string,
|
|
43
|
+
Aliases extends string,
|
|
44
|
+
S extends DatabaseSchema,
|
|
45
|
+
Nullable extends string = never
|
|
46
|
+
> =
|
|
47
|
+
IsIgnorableRuntimeExpr<E> extends true
|
|
48
|
+
? {}
|
|
49
|
+
: ExtractAliasResult<E> extends { expr: infer RawExpr extends string; alias: infer Alias }
|
|
50
|
+
? IsIgnorableRuntimeExpr<RawExpr> extends true
|
|
51
|
+
? [Alias] extends [never]
|
|
52
|
+
? {}
|
|
53
|
+
: Alias extends string
|
|
54
|
+
? { [K in Alias]: unknown }
|
|
55
|
+
: {}
|
|
56
|
+
: [Alias] extends [never]
|
|
57
|
+
? CleanIdent<RawExpr> extends "*"
|
|
58
|
+
? RowTypeForTables<Tables, S>
|
|
59
|
+
: CleanIdent<RawExpr> extends `${infer T}.*`
|
|
60
|
+
? MaybeNullableRow<RowTypeForTable<ResolveTableKey<CleanIdent<T>, Tables, Aliases, S>, S>, T, Nullable>
|
|
61
|
+
: ExprKey<E, Tables, Aliases, S> extends infer Key extends string | never
|
|
62
|
+
? Key extends string
|
|
63
|
+
? { [K in Key]: ApplyProjectionNull<ExprType<RawExpr, Tables, Aliases, S>, RawExpr, Tables, Aliases, S, Nullable> }
|
|
64
|
+
: Record<string, unknown>
|
|
65
|
+
: Record<string, unknown>
|
|
66
|
+
: Alias extends string
|
|
67
|
+
? { [K in Alias]: ApplyProjectionNull<ExprType<RawExpr, Tables, Aliases, S>, RawExpr, Tables, Aliases, S, Nullable> }
|
|
68
|
+
: Record<string, unknown>
|
|
69
|
+
: Record<string, unknown>;
|
|
70
|
+
|
|
71
|
+
// Outer-join nullability for a directly-projected column. `Nullable` is the set
|
|
72
|
+
// of reference qualifiers (aliases / table names) that are nullable due to an
|
|
73
|
+
// outer join (see `NullableRelations`). When the projected expression is a plain
|
|
74
|
+
// column ref (optionally wrapped in a cast) qualified by one of them, union
|
|
75
|
+
// `| null` onto its type. Function calls, concatenations, literals, and `*` are
|
|
76
|
+
// not plain qualified column refs, so they keep their computed type. The
|
|
77
|
+
// `[Nullable] extends [never]` guard makes join-free queries pay zero extra cost.
|
|
78
|
+
export type ApplyJoinNull<
|
|
79
|
+
T,
|
|
80
|
+
E extends string,
|
|
81
|
+
Nullable extends string
|
|
82
|
+
> =
|
|
83
|
+
[Nullable] extends [never]
|
|
84
|
+
? T
|
|
85
|
+
: RefQualifier<E> extends infer Q extends string
|
|
86
|
+
? [Q] extends [never]
|
|
87
|
+
? T
|
|
88
|
+
: Q extends Nullable
|
|
89
|
+
? T | null
|
|
90
|
+
: T
|
|
91
|
+
: T;
|
|
92
|
+
|
|
93
|
+
// The qualifier of a plain column ref (`tr."name"` -> `tr`), after stripping an
|
|
94
|
+
// outer cast (`tms."currency"::text` -> `tms`). `never` when the expression has
|
|
95
|
+
// no qualifier (bare column) or is not a plain column ref.
|
|
96
|
+
export type RefQualifier<E extends string> =
|
|
97
|
+
StripOuterCast<E> extends infer Inner extends string
|
|
98
|
+
? CleanExpr<Inner> extends `${infer Q}.${string}`
|
|
99
|
+
? CleanIdent<Q>
|
|
100
|
+
: never
|
|
101
|
+
: never;
|
|
102
|
+
|
|
103
|
+
export type StripOuterCast<E extends string> =
|
|
104
|
+
CleanExpr<E> extends `${infer Inner}::${string}`
|
|
105
|
+
? Inner
|
|
106
|
+
: CleanExpr<E> extends `cast(${infer Inner} as ${string})`
|
|
107
|
+
? Inner
|
|
108
|
+
: CleanExpr<E> extends `cast (${infer Inner} as ${string})`
|
|
109
|
+
? Inner
|
|
110
|
+
: E;
|
|
111
|
+
|
|
112
|
+
// Projection-level nullability dispatcher. Plain column refs go through
|
|
113
|
+
// `ApplyJoinNull` (qualifier-in-nullable-set check). A `coalesce(...)` projection
|
|
114
|
+
// (optionally wrapped in an outer cast like `coalesce(...)::text`) hides its column
|
|
115
|
+
// refs from `RefQualifier`, so `ApplyJoinNull` can never see them — we handle it
|
|
116
|
+
// here instead. SQL: `coalesce(a, b, c)` is NULL only when EVERY argument is NULL,
|
|
117
|
+
// so the projection gains `| null` only when all args are nullable (an outer-join
|
|
118
|
+
// nullable qualifier OR an already-nullable base type). A non-null literal
|
|
119
|
+
// (`coalesce(o.x, '')`) keeps the result non-null. `T` is the already-computed
|
|
120
|
+
// projection type (for the cast case, the cast's target type); we only add `| null`.
|
|
121
|
+
export type ApplyProjectionNull<
|
|
122
|
+
T,
|
|
123
|
+
E extends string,
|
|
124
|
+
Tables extends string,
|
|
125
|
+
Aliases extends string,
|
|
126
|
+
S extends DatabaseSchema,
|
|
127
|
+
Nullable extends string
|
|
128
|
+
> =
|
|
129
|
+
CleanExpr<StripOuterCast<E>> extends `coalesce(${infer Args})`
|
|
130
|
+
? CoalesceAllArgsNullable<SplitTopLevel<Args>, Tables, Aliases, S, Nullable> extends true
|
|
131
|
+
? T | null
|
|
132
|
+
: T
|
|
133
|
+
: ApplyJoinNull<T, E, Nullable>;
|
|
134
|
+
|
|
135
|
+
// True only when every coalesce argument is nullable. An empty/exhausted list is
|
|
136
|
+
// vacuously `true`, but the wrapper above only reaches this for a real coalesce call
|
|
137
|
+
// (at least one arg). Recursion is depth-capped like the other arg walkers.
|
|
138
|
+
export type CoalesceAllArgsNullable<
|
|
139
|
+
Args extends string[],
|
|
140
|
+
Tables extends string,
|
|
141
|
+
Aliases extends string,
|
|
142
|
+
S extends DatabaseSchema,
|
|
143
|
+
Nullable extends string,
|
|
144
|
+
Steps extends any[] = []
|
|
145
|
+
> = Steps["length"] extends 30
|
|
146
|
+
? true
|
|
147
|
+
: Args extends [infer H extends string, ...infer Rest extends string[]]
|
|
148
|
+
? CoalesceArgNullable<H, Tables, Aliases, S, Nullable> extends true
|
|
149
|
+
? CoalesceAllArgsNullable<Rest, Tables, Aliases, S, Nullable, [any, ...Steps]>
|
|
150
|
+
: false
|
|
151
|
+
: true;
|
|
152
|
+
|
|
153
|
+
// A single coalesce argument is nullable when its qualifier is in the outer-join
|
|
154
|
+
// nullable set (`ri.sku` under `left join ... ri`), or — failing that — when its
|
|
155
|
+
// base type already admits `null` (a base-nullable column, or an unresolved
|
|
156
|
+
// `unknown` arg). A non-null literal resolves to a non-null base type -> `false`.
|
|
157
|
+
export type CoalesceArgNullable<
|
|
158
|
+
Arg extends string,
|
|
159
|
+
Tables extends string,
|
|
160
|
+
Aliases extends string,
|
|
161
|
+
S extends DatabaseSchema,
|
|
162
|
+
Nullable extends string
|
|
163
|
+
> =
|
|
164
|
+
RefQualifier<Arg> extends infer Q extends string
|
|
165
|
+
? [Q] extends [never]
|
|
166
|
+
? null extends ExprType<Arg, Tables, Aliases, S>
|
|
167
|
+
? true
|
|
168
|
+
: false
|
|
169
|
+
: Q extends Nullable
|
|
170
|
+
? true
|
|
171
|
+
: null extends ExprType<Arg, Tables, Aliases, S>
|
|
172
|
+
? true
|
|
173
|
+
: false
|
|
174
|
+
: null extends ExprType<Arg, Tables, Aliases, S>
|
|
175
|
+
? true
|
|
176
|
+
: false;
|
|
177
|
+
|
|
178
|
+
// Nullablize every column of a `*`-expanded row when its qualifier is nullable.
|
|
179
|
+
export type MaybeNullableRow<Row, Qualifier extends string, Nullable extends string> =
|
|
180
|
+
[Nullable] extends [never]
|
|
181
|
+
? Row
|
|
182
|
+
: CleanIdent<Qualifier> extends Nullable
|
|
183
|
+
? { [K in keyof Row]: Row[K] | null }
|
|
184
|
+
: Row;
|
|
185
|
+
|
|
186
|
+
export type ExprKey<E extends string, Tables extends string, Aliases extends string, S extends DatabaseSchema> =
|
|
187
|
+
CleanIdent<E> extends "*" ? never :
|
|
188
|
+
CleanIdent<E> extends `${infer T}.*` ? never :
|
|
189
|
+
CleanExpr<E> extends `${infer Inner}::${string}`
|
|
190
|
+
? ColumnKeyFromExpr<Inner, Tables, Aliases, S>
|
|
191
|
+
: CleanExpr<E> extends `cast(${infer Inner} as ${string})`
|
|
192
|
+
? ColumnKeyFromExpr<Inner, Tables, Aliases, S>
|
|
193
|
+
: CleanExpr<E> extends `cast (${infer Inner} as ${string})`
|
|
194
|
+
? ColumnKeyFromExpr<Inner, Tables, Aliases, S>
|
|
195
|
+
: [ColumnKeyFromExpr<E, Tables, Aliases, S>] extends [never]
|
|
196
|
+
? FunctionKeyFromExpr<E>
|
|
197
|
+
: ColumnKeyFromExpr<E, Tables, Aliases, S>;
|
|
198
|
+
|
|
199
|
+
export type ColumnKeyFromExpr<E extends string, Tables extends string, Aliases extends string, S extends DatabaseSchema> =
|
|
200
|
+
ParseColumnRef<CleanExpr<E>, Tables, Aliases, S> extends infer Ref
|
|
201
|
+
? Ref extends ColumnRef<any, any>
|
|
202
|
+
? Ref["column"]
|
|
203
|
+
: never
|
|
204
|
+
: never;
|
|
205
|
+
|
|
206
|
+
export type FunctionKeyFromExpr<E extends string> =
|
|
207
|
+
CleanExpr<E> extends `case ${string}` ? "case" :
|
|
208
|
+
CleanExpr<E> extends `${infer Func}(${string}`
|
|
209
|
+
? CleanIdent<Func>
|
|
210
|
+
: CleanExpr<E> extends `${infer Func} (${string}`
|
|
211
|
+
? CleanIdent<Func>
|
|
212
|
+
: never;
|
|
213
|
+
|
|
214
|
+
// A projected expression whose top-level operator is a comparison
|
|
215
|
+
// (`<`, `>`, `<=`, `>=`, `<>`, `!=`, `=`) yields `boolean`. CASE expressions are
|
|
216
|
+
// excluded: their `when … > …` comparison sits at top level but the expression's
|
|
217
|
+
// type is the THEN/ELSE branch, not boolean. A `case` wrapped in parens (e.g.
|
|
218
|
+
// `(case … end)`) is already protected — its inner comparison is below depth 0.
|
|
219
|
+
export type IsBoolExpr<CE extends string> =
|
|
220
|
+
CE extends `case ${string}`
|
|
221
|
+
? false
|
|
222
|
+
: HasTopLevelCompare<CE>;
|
|
223
|
+
|
|
224
|
+
// Scans for a comparison operator outside parens and outside `'…'`/`"…"` quotes.
|
|
225
|
+
// `->>`, `#>>` and `::` are consumed as units so their `>`/`:` are not mistaken
|
|
226
|
+
// for comparisons. Modelled on the char-walker in `SplitTopLevel` (parsing.ts).
|
|
227
|
+
export type HasTopLevelCompare<
|
|
228
|
+
S extends string,
|
|
229
|
+
Depth extends any[] = [],
|
|
230
|
+
Steps extends any[] = [],
|
|
231
|
+
InQ extends boolean = false,
|
|
232
|
+
InDQ extends boolean = false
|
|
233
|
+
> = Steps["length"] extends 400
|
|
234
|
+
? false
|
|
235
|
+
: S extends `${infer C}${infer Rest}`
|
|
236
|
+
? InQ extends true
|
|
237
|
+
? HasTopLevelCompare<Rest, Depth, [any, ...Steps], C extends "'" ? false : true, InDQ>
|
|
238
|
+
: InDQ extends true
|
|
239
|
+
? HasTopLevelCompare<Rest, Depth, [any, ...Steps], InQ, C extends `"` ? false : true>
|
|
240
|
+
: C extends "'"
|
|
241
|
+
? HasTopLevelCompare<Rest, Depth, [any, ...Steps], true, InDQ>
|
|
242
|
+
: C extends `"`
|
|
243
|
+
? HasTopLevelCompare<Rest, Depth, [any, ...Steps], InQ, true>
|
|
244
|
+
: C extends "("
|
|
245
|
+
? HasTopLevelCompare<Rest, [any, ...Depth], [any, ...Steps], InQ, InDQ>
|
|
246
|
+
: C extends ")"
|
|
247
|
+
? HasTopLevelCompare<Rest, Depth extends [any, ...infer D] ? D : [], [any, ...Steps], InQ, InDQ>
|
|
248
|
+
: Depth["length"] extends 0
|
|
249
|
+
// Consume multi-char operators whose `<`/`>`/`:` are NOT comparisons:
|
|
250
|
+
// JSON access (`->`, `->>`, `#>`, `#>>`), containment (`@>`, `<@`),
|
|
251
|
+
// cast (`::`), and bit-shift (`<<`, `>>`). Longer forms first.
|
|
252
|
+
? S extends `->>${infer R}`
|
|
253
|
+
? HasTopLevelCompare<R, Depth, [any, ...Steps], InQ, InDQ>
|
|
254
|
+
: S extends `->${infer R}`
|
|
255
|
+
? HasTopLevelCompare<R, Depth, [any, ...Steps], InQ, InDQ>
|
|
256
|
+
: S extends `#>>${infer R}`
|
|
257
|
+
? HasTopLevelCompare<R, Depth, [any, ...Steps], InQ, InDQ>
|
|
258
|
+
: S extends `#>${infer R}`
|
|
259
|
+
? HasTopLevelCompare<R, Depth, [any, ...Steps], InQ, InDQ>
|
|
260
|
+
: S extends `@>${infer R}`
|
|
261
|
+
? HasTopLevelCompare<R, Depth, [any, ...Steps], InQ, InDQ>
|
|
262
|
+
: S extends `<@${infer R}`
|
|
263
|
+
? HasTopLevelCompare<R, Depth, [any, ...Steps], InQ, InDQ>
|
|
264
|
+
: S extends `::${infer R}`
|
|
265
|
+
? HasTopLevelCompare<R, Depth, [any, ...Steps], InQ, InDQ>
|
|
266
|
+
: S extends `<<${infer R}`
|
|
267
|
+
? HasTopLevelCompare<R, Depth, [any, ...Steps], InQ, InDQ>
|
|
268
|
+
: S extends `>>${infer R}`
|
|
269
|
+
? HasTopLevelCompare<R, Depth, [any, ...Steps], InQ, InDQ>
|
|
270
|
+
: C extends "<" | ">" | "=" | "!"
|
|
271
|
+
? true
|
|
272
|
+
: HasTopLevelCompare<Rest, Depth, [any, ...Steps], InQ, InDQ>
|
|
273
|
+
: HasTopLevelCompare<Rest, Depth, [any, ...Steps], InQ, InDQ>
|
|
274
|
+
: false;
|
|
275
|
+
|
|
276
|
+
// Strip a redundant fully-wrapping paren pair, repeatedly: `((expr))` and
|
|
277
|
+
// `((case ...)::text)` -> the inner expression whose parens wrap the WHOLE
|
|
278
|
+
// thing. Without this an outer operator hidden by a redundant wrap (e.g. the
|
|
279
|
+
// `::text` in `((case ...)::text)`) is not seen as top-level, so the cast is
|
|
280
|
+
// missed and the expression misparses (empty-function) to `unknown`.
|
|
281
|
+
// - A subquery `(select ...)` KEEPS its parens — its detection relies on them.
|
|
282
|
+
// - A trailing operator after the matching close (`(a)::int`, `(a) + b`) means
|
|
283
|
+
// the parens do NOT wrap the whole expression (`rest != ""`), so it is left
|
|
284
|
+
// as-is and the real outer operator is handled normally.
|
|
285
|
+
type UnwrapRedundantParens<E extends string, Steps extends any[] = []> =
|
|
286
|
+
Steps["length"] extends 12
|
|
287
|
+
? E
|
|
288
|
+
: E extends `(select ${string})`
|
|
289
|
+
? E
|
|
290
|
+
: E extends `(${string}`
|
|
291
|
+
? SplitBalancedParen<E> extends { inner: infer Inner extends string; rest: "" }
|
|
292
|
+
? UnwrapRedundantParens<Trim<Inner>, [any, ...Steps]>
|
|
293
|
+
: E
|
|
294
|
+
: E;
|
|
295
|
+
|
|
296
|
+
export type ExprType<
|
|
297
|
+
E extends string,
|
|
298
|
+
Tables extends string,
|
|
299
|
+
Aliases extends string,
|
|
300
|
+
S extends DatabaseSchema,
|
|
301
|
+
Steps extends any[] = []
|
|
302
|
+
> =
|
|
303
|
+
Steps["length"] extends 25
|
|
304
|
+
? unknown
|
|
305
|
+
: IsIgnorableRuntimeExpr<E> extends true
|
|
306
|
+
? unknown
|
|
307
|
+
: UnwrapRedundantParens<CleanExpr<E>> extends infer CE extends string
|
|
308
|
+
? IsRuntimeStringFragment<CE> extends true
|
|
309
|
+
? unknown
|
|
310
|
+
: CE extends "*"
|
|
311
|
+
? RowTypeForTables<Tables, S>
|
|
312
|
+
: CE extends `${infer T}.*`
|
|
313
|
+
? RowTypeForTable<ResolveTableKey<CleanIdent<T>, Tables, Aliases, S>, S>
|
|
314
|
+
: CE extends `(select ${infer SubBody})`
|
|
315
|
+
? ScalarSubqueryType<SubBody, S, [any, ...Steps]>
|
|
316
|
+
: IsBoolExpr<CE> extends true
|
|
317
|
+
? boolean
|
|
318
|
+
// A CASE expression is always `unknown` by design (the
|
|
319
|
+
// THEN/ELSE branch type is not inferred). Short-circuit here
|
|
320
|
+
// BEFORE the function/operator cascade so a large
|
|
321
|
+
// `(case when count(distinct <big expr>) ... end)` is not
|
|
322
|
+
// fully resolved (incl. its aggregate args) just to arrive at
|
|
323
|
+
// `unknown` — that resolution is pure cost that starves the
|
|
324
|
+
// per-query instantiation budget on wide SELECTs. A CASE with
|
|
325
|
+
// an OUTER cast (`(case ...)::text`) does NOT match here (it
|
|
326
|
+
// ends in the type name, not `)`), so it still takes its cast
|
|
327
|
+
// type via the branch below.
|
|
328
|
+
: IsCaseExpr<CE> extends true
|
|
329
|
+
? unknown
|
|
330
|
+
: [OuterCastName<CE>] extends [never]
|
|
331
|
+
// No TOP-LEVEL `::` cast: any `::` present is nested
|
|
332
|
+
// inside a function/paren arg (e.g. `f(a::int)`, or the
|
|
333
|
+
// inner casts of `sum(g(x::numeric))::float8`), so the
|
|
334
|
+
// cast is not the outer operator — fall through to the
|
|
335
|
+
// cast(...)/function/operator cascade below.
|
|
336
|
+
? CE extends `cast(${infer Inner} as ${infer CastTypeName})`
|
|
337
|
+
? ExprType<Inner, Tables, Aliases, S, [any, ...Steps]> extends never
|
|
338
|
+
? never
|
|
339
|
+
: SqlTypeToTs<CastTypeName>
|
|
340
|
+
: CE extends `cast (${infer Inner} as ${infer CastTypeName})`
|
|
341
|
+
? ExprType<Inner, Tables, Aliases, S, [any, ...Steps]> extends never
|
|
342
|
+
? never
|
|
343
|
+
: SqlTypeToTs<CastTypeName>
|
|
344
|
+
: CE extends `${infer Func}(${infer Args})`
|
|
345
|
+
? FunctionReturn<CleanIdent<Func>, Args, Tables, Aliases, S, [any, ...Steps]>
|
|
346
|
+
: CE extends `${infer Func} (${infer Args})`
|
|
347
|
+
? FunctionReturn<CleanIdent<Func>, Args, Tables, Aliases, S, [any, ...Steps]>
|
|
348
|
+
: CE extends `${string}||${string}`
|
|
349
|
+
? string
|
|
350
|
+
: CE extends `${infer JBase}->>${string}`
|
|
351
|
+
? ExprType<JBase, Tables, Aliases, S, [any, ...Steps]> extends never
|
|
352
|
+
? never
|
|
353
|
+
: string
|
|
354
|
+
: CE extends `${infer JBase}#>>${string}`
|
|
355
|
+
? ExprType<JBase, Tables, Aliases, S, [any, ...Steps]> extends never
|
|
356
|
+
? never
|
|
357
|
+
: string
|
|
358
|
+
: CE extends "null"
|
|
359
|
+
? null
|
|
360
|
+
: CE extends `'${infer L}'`
|
|
361
|
+
? string
|
|
362
|
+
: CE extends `${number}`
|
|
363
|
+
? number
|
|
364
|
+
: CE extends "true"
|
|
365
|
+
? boolean
|
|
366
|
+
: CE extends "false"
|
|
367
|
+
? boolean
|
|
368
|
+
: IsSqlConstant<CE> extends true
|
|
369
|
+
? SqlConstantType<CE>
|
|
370
|
+
: IsParamPlaceholder<CE> extends true
|
|
371
|
+
? unknown
|
|
372
|
+
: [ParseColumnRef<CE, Tables, Aliases, S>] extends [infer Ref]
|
|
373
|
+
? [Ref] extends [never]
|
|
374
|
+
? IsIdentifier<CE> extends true
|
|
375
|
+
? never
|
|
376
|
+
: unknown
|
|
377
|
+
: Ref extends ColumnRef<infer TableKey extends string, infer Column extends string>
|
|
378
|
+
? ColumnTypeFromTableKey<TableKey, Column, S>
|
|
379
|
+
: IsIdentifier<CE> extends true
|
|
380
|
+
? never
|
|
381
|
+
: unknown
|
|
382
|
+
: unknown
|
|
383
|
+
// A genuine TOP-LEVEL `::T` cast. As with the JSON-text
|
|
384
|
+
// operators, a `->>` / `#>>` to the right of the cast type
|
|
385
|
+
// name means the cast is NOT the outermost operator — the
|
|
386
|
+
// JSON text extraction is, yielding `string`.
|
|
387
|
+
: OuterCastName<CE> extends `${string}->>${string}`
|
|
388
|
+
? string
|
|
389
|
+
: OuterCastName<CE> extends `${string}#>>${string}`
|
|
390
|
+
? string
|
|
391
|
+
// The `extends never` guard exists only to surface a
|
|
392
|
+
// BARE invalid column (`not_a_col::text` -> never).
|
|
393
|
+
// Run it only when the cast's inner is a simple ref;
|
|
394
|
+
// for a COMPOUND inner (`sum(...)::float8`,
|
|
395
|
+
// `(bool_and(...) ...)::boolean`) take the cast type
|
|
396
|
+
// directly — fully type-resolving such inners just to
|
|
397
|
+
// check `never` is a large, needless instantiation
|
|
398
|
+
// cost (it starves the per-query budget so later
|
|
399
|
+
// projections in a wide SELECT bail to `never`), and
|
|
400
|
+
// hidden bad refs are caught by the SELECT-list
|
|
401
|
+
// validator, not here.
|
|
402
|
+
: CastInnerIsSimpleRef<OuterCastInner<CE>> extends true
|
|
403
|
+
? ExprType<OuterCastInner<CE>, Tables, Aliases, S, [any, ...Steps]> extends never
|
|
404
|
+
? never
|
|
405
|
+
: SqlTypeToTs<OuterCastName<CE>>
|
|
406
|
+
: SqlTypeToTs<OuterCastName<CE>>
|
|
407
|
+
: unknown;
|
|
408
|
+
|
|
409
|
+
// Scalar subquery in an expression position -> the type of its single
|
|
410
|
+
// projected column. `SubBody` is everything after `(select `, e.g.
|
|
411
|
+
// "count(*) from payments p where p.order_id = o.id". We extract the inner
|
|
412
|
+
// select list (paren/quote-aware, stopping at the inner top-level FROM), take
|
|
413
|
+
// its first projected expression, and type it against the SUBQUERY's own
|
|
414
|
+
// tables/aliases so correlated column refs (e.g. max(amount)) resolve. Inner
|
|
415
|
+
// column refs against the OUTER query (correlation) fall back to unknown, which
|
|
416
|
+
// is acceptable for a scalar result type.
|
|
417
|
+
export type ScalarSubqueryType<
|
|
418
|
+
SubBody extends string,
|
|
419
|
+
S extends DatabaseSchema,
|
|
420
|
+
Steps extends any[]
|
|
421
|
+
> =
|
|
422
|
+
ExtractBeforeFromTopLevel<SubBody> extends infer SL extends string
|
|
423
|
+
? SplitTopLevel<SL> extends [infer First extends string, ...infer _Rest]
|
|
424
|
+
? First extends string
|
|
425
|
+
? `select ${SubBody}` extends infer SubQuery extends string
|
|
426
|
+
? TablesInQuery<SubQuery, S> extends infer SubTables extends string
|
|
427
|
+
? AliasesInQuery<SubQuery, S> extends infer SubAliases extends string
|
|
428
|
+
? ExtractAliasResult<First> extends { expr: infer RawExpr extends string }
|
|
429
|
+
? ExprType<RawExpr, SubTables, SubAliases, S, Steps>
|
|
430
|
+
: unknown
|
|
431
|
+
: unknown
|
|
432
|
+
: unknown
|
|
433
|
+
: unknown
|
|
434
|
+
: unknown
|
|
435
|
+
: unknown
|
|
436
|
+
: unknown;
|
|
437
|
+
|
|
438
|
+
// Function returns
|
|
439
|
+
|
|
440
|
+
export type FunctionReturn<
|
|
441
|
+
Func extends string,
|
|
442
|
+
Args extends string,
|
|
443
|
+
Tables extends string,
|
|
444
|
+
Aliases extends string,
|
|
445
|
+
S extends DatabaseSchema,
|
|
446
|
+
Steps extends any[] = []
|
|
447
|
+
> =
|
|
448
|
+
Steps["length"] extends 25
|
|
449
|
+
? unknown
|
|
450
|
+
: ArgsValid<Args, Tables, Aliases, S, Steps> extends false
|
|
451
|
+
? never
|
|
452
|
+
: Func extends "count"
|
|
453
|
+
? number
|
|
454
|
+
: Func extends "sum" | "avg"
|
|
455
|
+
? number
|
|
456
|
+
: Func extends "min" | "max"
|
|
457
|
+
? FirstArgType<Args, Tables, Aliases, S, Steps>
|
|
458
|
+
: Func extends "upper" | "lower" | "concat"
|
|
459
|
+
? string
|
|
460
|
+
: Func extends "coalesce"
|
|
461
|
+
? UnionArgTypes<Args, Tables, Aliases, S, Steps>
|
|
462
|
+
: unknown;
|
|
463
|
+
|
|
464
|
+
// Expression validation
|
|
465
|
+
|
|
466
|
+
export type ExprsValid<Exprs extends string[], N extends string, S extends DatabaseSchema> =
|
|
467
|
+
TablesInQuery<N, S> extends infer Tables extends string
|
|
468
|
+
? AliasesInQuery<N, S> extends infer Aliases extends string
|
|
469
|
+
? ExprsValidList<Exprs, Tables, Aliases, S>
|
|
470
|
+
: true
|
|
471
|
+
: true;
|
|
472
|
+
|
|
473
|
+
export type ExprValid<
|
|
474
|
+
E extends string,
|
|
475
|
+
Tables extends string,
|
|
476
|
+
Aliases extends string,
|
|
477
|
+
S extends DatabaseSchema
|
|
478
|
+
> =
|
|
479
|
+
IsIgnorableRuntimeExpr<E> extends true
|
|
480
|
+
? true
|
|
481
|
+
: ExtractAlias<E> extends { expr: infer RawExpr extends string }
|
|
482
|
+
? IsIgnorableRuntimeExpr<RawExpr> extends true
|
|
483
|
+
? true
|
|
484
|
+
: ExprType<RawExpr, Tables, Aliases, S> extends never
|
|
485
|
+
? false
|
|
486
|
+
: NeedsTokenRefValidation<RawExpr> extends true
|
|
487
|
+
? ExprColumnRefsValid<RawExpr, Tables, Aliases, S>
|
|
488
|
+
: FuncCompoundArgsValid<RawExpr, Tables, Aliases, S>
|
|
489
|
+
: true;
|
|
490
|
+
|
|
491
|
+
// A function-call (or cast) projection skips the token ref-scan above
|
|
492
|
+
// (`NeedsTokenRefValidation` is false for `${fn}(${args})`), which is why an
|
|
493
|
+
// invalid column hidden inside an aggregate/function argument — `sum(price +
|
|
494
|
+
// bogus_col)`, `date_trunc('day', bogus_col)`, `array_agg(bogus_col ORDER BY
|
|
495
|
+
// created_at)` — escapes validation while the same ref written OUTSIDE a function
|
|
496
|
+
// is caught. Recover that case here, in the SELECT-list validation path ONLY (not
|
|
497
|
+
// `ExprType`, so the return-type/`QueryResult` path and its instantiation cost are
|
|
498
|
+
// untouched).
|
|
499
|
+
//
|
|
500
|
+
// We extract the call's argument list and run each argument through the same
|
|
501
|
+
// loose column-ref scan the rest of the query uses (`ExprColumnRefsValid`), which
|
|
502
|
+
// already skips string literals, numbers, params, operators, and SQL keywords —
|
|
503
|
+
// so an aggregate-local `ORDER BY` (`array_agg(id ORDER BY created_at)`) and
|
|
504
|
+
// keyword-style args resolve their genuine column surfaces while non-columns stay
|
|
505
|
+
// lenient. The `EXTRACT(field FROM source)` date-part keyword grammar is handled
|
|
506
|
+
// upstream by `RewriteExtractCall` (rewritten to `extract(source)`) so the field
|
|
507
|
+
// token is never seen here. The `extends false` guard rejects only on a DEFINITE
|
|
508
|
+
// invalid column.
|
|
509
|
+
export type FuncCompoundArgsValid<
|
|
510
|
+
E extends string,
|
|
511
|
+
Tables extends string,
|
|
512
|
+
Aliases extends string,
|
|
513
|
+
S extends DatabaseSchema
|
|
514
|
+
> =
|
|
515
|
+
ExtractFuncArgList<CleanExpr<E>> extends infer Args extends string
|
|
516
|
+
? [Args] extends [never]
|
|
517
|
+
? true
|
|
518
|
+
: ArgsArithRefsValid<SplitTopLevel<Args>, Tables, Aliases, S>
|
|
519
|
+
: true;
|
|
520
|
+
|
|
521
|
+
// The inner argument list of the FIRST `(...)` group, or `never` when the
|
|
522
|
+
// expression is not a call (e.g. a cast like `x::int`, which must stay lenient).
|
|
523
|
+
export type ExtractFuncArgList<E extends string> =
|
|
524
|
+
E extends `${infer _Func}(${infer AfterOpen}`
|
|
525
|
+
? SplitBalancedParen<`(${AfterOpen}`> extends { inner: infer Inner extends string }
|
|
526
|
+
? Inner
|
|
527
|
+
: never
|
|
528
|
+
: never;
|
|
529
|
+
|
|
530
|
+
export type ArgsArithRefsValid<
|
|
531
|
+
Args extends string[],
|
|
532
|
+
Tables extends string,
|
|
533
|
+
Aliases extends string,
|
|
534
|
+
S extends DatabaseSchema,
|
|
535
|
+
Steps extends any[] = []
|
|
536
|
+
> = Steps["length"] extends 30
|
|
537
|
+
? true
|
|
538
|
+
: Args extends [infer H extends string, ...infer Rest extends string[]]
|
|
539
|
+
// The loose ref-scan inside `ExprColumnRefsValid` tokenizes (padding
|
|
540
|
+
// operators itself) and only surfaces genuine column candidates, so each
|
|
541
|
+
// argument — bare column, arithmetic, CASE, or aggregate-local `ORDER BY`
|
|
542
|
+
// — is validated while literals/params/keywords stay lenient.
|
|
543
|
+
? Trim<H> extends infer TH extends string
|
|
544
|
+
? ExprColumnRefsValid<TH, Tables, Aliases, S> extends false
|
|
545
|
+
? false
|
|
546
|
+
: ArgsArithRefsValid<Rest, Tables, Aliases, S, [any, ...Steps]>
|
|
547
|
+
: ArgsArithRefsValid<Rest, Tables, Aliases, S, [any, ...Steps]>
|
|
548
|
+
: true;
|
|
549
|
+
|
|
550
|
+
export type NeedsTokenRefValidation<E extends string> =
|
|
551
|
+
CleanExpr<E> extends `${string}::${string}` ? false :
|
|
552
|
+
CleanExpr<E> extends `cast(${string} as ${string})` ? false :
|
|
553
|
+
CleanExpr<E> extends `cast (${string} as ${string})` ? false :
|
|
554
|
+
CleanExpr<E> extends `${string}(${string}` ? false :
|
|
555
|
+
CleanExpr<E> extends `${string} (${string}` ? false :
|
|
556
|
+
true;
|
|
557
|
+
|
|
558
|
+
export type ExprColumnRefsValid<
|
|
559
|
+
E extends string,
|
|
560
|
+
Tables extends string,
|
|
561
|
+
Aliases extends string,
|
|
562
|
+
S extends DatabaseSchema
|
|
563
|
+
> = ExprQualifiedRefsValid<E, Tables, Aliases, S> extends true
|
|
564
|
+
? ExprUnqualifiedRefsValid<E, Tables, Aliases, S>
|
|
565
|
+
: false;
|
|
566
|
+
|
|
567
|
+
export type ExprQualifiedRefsValid<
|
|
568
|
+
E extends string,
|
|
569
|
+
Tables extends string,
|
|
570
|
+
Aliases extends string,
|
|
571
|
+
S extends DatabaseSchema
|
|
572
|
+
> = QualifiedColumnRefs<TokenizeLoose<E>, S, Tables, Aliases> extends infer Cols
|
|
573
|
+
? AllTrue<Cols extends string ? ColumnRefValidLooseWith<Cols, Tables, Aliases, S> : true>
|
|
574
|
+
: true;
|
|
575
|
+
|
|
576
|
+
export type ExprUnqualifiedRefsValid<
|
|
577
|
+
E extends string,
|
|
578
|
+
Tables extends string,
|
|
579
|
+
Aliases extends string,
|
|
580
|
+
S extends DatabaseSchema
|
|
581
|
+
> = UnqualifiedColumnRefs<TokenizeLoose<E>, S, Tables, Aliases> extends infer Cols
|
|
582
|
+
? AllTrue<Cols extends string ? UnqualifiedColumnValid<Cols, Tables, Aliases, S> : true>
|
|
583
|
+
: true;
|
|
584
|
+
|
|
585
|
+
export type ExprsValidList<
|
|
586
|
+
Exprs extends string[],
|
|
587
|
+
Tables extends string,
|
|
588
|
+
Aliases extends string,
|
|
589
|
+
S extends DatabaseSchema,
|
|
590
|
+
Steps extends any[] = []
|
|
591
|
+
> = Steps["length"] extends 100
|
|
592
|
+
? true
|
|
593
|
+
: Exprs extends [infer H extends string, ...infer Rest extends string[]]
|
|
594
|
+
? ExprValid<H, Tables, Aliases, S> extends true
|
|
595
|
+
? ExprsValidList<Rest, Tables, Aliases, S, [any, ...Steps]>
|
|
596
|
+
: false
|
|
597
|
+
: true;
|
|
598
|
+
|
|
599
|
+
// Argument parsing
|
|
600
|
+
|
|
601
|
+
export type FirstArgType<
|
|
602
|
+
Args extends string,
|
|
603
|
+
Tables extends string,
|
|
604
|
+
Aliases extends string,
|
|
605
|
+
S extends DatabaseSchema,
|
|
606
|
+
Steps extends any[]
|
|
607
|
+
> =
|
|
608
|
+
SplitTopLevel<Args> extends [infer First extends string, ...infer _]
|
|
609
|
+
? ExprType<First, Tables, Aliases, S, Steps>
|
|
610
|
+
: unknown;
|
|
611
|
+
|
|
612
|
+
export type UnionArgTypes<
|
|
613
|
+
Args extends string,
|
|
614
|
+
Tables extends string,
|
|
615
|
+
Aliases extends string,
|
|
616
|
+
S extends DatabaseSchema,
|
|
617
|
+
Steps extends any[]
|
|
618
|
+
> =
|
|
619
|
+
SplitTopLevel<Args> extends infer Parts extends string[]
|
|
620
|
+
? Parts[number] extends infer P extends string
|
|
621
|
+
? ExprType<P, Tables, Aliases, S, Steps>
|
|
622
|
+
: unknown
|
|
623
|
+
: unknown;
|
|
624
|
+
|
|
625
|
+
export type ArgsValid<
|
|
626
|
+
Args extends string,
|
|
627
|
+
Tables extends string,
|
|
628
|
+
Aliases extends string,
|
|
629
|
+
S extends DatabaseSchema,
|
|
630
|
+
Steps extends any[]
|
|
631
|
+
> =
|
|
632
|
+
Trim<Args> extends ""
|
|
633
|
+
? true
|
|
634
|
+
: SplitTopLevel<Args> extends infer Parts extends string[]
|
|
635
|
+
? AllTrue<Parts[number] extends infer P extends string ? (ExprType<P, Tables, Aliases, S, Steps> extends never ? false : true) : true>
|
|
636
|
+
: true;
|
|
637
|
+
|
|
638
|
+
// SQL type mapping
|
|
639
|
+
|
|
640
|
+
// Maps a SQL cast target type name to its TypeScript type.
|
|
641
|
+
// Handles, in order: chained casts (`a::int::text` -> last type wins), array
|
|
642
|
+
// suffixes (`int[]` -> number[]), and finally a flat scalar mapping.
|
|
643
|
+
export type SqlTypeToTs<T extends string> =
|
|
644
|
+
Trim<T> extends `${string}::${infer Rest}`
|
|
645
|
+
? SqlTypeToTs<Rest>
|
|
646
|
+
: Trim<T> extends `${infer Base}[]`
|
|
647
|
+
? SqlScalarToTs<NormalizeTypeName<Base>>[]
|
|
648
|
+
: SqlScalarToTs<NormalizeTypeName<T>>;
|
|
649
|
+
|
|
650
|
+
export type SqlScalarToTs<N extends string> =
|
|
651
|
+
N extends "int" | "integer" | "bigint" | "smallint" | "numeric" | "decimal" | "real" | "double" | "float"
|
|
652
|
+
| "int2" | "int4" | "int8" | "float4" | "float8"
|
|
653
|
+
? number
|
|
654
|
+
: N extends "bool" | "boolean"
|
|
655
|
+
? boolean
|
|
656
|
+
: N extends "text" | "varchar" | "char" | "character" | "uuid"
|
|
657
|
+
? string
|
|
658
|
+
: N extends "date" | "time" | "timestamp" | "timestamptz"
|
|
659
|
+
? string
|
|
660
|
+
: N extends "json" | "jsonb"
|
|
661
|
+
? unknown
|
|
662
|
+
: N extends "bytea" | "blob"
|
|
663
|
+
? Uint8Array
|
|
664
|
+
: unknown;
|
|
665
|
+
|
|
666
|
+
export type NormalizeTypeName<S extends string> =
|
|
667
|
+
CleanIdent<ExtractBefore<Trim<S>, "(">>;
|
|
668
|
+
|
|
669
|
+
// The TS type implied by an expression's OUTER cast (`expr::int`,
|
|
670
|
+
// `cast(expr as int)`), if any. Mirrors `ExprType`'s cast detection but is
|
|
671
|
+
// self-contained — used where the full `ExprType` context isn't available
|
|
672
|
+
// (e.g. typing an outer projection over a derived subquery, whose column refs
|
|
673
|
+
// resolve against the subquery row, not the schema). Returns `unknown` when
|
|
674
|
+
// there is no outer cast or the cast target doesn't map to a known scalar, so
|
|
675
|
+
// callers that fall back to `unknown` are never made worse.
|
|
676
|
+
export type OuterCastTs<E extends string> =
|
|
677
|
+
CleanExpr<E> extends `${string}::${infer CastName}`
|
|
678
|
+
? SqlTypeToTs<CastName>
|
|
679
|
+
: CleanExpr<E> extends `cast(${string} as ${infer CastName})`
|
|
680
|
+
? SqlTypeToTs<CastName>
|
|
681
|
+
: CleanExpr<E> extends `cast (${string} as ${infer CastName})`
|
|
682
|
+
? SqlTypeToTs<CastName>
|
|
683
|
+
: unknown;
|
|
684
|
+
|
|
685
|
+
// --- Top-level (outer) `::` cast detection --------------------------------
|
|
686
|
+
// A `::T` cast is the OUTER operator of an expression only when it sits at paren
|
|
687
|
+
// depth 0. Splitting at the LEFTMOST `::` (the naive `${infer Inner}::${infer T}`)
|
|
688
|
+
// is wrong for casts nested in call args: `sum(g(x::numeric))::float8` would split
|
|
689
|
+
// at `::numeric`, leaving the unbalanced `sum(g(x` as the inner expr (which types
|
|
690
|
+
// to `never`) and poisoning the whole projection. Cheap top-level signal: scanning
|
|
691
|
+
// `::` left-to-right, the type-name part of a TOP-LEVEL cast contains no `)` — a
|
|
692
|
+
// `)` after the `::` means an unclosed `(` precedes it, so that `::` is nested.
|
|
693
|
+
//
|
|
694
|
+
// `OuterCastName<E>` -> the outer cast's type-name (possibly chained, e.g.
|
|
695
|
+
// `int::text`, which `SqlTypeToTs` resolves last-wins), or `never` when there is
|
|
696
|
+
// no top-level cast. `OuterCastInner<E>` -> `E` with that outer cast stripped, with
|
|
697
|
+
// any inner casts preserved.
|
|
698
|
+
//
|
|
699
|
+
// A `::` is NESTED (not the outer operator) when the text to its right has an
|
|
700
|
+
// UNMATCHED closing paren — a `)` with no `(` before it (the `)` closes a `(` that
|
|
701
|
+
// opened to the LEFT of the `::`). A parameterized type name such as
|
|
702
|
+
// `numeric(10,2)` has its `(` BEFORE the `)`, so it is NOT flagged and the cast
|
|
703
|
+
// stays top-level.
|
|
704
|
+
type CastAfterIsNested<After extends string> =
|
|
705
|
+
After extends `${infer P})${string}`
|
|
706
|
+
? P extends `${string}(${string}`
|
|
707
|
+
? false
|
|
708
|
+
: true
|
|
709
|
+
: false;
|
|
710
|
+
|
|
711
|
+
export type OuterCastName<E extends string> =
|
|
712
|
+
E extends `${string}::${infer After}`
|
|
713
|
+
? CastAfterIsNested<After> extends true
|
|
714
|
+
? OuterCastName<After>
|
|
715
|
+
: After
|
|
716
|
+
: never;
|
|
717
|
+
|
|
718
|
+
export type OuterCastInner<E extends string, Acc extends string = ""> =
|
|
719
|
+
E extends `${infer A}::${infer After}`
|
|
720
|
+
? CastAfterIsNested<After> extends true
|
|
721
|
+
? OuterCastInner<After, `${Acc}${A}::`>
|
|
722
|
+
: `${Acc}${A}`
|
|
723
|
+
: E;
|
|
724
|
+
|
|
725
|
+
// A cast's inner expression is a "simple ref" — a bare (optionally qualified /
|
|
726
|
+
// quoted) column or identifier — when, after trimming, it contains no `(` (no
|
|
727
|
+
// call / paren group) and no space (no operator / compound expression). Only then
|
|
728
|
+
// is the invalid-bare-column `extends never` guard meaningful (and cheap); a
|
|
729
|
+
// compound inner takes its cast type directly.
|
|
730
|
+
export type CastInnerIsSimpleRef<I extends string> =
|
|
731
|
+
Trim<I> extends `${string}(${string}`
|
|
732
|
+
? false
|
|
733
|
+
: Trim<I> extends `${string} ${string}`
|
|
734
|
+
? false
|
|
735
|
+
: true;
|
|
736
|
+
|
|
737
|
+
// A top-level CASE expression — `case ...`, optionally wrapped in balanced parens
|
|
738
|
+
// (`(case ... end)`). Used to short-circuit CASE typing to `unknown` (its design
|
|
739
|
+
// result) without resolving the branches/aggregate args.
|
|
740
|
+
export type IsCaseExpr<E extends string> =
|
|
741
|
+
Trim<E> extends `case ${string}`
|
|
742
|
+
? true
|
|
743
|
+
: Trim<E> extends `(${infer Inner})`
|
|
744
|
+
? IsCaseExpr<Inner>
|
|
745
|
+
: false;
|