@kuindji/typed-sql 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -3
- package/dist/columns.d.ts +11 -3
- package/dist/columns.d.ts.map +1 -1
- package/dist/expressions.d.ts +84 -13
- package/dist/expressions.d.ts.map +1 -1
- package/dist/parsing/extract.d.ts +13 -9
- package/dist/parsing/extract.d.ts.map +1 -1
- package/dist/parsing/normalize.d.ts +9 -3
- package/dist/parsing/normalize.d.ts.map +1 -1
- package/dist/parsing/pg-literals.d.ts +10 -2
- package/dist/parsing/pg-literals.d.ts.map +1 -1
- package/dist/parsing/split.d.ts +27 -3
- package/dist/parsing/split.d.ts.map +1 -1
- package/dist/parsing/string-utils.d.ts +2 -4
- package/dist/parsing/string-utils.d.ts.map +1 -1
- package/dist/parsing/tokenize.d.ts +27 -17
- package/dist/parsing/tokenize.d.ts.map +1 -1
- package/dist/partial.d.ts +6 -6
- package/dist/partial.d.ts.map +1 -1
- package/dist/tables.d.ts +58 -13
- package/dist/tables.d.ts.map +1 -1
- package/dist/validation/dispatch.d.ts +7 -5
- package/dist/validation/dispatch.d.ts.map +1 -1
- package/dist/validation/joins.d.ts +3 -3
- package/dist/validation/joins.d.ts.map +1 -1
- package/dist/validation/return-derived.d.ts +8 -4
- package/dist/validation/return-derived.d.ts.map +1 -1
- package/dist/validation/return-derived.js.map +1 -1
- package/dist/validation/return-types.d.ts +1 -1
- package/dist/validation/return-types.d.ts.map +1 -1
- package/dist/validation/validate-columns.d.ts +27 -16
- package/dist/validation/validate-columns.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/columns.ts +168 -32
- package/src/expressions.ts +589 -63
- package/src/parsing/extract.ts +72 -32
- package/src/parsing/normalize.ts +114 -10
- package/src/parsing/pg-literals.ts +32 -10
- package/src/parsing/split.ts +236 -72
- package/src/parsing/string-utils.ts +15 -15
- package/src/parsing/tokenize.ts +224 -146
- package/src/partial.ts +9 -15
- package/src/tables.ts +546 -183
- package/src/validation/dispatch.ts +58 -52
- package/src/validation/joins.ts +15 -19
- package/src/validation/return-derived.ts +60 -4
- package/src/validation/return-types.ts +9 -3
- package/src/validation/validate-columns.ts +161 -67
|
@@ -6,7 +6,7 @@ import type { CteNames, NonCteTables, SingleCteMatch } from "./cte.js";
|
|
|
6
6
|
import type { DatabaseSchema } from "../schema.js";
|
|
7
7
|
import type { DerivedAliasName, DerivedFirstWord, DerivedTableMatch } from "./return-derived.js";
|
|
8
8
|
import type { DistinctOnColsValid, JoinUsingColsValid, WindowFilterColsValid } from "./joins.js";
|
|
9
|
-
import type { ExceedsLengthBudget, ExtractBefore, ExtractLastWhere, HasLineBreaks,
|
|
9
|
+
import type { ExceedsLengthBudget, ExtractBefore, ExtractLastWhere, HasLineBreaks, Trim, ValidationScanView } from "../parsing.js";
|
|
10
10
|
import type { RefScanSegment } from "./return-types.js";
|
|
11
11
|
// Core validation / inference
|
|
12
12
|
|
|
@@ -126,16 +126,13 @@ export type WhereColsValidForUpdate<
|
|
|
126
126
|
TargetKey extends string,
|
|
127
127
|
AliasEntry extends string,
|
|
128
128
|
S extends DatabaseSchema
|
|
129
|
-
> =
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
true
|
|
137
|
-
>
|
|
138
|
-
: true;
|
|
129
|
+
> = And<
|
|
130
|
+
QualifiedColumnRefsValidFor<W, S, TargetKey, AliasEntry, W>,
|
|
131
|
+
UnqualifiedColumnRefsValidFor<W, S, TargetKey, AliasEntry, W, never>,
|
|
132
|
+
true,
|
|
133
|
+
true,
|
|
134
|
+
true
|
|
135
|
+
>;
|
|
139
136
|
|
|
140
137
|
// "Light" validator for high-complexity SELECTs: validate the cheap, bounded
|
|
141
138
|
// parts (every referenced table exists, and the select/returning list resolves)
|
|
@@ -217,21 +214,19 @@ export type LightSelectTablesAndList<N extends string, S extends DatabaseSchema>
|
|
|
217
214
|
export type ValidateSQLNormalizedCore<N extends string, S extends DatabaseSchema> =
|
|
218
215
|
TablesInQuery<N, S> extends infer Tables extends string
|
|
219
216
|
? AliasesInQuery<N, S> extends infer Aliases extends string
|
|
220
|
-
?
|
|
221
|
-
?
|
|
222
|
-
?
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
?
|
|
226
|
-
?
|
|
227
|
-
?
|
|
228
|
-
? true
|
|
229
|
-
: false
|
|
217
|
+
? AllTablesValidFor<NonCteTables<N, S, Tables>, S> extends true
|
|
218
|
+
? AllColumnsValidFor<N, S, Tables, Aliases, RefScanSegment<N>> extends true
|
|
219
|
+
? NoAliasShadowedQualifiers<N, S, Tables, Aliases> extends true
|
|
220
|
+
? OuterScopeUnqualifiedValid<N, S> extends true
|
|
221
|
+
? WindowFilterColsValid<N, S, Tables, Aliases> extends true
|
|
222
|
+
? JoinUsingColsValid<N, S, Tables> extends true
|
|
223
|
+
? DistinctOnColsValid<N, S, Tables, Aliases> extends true
|
|
224
|
+
? true
|
|
230
225
|
: false
|
|
231
226
|
: false
|
|
232
227
|
: false
|
|
233
|
-
: false
|
|
234
228
|
: false
|
|
229
|
+
: false
|
|
235
230
|
: false
|
|
236
231
|
: false
|
|
237
232
|
: false
|
|
@@ -290,41 +285,52 @@ export type HasReturning<N extends string> =
|
|
|
290
285
|
|
|
291
286
|
// Quote-free fast-path: a query with no `'` and no `"` has no place for a
|
|
292
287
|
// ` returning ` to hide, so every occurrence is top-level — a single pattern test
|
|
293
|
-
// is exact and skips the
|
|
294
|
-
//
|
|
295
|
-
//
|
|
296
|
-
export type HasReturningQuoteAware<
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
? HasReturningQuoteAwareWalk<S, InString, InDString, Steps>
|
|
305
|
-
: S extends `${string}"${string}`
|
|
306
|
-
? HasReturningQuoteAwareWalk<S, InString, InDString, Steps>
|
|
307
|
-
: S extends `${string} returning ${string}` ? true : false;
|
|
288
|
+
// is exact and skips the walk (these run on every DML). Only quote-bearing
|
|
289
|
+
// queries pay for the walk below. The fast-path pattern matches the step-cap
|
|
290
|
+
// fallback this walk already uses, so it is consistent with prior behavior.
|
|
291
|
+
export type HasReturningQuoteAware<S extends string> =
|
|
292
|
+
string extends S
|
|
293
|
+
? false
|
|
294
|
+
: S extends `${string}'${string}`
|
|
295
|
+
? HasReturningQuoteAwareWalk<S>
|
|
296
|
+
: S extends `${string}"${string}`
|
|
297
|
+
? HasReturningQuoteAwareWalk<S>
|
|
298
|
+
: S extends `${string} returning ${string}` ? true : false;
|
|
308
299
|
|
|
300
|
+
// Quote-jump, not per-char (the old walk minted the tail string PER CHARACTER on
|
|
301
|
+
// every quote-bearing DML). Find the leftmost ` returning `; if no quote opens
|
|
302
|
+
// before it, it is top-level — answer found. Otherwise jump the leftmost quote
|
|
303
|
+
// span whole (`'…'` or `"…"`, whichever opens first — a quote of the other kind
|
|
304
|
+
// inside the span is data, mirroring the old InString/InDString suppression) and
|
|
305
|
+
// re-test the remainder. O(quote spans) instead of O(chars); an unterminated
|
|
306
|
+
// quote swallows the rest, exactly like the old walk-to-EOF inside a literal.
|
|
309
307
|
type HasReturningQuoteAwareWalk<
|
|
310
308
|
S extends string,
|
|
311
|
-
InString extends boolean = false,
|
|
312
|
-
InDString extends boolean = false,
|
|
313
309
|
Steps extends any[] = []
|
|
314
310
|
> = string extends S
|
|
315
311
|
? false
|
|
316
|
-
: Steps["length"] extends
|
|
312
|
+
: Steps["length"] extends 400
|
|
317
313
|
? S extends `${string} returning ${string}` ? true : false
|
|
318
|
-
:
|
|
319
|
-
?
|
|
320
|
-
?
|
|
314
|
+
: S extends `${infer Before} returning ${string}`
|
|
315
|
+
? Before extends `${string}'${string}` | `${string}"${string}`
|
|
316
|
+
? HrqaQuoteJump<S, Steps>
|
|
317
|
+
: true
|
|
318
|
+
: false;
|
|
319
|
+
|
|
320
|
+
// Leftmost of `'` / `"` (the caller guarantees at least one occurs before the
|
|
321
|
+
// first ` returning `): skip its whole span, resume after the closing quote.
|
|
322
|
+
type HrqaQuoteJump<S extends string, Steps extends any[]> =
|
|
323
|
+
S extends `${infer P}'${infer R}`
|
|
324
|
+
? P extends `${string}"${string}`
|
|
325
|
+
? HrqaDQuoteJump<S, Steps>
|
|
326
|
+
: R extends `${string}'${infer R2}`
|
|
327
|
+
? HasReturningQuoteAwareWalk<R2, [any, ...Steps]>
|
|
321
328
|
: false
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
: false;
|
|
329
|
+
: HrqaDQuoteJump<S, Steps>;
|
|
330
|
+
|
|
331
|
+
type HrqaDQuoteJump<S extends string, Steps extends any[]> =
|
|
332
|
+
S extends `${string}"${infer R}`
|
|
333
|
+
? R extends `${string}"${infer R2}`
|
|
334
|
+
? HasReturningQuoteAwareWalk<R2, [any, ...Steps]>
|
|
335
|
+
: false
|
|
336
|
+
: false;
|
package/src/validation/joins.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// JOIN USING / window-filter / DISTINCT ON column validation.
|
|
2
2
|
import type { AllTrue, And, IsUnion } from "../utils.js";
|
|
3
|
-
import type { CleanIdent, ExtractCallParenBodies, SplitCommaSimple,
|
|
3
|
+
import type { CleanIdent, ExtractCallParenBodies, SplitCommaSimple, Trim } from "../parsing.js";
|
|
4
4
|
import type { ColumnExists, DatabaseSchema } from "../schema.js";
|
|
5
5
|
import type { QualifiedColumnRefsValidFor, UnqualifiedColumnRefsValidFor } from "./validate-columns.js";
|
|
6
6
|
import type { QueryKind } from "./dispatch.js";
|
|
@@ -151,15 +151,13 @@ export type WindowFilterColsValid<
|
|
|
151
151
|
? `${ExtractCallParenBodies<N, " over (">} ${ExtractCallParenBodies<N, " over(">} ${ExtractCallParenBodies<N, " filter (">} ${ExtractCallParenBodies<N, " filter(">} ${ExtractCallParenBodies<N, " within group (">} ${ExtractCallParenBodies<N, " within group(">}` extends infer Seg extends string
|
|
152
152
|
? Trim<Seg> extends ""
|
|
153
153
|
? true
|
|
154
|
-
:
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
>
|
|
162
|
-
: true
|
|
154
|
+
: And<
|
|
155
|
+
QualifiedColumnRefsValidFor<N, S, Tables, Aliases, Seg>,
|
|
156
|
+
UnqualifiedColumnRefsValidFor<N, S, Tables, Aliases, Seg, never>,
|
|
157
|
+
true,
|
|
158
|
+
true,
|
|
159
|
+
true
|
|
160
|
+
>
|
|
163
161
|
: true
|
|
164
162
|
: true;
|
|
165
163
|
|
|
@@ -183,15 +181,13 @@ export type DistinctOnColsValid<
|
|
|
183
181
|
? `${ExtractCallParenBodies<N, " distinct on (">} ${ExtractCallParenBodies<N, " distinct on(">}` extends infer Seg extends string
|
|
184
182
|
? Trim<Seg> extends ""
|
|
185
183
|
? true
|
|
186
|
-
:
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
>
|
|
194
|
-
: true
|
|
184
|
+
: And<
|
|
185
|
+
QualifiedColumnRefsValidFor<N, S, Tables, Aliases, Seg>,
|
|
186
|
+
UnqualifiedColumnRefsValidFor<N, S, Tables, Aliases, Seg, never>,
|
|
187
|
+
true,
|
|
188
|
+
true,
|
|
189
|
+
true
|
|
190
|
+
>
|
|
195
191
|
: true
|
|
196
192
|
: true
|
|
197
193
|
: true;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Derived-table & JOIN LATERAL / joined-derived result inference.
|
|
2
|
-
import type { AliasesInQuery, TablesInQuery } from "../tables.js";
|
|
2
|
+
import type { AliasesInQuery, NullableRelations, TablesInQuery } from "../tables.js";
|
|
3
3
|
import type { ApplyJoinNull, OuterCastTs, RefQualifier } from "../expressions.js";
|
|
4
|
-
import type { CleanIdent, ExtractAliasResult, ExtractFromClause, ExtractSelectList, SplitBalancedParen, SplitCommaSimple, SplitSelectList, Trim, TrimLeft } from "../parsing.js";
|
|
4
|
+
import type { CleanIdent, ExtractAliasResult, ExtractFromClause, ExtractSelectList, SplitBalancedParen, SplitCommaSimple, SplitSelectList, StripSubqueries, Trim, TrimLeft } from "../parsing.js";
|
|
5
5
|
import type { DatabaseSchema } from "../schema.js";
|
|
6
6
|
import type { MergeRow, SelectReturnWith } from "./return-types.js";
|
|
7
7
|
import type { Simplify, UnionToIntersection } from "../utils.js";
|
|
@@ -107,7 +107,30 @@ export type DerivedBodyColType<E extends string, BaseRow> =
|
|
|
107
107
|
export type DerivedTableReturn<N extends string, S extends DatabaseSchema> =
|
|
108
108
|
DerivedTableMatch<N> extends { body: infer Body extends string; alias: infer DAlias extends string; cols: infer Cols extends string[] }
|
|
109
109
|
? DerivedRenamedRow<Body, Cols, S> extends infer SubRow
|
|
110
|
-
?
|
|
110
|
+
? Simplify<MergeRow<
|
|
111
|
+
DerivedJoinedBaseRow<N, S>,
|
|
112
|
+
BuildDerivedReturn<SplitSelectList<ExtractSelectList<N>>, DAlias, SubRow>
|
|
113
|
+
>>
|
|
114
|
+
: {}
|
|
115
|
+
: {};
|
|
116
|
+
|
|
117
|
+
// Tables JOINed onto the leading derived source (`FROM (<subquery>) d JOIN base
|
|
118
|
+
// b ...`) are real outer relations, so projected refs to them (`b.col`,
|
|
119
|
+
// `b.col AS x`) must resolve against the schema — not leak as a dotted-literal
|
|
120
|
+
// key (`{"b.col": unknown}`) or an `unknown` type. Excise the subquery body
|
|
121
|
+
// (`StripSubqueries`) to recover just those outer relations, then resolve the
|
|
122
|
+
// outer SELECT list against them with outer-join nullability. Refs to the derived
|
|
123
|
+
// alias (`d.col`) don't resolve here (it isn't a real table); the derived-alias
|
|
124
|
+
// path supplies those and `MergeRow` lets it win on overlap. Gated on an actual
|
|
125
|
+
// JOIN so a plain `FROM (<subquery>) d` pays nothing extra.
|
|
126
|
+
export type DerivedJoinedBaseRow<N extends string, S extends DatabaseSchema> =
|
|
127
|
+
StripSubqueries<N> extends infer Outer extends string
|
|
128
|
+
? Outer extends `${string} join ${string}`
|
|
129
|
+
? TablesInQuery<Outer, S> extends infer OT extends string
|
|
130
|
+
? AliasesInQuery<Outer, S> extends infer OA extends string
|
|
131
|
+
? SelectReturnWith<ExtractSelectList<N>, OT, OA, S, NullableRelations<N, S>>
|
|
132
|
+
: {}
|
|
133
|
+
: {}
|
|
111
134
|
: {}
|
|
112
135
|
: {};
|
|
113
136
|
|
|
@@ -123,6 +146,37 @@ export type BuildDerivedReturn<
|
|
|
123
146
|
? BuildDerivedReturn<Rest, DAlias, SubRow, MergeRow<Acc, DerivedExprToObject<H, DAlias, SubRow>>, [any, ...Steps]>
|
|
124
147
|
: Simplify<Acc>;
|
|
125
148
|
|
|
149
|
+
// A "bare identifier": a simple unquoted name with no operator/paren/space/dot
|
|
150
|
+
// chars. Lets us tell a *plain* qualified column ref (`ip.entity_id`) apart from
|
|
151
|
+
// an expression that merely contains a dot (`sum(t.x)`, `(array_agg(a.b))[1]`).
|
|
152
|
+
export type IsBareIdent<S extends string> =
|
|
153
|
+
S extends "" ? false
|
|
154
|
+
: S extends `${string}${
|
|
155
|
+
| " " | "(" | ")" | "+" | "-" | "*" | "/" | ":" | "'" | "\"" | "["
|
|
156
|
+
| "]" | "," | "." | "=" | "<" | ">" | "|" | "%" | "!" | "~" | "@"
|
|
157
|
+
}${string}`
|
|
158
|
+
? false
|
|
159
|
+
: true;
|
|
160
|
+
|
|
161
|
+
// The qualifier of a *simple* qualified column ref `Q.col` (both parts bare),
|
|
162
|
+
// else `never` (functions/expressions/quoted refs don't qualify).
|
|
163
|
+
export type SimpleQualRef<E extends string> =
|
|
164
|
+
CleanIdent<E> extends `${infer Q}.${infer Col}`
|
|
165
|
+
? IsBareIdent<Q> extends true
|
|
166
|
+
? IsBareIdent<Col> extends true ? Q : never
|
|
167
|
+
: never
|
|
168
|
+
: never;
|
|
169
|
+
|
|
170
|
+
// True when `RawExpr` is a simple qualified column ref pointing at something
|
|
171
|
+
// OTHER than the derived alias — i.e. a JOINed base table (`ip.entity_id`,
|
|
172
|
+
// `t.name`). `DerivedTableReturn` resolves those against the schema separately,
|
|
173
|
+
// so the derived-alias path skips them rather than emitting a bogus dotted key.
|
|
174
|
+
export type IsForeignSimpleRef<RawExpr extends string, DAlias extends string> =
|
|
175
|
+
SimpleQualRef<RawExpr> extends infer Q extends string
|
|
176
|
+
? [Q] extends [never] ? false
|
|
177
|
+
: Q extends DAlias ? (DAlias extends Q ? false : true) : true
|
|
178
|
+
: false;
|
|
179
|
+
|
|
126
180
|
// `Nullable` carries the outer-join nullable-qualifier set. When the derived
|
|
127
181
|
// table is the nullable side of an outer join (`LEFT JOIN LATERAL (...) d`), its
|
|
128
182
|
// exposed columns must gain `| null` (Postgres: the whole derived row is NULL when
|
|
@@ -130,7 +184,9 @@ export type BuildDerivedReturn<
|
|
|
130
184
|
// don't pass it are unaffected.
|
|
131
185
|
export type DerivedExprToObject<E extends string, DAlias extends string, SubRow, Nullable extends string = never> =
|
|
132
186
|
ExtractAliasResult<E> extends { expr: infer RawExpr extends string; alias: infer OutAlias }
|
|
133
|
-
?
|
|
187
|
+
? IsForeignSimpleRef<RawExpr, DAlias> extends true
|
|
188
|
+
? {}
|
|
189
|
+
: [OutAlias] extends [never]
|
|
134
190
|
? CleanIdent<RawExpr> extends "*"
|
|
135
191
|
? SubRow
|
|
136
192
|
: CleanIdent<RawExpr> extends `${DAlias}.*`
|
|
@@ -259,9 +259,15 @@ export type SelectAliases<
|
|
|
259
259
|
? Acc
|
|
260
260
|
: Exprs extends [infer H extends string, ...infer Rest extends string[]]
|
|
261
261
|
? ExtractAlias<H> extends { alias: infer Alias }
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
262
|
+
// A non-aliased projection yields `alias: never`. Guard it in tuple
|
|
263
|
+
// position: a NAKED `never extends string ?` arm collapses the whole
|
|
264
|
+
// conditional (and with it the entire recursion) to `never`, silently
|
|
265
|
+
// dropping every alias the list DOES define.
|
|
266
|
+
? [Alias] extends [never]
|
|
267
|
+
? SelectAliases<Rest, Acc, [any, ...Steps]>
|
|
268
|
+
: Alias extends string
|
|
269
|
+
? SelectAliases<Rest, Acc | Alias, [any, ...Steps]>
|
|
270
|
+
: SelectAliases<Rest, Acc, [any, ...Steps]>
|
|
265
271
|
: SelectAliases<Rest, Acc, [any, ...Steps]>
|
|
266
272
|
: Acc;
|
|
267
273
|
|