@kuindji/typed-sql 0.3.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 +73 -8
- 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 +3 -1
- 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/validate-columns.d.ts +14 -14
- 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 +550 -57
- package/src/parsing/extract.ts +72 -32
- package/src/parsing/normalize.ts +54 -8
- 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 -214
- package/src/validation/dispatch.ts +58 -52
- package/src/validation/joins.ts +15 -19
- package/src/validation/validate-columns.ts +54 -64
package/src/expressions.ts
CHANGED
|
@@ -3,9 +3,9 @@ import type {
|
|
|
3
3
|
ColumnRef,
|
|
4
4
|
ColumnRefValidLooseWith,
|
|
5
5
|
ParseColumnRef,
|
|
6
|
-
|
|
6
|
+
QualifiedRefScan,
|
|
7
7
|
ResolveTableKey,
|
|
8
|
-
|
|
8
|
+
UnqualifiedRefScan,
|
|
9
9
|
UnqualifiedColumnValid
|
|
10
10
|
} from "./columns.js";
|
|
11
11
|
import type { AliasesInQuery, TablesInQuery } from "./tables.js";
|
|
@@ -23,7 +23,6 @@ import type {
|
|
|
23
23
|
SqlConstantType,
|
|
24
24
|
SplitBalancedParen,
|
|
25
25
|
SplitTopLevel,
|
|
26
|
-
TokenizeLoose,
|
|
27
26
|
Trim
|
|
28
27
|
} from "./parsing.js";
|
|
29
28
|
import type { AllTrue } from "./utils.js";
|
|
@@ -144,7 +143,95 @@ export type ApplyProjectionNull<
|
|
|
144
143
|
? CoalesceAllArgsNullable<SplitTopLevel<Args>, Tables, Aliases, S, Nullable> extends true
|
|
145
144
|
? T | null
|
|
146
145
|
: T
|
|
147
|
-
:
|
|
146
|
+
: [Nullable] extends [never]
|
|
147
|
+
? T
|
|
148
|
+
: [T] extends [never]
|
|
149
|
+
? ApplyJoinNull<T, E, Nullable>
|
|
150
|
+
: [T] extends [number | null]
|
|
151
|
+
? E extends `${string}${"+" | "-" | "*" | "/" | "%"}${string}`
|
|
152
|
+
? ArithRefJoinNullable<E, Tables, Aliases, S, Nullable> extends true
|
|
153
|
+
? T | null
|
|
154
|
+
: ApplyJoinNull<T, E, Nullable>
|
|
155
|
+
: ApplyJoinNull<T, E, Nullable>
|
|
156
|
+
: ApplyJoinNull<T, E, Nullable>;
|
|
157
|
+
|
|
158
|
+
// Outer-join nullability for a TOP-LEVEL ARITHMETIC projection (`A op B`).
|
|
159
|
+
// SQL NULL arithmetic is NULL, so the result is nullable when ANY operand is
|
|
160
|
+
// sourced from the nullable side of an outer join. `RefQualifier` cannot see
|
|
161
|
+
// operand refs (an arithmetic expression is not a plain column ref — or worse,
|
|
162
|
+
// its leftmost dot fakes one: `u.id + o.total` "qualifies" as `u`), so this
|
|
163
|
+
// walks the operands the same way the arithmetic TYPING did: split at the
|
|
164
|
+
// top-level operator and recurse each side. Only consulted when the projection
|
|
165
|
+
// already typed `number`/`number | null` (the arithmetic result types), under
|
|
166
|
+
// a non-empty `Nullable` set, with an operator char present — join-free
|
|
167
|
+
// queries and plain projections pay nothing. A `false` verdict falls back to
|
|
168
|
+
// `ApplyJoinNull`, so a non-arithmetic expression that slips past the op-char
|
|
169
|
+
// gate (e.g. a quoted-punct ref like `"u-1".id`) keeps its plain-ref handling.
|
|
170
|
+
type ArithRefJoinNullable<
|
|
171
|
+
E extends string,
|
|
172
|
+
Tables extends string,
|
|
173
|
+
Aliases extends string,
|
|
174
|
+
S extends DatabaseSchema,
|
|
175
|
+
Nullable extends string,
|
|
176
|
+
Steps extends any[] = []
|
|
177
|
+
> =
|
|
178
|
+
Steps["length"] extends 8
|
|
179
|
+
? false
|
|
180
|
+
: UnwrapRedundantParens<Trim<E>> extends infer SC extends string
|
|
181
|
+
? SC extends `${string}${"+" | "-" | "*" | "/" | "%"}${string}`
|
|
182
|
+
? SplitTopLevelOp<SC> extends infer SR
|
|
183
|
+
? [SR] extends [never]
|
|
184
|
+
? ArithOperandJoinNullable<SC, Tables, Aliases, S, Nullable>
|
|
185
|
+
: SR extends { __op: [infer L extends string, infer Op extends string, infer R extends string] }
|
|
186
|
+
? Op extends "||"
|
|
187
|
+
? false
|
|
188
|
+
: ArithRefJoinNullable<Trim<L>, Tables, Aliases, S, Nullable, [any, ...Steps]> extends true
|
|
189
|
+
? true
|
|
190
|
+
: ArithRefJoinNullable<Trim<R>, Tables, Aliases, S, Nullable, [any, ...Steps]>
|
|
191
|
+
: ArithOperandJoinNullable<SC, Tables, Aliases, S, Nullable>
|
|
192
|
+
: false
|
|
193
|
+
: ArithOperandJoinNullable<SC, Tables, Aliases, S, Nullable>
|
|
194
|
+
: false;
|
|
195
|
+
|
|
196
|
+
// A LEAF arithmetic operand (no top-level operator left). A whole-operand
|
|
197
|
+
// `coalesce(...)` keeps its all-args-nullable semantics (`coalesce(o.x, 0)`
|
|
198
|
+
// stays non-null even on the nullable side). A function-call operand
|
|
199
|
+
// (`sum(o.total)`) is conservatively nullable when any nullable-side
|
|
200
|
+
// qualified ref appears inside it — an all-NULL group aggregates to NULL.
|
|
201
|
+
// A plain ref consults its qualifier; literals and params stay non-null.
|
|
202
|
+
type ArithOperandJoinNullable<
|
|
203
|
+
SC extends string,
|
|
204
|
+
Tables extends string,
|
|
205
|
+
Aliases extends string,
|
|
206
|
+
S extends DatabaseSchema,
|
|
207
|
+
Nullable extends string
|
|
208
|
+
> =
|
|
209
|
+
CleanExpr<StripOuterCast<SC>> extends `coalesce(${infer Args})`
|
|
210
|
+
? CoalesceAllArgsNullable<SplitTopLevel<Args>, Tables, Aliases, S, Nullable>
|
|
211
|
+
: SC extends `${string}(${string}`
|
|
212
|
+
? NullableQualRefIn<SC, Nullable>
|
|
213
|
+
: RefQualifier<SC> extends infer Q extends string
|
|
214
|
+
? [Q] extends [never]
|
|
215
|
+
? false
|
|
216
|
+
: Q extends Nullable
|
|
217
|
+
? true
|
|
218
|
+
: false
|
|
219
|
+
: false;
|
|
220
|
+
|
|
221
|
+
// True when a `<Q>.`-qualified ref appears in `E` for any nullable qualifier
|
|
222
|
+
// `Q` — at the start, or after a boundary char that cannot be part of an
|
|
223
|
+
// identifier (so alias-suffix lookalikes like `po.x` never match `o`).
|
|
224
|
+
type NullableQualRefIn<E extends string, Nullable extends string> =
|
|
225
|
+
true extends (Nullable extends string ? QualRefIn<E, Nullable> : never)
|
|
226
|
+
? true
|
|
227
|
+
: false;
|
|
228
|
+
|
|
229
|
+
type QualRefIn<E extends string, Q extends string> =
|
|
230
|
+
E extends `${Q}.${string}`
|
|
231
|
+
? true
|
|
232
|
+
: E extends `${string}${" " | "(" | "," | "+" | "-" | "*" | "/" | "%"}${Q}.${string}`
|
|
233
|
+
? true
|
|
234
|
+
: false;
|
|
148
235
|
|
|
149
236
|
// True only when every coalesce argument is nullable. An empty/exhausted list is
|
|
150
237
|
// vacuously `true`, but the wrapper above only reaches this for a real coalesce call
|
|
@@ -243,57 +330,452 @@ export type IsBoolExpr<CE extends string> =
|
|
|
243
330
|
: false;
|
|
244
331
|
|
|
245
332
|
// Scans for a comparison operator outside parens and outside `'…'`/`"…"` quotes.
|
|
246
|
-
// `->>`, `#>>`
|
|
247
|
-
//
|
|
248
|
-
|
|
333
|
+
// `->>`, `#>>` etc. are consumed as units so their `>` is not mistaken for a
|
|
334
|
+
// comparison.
|
|
335
|
+
//
|
|
336
|
+
// Struct-jump, not per-char (the old walk minted the tail PER CHARACTER over
|
|
337
|
+
// every compare-bearing expression, including whole casted subquery bodies).
|
|
338
|
+
// Each step advances to the leftmost of `'` `"` `(` `)` (pairwise narrowing);
|
|
339
|
+
// the RUN before it — structural-char-free by construction — is tested at
|
|
340
|
+
// depth 0 with `HtcRunCheck`: a run containing `=` or `!` is a comparison
|
|
341
|
+
// outright (no non-comparison unit contains either), and a run with only
|
|
342
|
+
// `<`/`>` is scanned unit-to-unit (`->`, `->>`, `#>`, `#>>`, `@>`, `<@`,
|
|
343
|
+
// `<<`, `>>` consumed by 1-char context; the old `::` consume was a no-op —
|
|
344
|
+
// `:` never matched the compare set). Quote spans are jumped quote-to-quote
|
|
345
|
+
// (the other quote kind inside a span is data, the old InQ/InDQ suppression);
|
|
346
|
+
// an unterminated quote swallows the rest (old walk-to-EOF → `false`). The
|
|
347
|
+
// cap counts jumps, `false` on overflow as before.
|
|
348
|
+
export type HasTopLevelCompare<S extends string> = HtcJump<S, [], []>;
|
|
349
|
+
|
|
350
|
+
type HtcJump<
|
|
249
351
|
S extends string,
|
|
250
|
-
Depth extends any[]
|
|
251
|
-
Steps extends any[]
|
|
252
|
-
InQ extends boolean = false,
|
|
253
|
-
InDQ extends boolean = false
|
|
352
|
+
Depth extends any[],
|
|
353
|
+
Steps extends any[]
|
|
254
354
|
> = Steps["length"] extends 400
|
|
255
355
|
? false
|
|
256
|
-
: S extends `${infer
|
|
257
|
-
?
|
|
258
|
-
?
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
:
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
356
|
+
: S extends `${infer P}'${infer R}`
|
|
357
|
+
? P extends `${string}"${string}` | `${string}(${string}` | `${string})${string}`
|
|
358
|
+
? HtcJump2<S, Depth, Steps>
|
|
359
|
+
: HtcRunCheck<P, Depth> extends true
|
|
360
|
+
? true
|
|
361
|
+
: R extends `${string}'${infer R2}`
|
|
362
|
+
? HtcJump<R2, Depth, [any, ...Steps]>
|
|
363
|
+
: false
|
|
364
|
+
: HtcJump2<S, Depth, Steps>;
|
|
365
|
+
|
|
366
|
+
type HtcJump2<
|
|
367
|
+
S extends string,
|
|
368
|
+
Depth extends any[],
|
|
369
|
+
Steps extends any[]
|
|
370
|
+
> = S extends `${infer P}"${infer R}`
|
|
371
|
+
? P extends `${string}(${string}` | `${string})${string}`
|
|
372
|
+
? HtcJump3<S, Depth, Steps>
|
|
373
|
+
: HtcRunCheck<P, Depth> extends true
|
|
374
|
+
? true
|
|
375
|
+
: R extends `${string}"${infer R2}`
|
|
376
|
+
? HtcJump<R2, Depth, [any, ...Steps]>
|
|
377
|
+
: false
|
|
378
|
+
: HtcJump3<S, Depth, Steps>;
|
|
379
|
+
|
|
380
|
+
type HtcJump3<
|
|
381
|
+
S extends string,
|
|
382
|
+
Depth extends any[],
|
|
383
|
+
Steps extends any[]
|
|
384
|
+
> = S extends `${infer P}(${infer R}`
|
|
385
|
+
? P extends `${string})${string}`
|
|
386
|
+
? HtcJump4<S, Depth, Steps>
|
|
387
|
+
: HtcRunCheck<P, Depth> extends true
|
|
388
|
+
? true
|
|
389
|
+
: HtcJump<R, [any, ...Depth], [any, ...Steps]>
|
|
390
|
+
: HtcJump4<S, Depth, Steps>;
|
|
391
|
+
|
|
392
|
+
type HtcJump4<
|
|
393
|
+
S extends string,
|
|
394
|
+
Depth extends any[],
|
|
395
|
+
Steps extends any[]
|
|
396
|
+
> = S extends `${infer P})${infer R}`
|
|
397
|
+
? HtcRunCheck<P, Depth> extends true
|
|
398
|
+
? true
|
|
399
|
+
: HtcJump<R, Depth extends [any, ...infer D] ? D : [], [any, ...Steps]>
|
|
400
|
+
: HtcRunCheck<S, Depth>;
|
|
401
|
+
|
|
402
|
+
// A structural-char-free run is only inspected at depth 0. `=`/`!` never occur
|
|
403
|
+
// in a non-comparison unit, so their presence alone is a comparison; `<`/`>`
|
|
404
|
+
// need the unit scan.
|
|
405
|
+
type HtcRunCheck<P extends string, Depth extends any[]> =
|
|
406
|
+
Depth["length"] extends 0
|
|
407
|
+
? P extends `${string}${"=" | "!"}${string}`
|
|
408
|
+
? true
|
|
409
|
+
: P extends `${string}${"<" | ">"}${string}`
|
|
410
|
+
? HtcRunScan<P>
|
|
411
|
+
: false
|
|
412
|
+
: false;
|
|
413
|
+
|
|
414
|
+
// Unit-to-unit scan of a `<`/`>`-bearing run (no `=`/`!`, no structural chars):
|
|
415
|
+
// jump to the leftmost `<` or `>` and judge it by 1-char context — part of
|
|
416
|
+
// `<@`/`<<` (next char) or `->`/`->>`/`#>`/`#>>`/`@>`/`>>` (previous/next
|
|
417
|
+
// char) is consumed as a unit; anything else is a bare comparison.
|
|
418
|
+
type HtcRunScan<R extends string, Steps extends any[] = []> =
|
|
419
|
+
Steps["length"] extends 50
|
|
420
|
+
? false
|
|
421
|
+
: R extends `${infer A}<${infer B}`
|
|
422
|
+
? A extends `${string}>${string}`
|
|
423
|
+
? HtcRunGt<R, Steps>
|
|
424
|
+
: B extends `@${infer B2}`
|
|
425
|
+
? HtcRunScan<B2, [any, ...Steps]>
|
|
426
|
+
: B extends `<${infer B2}`
|
|
427
|
+
? HtcRunScan<B2, [any, ...Steps]>
|
|
428
|
+
: true
|
|
429
|
+
: HtcRunGt<R, Steps>;
|
|
430
|
+
|
|
431
|
+
type HtcRunGt<R extends string, Steps extends any[]> =
|
|
432
|
+
R extends `${infer A}>${infer B}`
|
|
433
|
+
? A extends `${string}${"-" | "#" | "@"}`
|
|
434
|
+
? B extends `>${infer B2}`
|
|
435
|
+
? HtcRunScan<B2, [any, ...Steps]>
|
|
436
|
+
: HtcRunScan<B, [any, ...Steps]>
|
|
437
|
+
: B extends `>${infer B2}`
|
|
438
|
+
? HtcRunScan<B2, [any, ...Steps]>
|
|
439
|
+
: true
|
|
295
440
|
: false;
|
|
296
441
|
|
|
442
|
+
// ---------------------------------------------------------------------------
|
|
443
|
+
// Tier-2 arithmetic: top-level operator split.
|
|
444
|
+
//
|
|
445
|
+
// Finds the LEFTMOST operator in {`||`, `+`, `-`, `*`, `/`, `%`} that sits at
|
|
446
|
+
// paren depth 0 outside `'…'`/`"…"` quotes, and splits the expression around
|
|
447
|
+
// it: `{ __op: [left, op, right] }`. A top-level UNMODELED operator char
|
|
448
|
+
// (single `|` bitwise-or, or the `||/` cube-root prefix) yields the abort
|
|
449
|
+
// marker `{ __ab: true }` (consumer: `unknown`); `never` means no top-level
|
|
450
|
+
// modeled operator exists. `->` / `->>` JSON arrows are consumed as units so
|
|
451
|
+
// their `-` is not mistaken for subtraction.
|
|
452
|
+
//
|
|
453
|
+
// Structure mirrors `HasTopLevelCompare` (struct-jump to the leftmost of
|
|
454
|
+
// `'` `"` `(` `)`, pairwise narrowing) but ACCUMULATES the consumed prefix so
|
|
455
|
+
// the split can be returned, and is worker/driver CHUNKED like
|
|
456
|
+
// `SplitTopLevel` (split.ts): each jump costs ~4 structural helpers plus a
|
|
457
|
+
// ≤6-level run scan at depth 0, so 80 jumps/chunk stays well under TS's
|
|
458
|
+
// ~1000 tail-count budget (round-11 lesson: budget chunks in tail counts,
|
|
459
|
+
// not jumps). Quote spans are jumped quote-to-quote (`''` escapes alternate
|
|
460
|
+
// close/re-open across jumps, so escaped content stays "inside"); an
|
|
461
|
+
// unterminated quote means no trustworthy split -> `never`.
|
|
462
|
+
type SplitTopLevelOp<S extends string> = StoDrive<StoWorker<S>>;
|
|
463
|
+
|
|
464
|
+
// `[R] extends [never]` MUST be guarded first — `never` distributes through
|
|
465
|
+
// the `extends {…}` test and would otherwise fall into the infer arm
|
|
466
|
+
// (round-10 lesson).
|
|
467
|
+
type StoDrive<R> =
|
|
468
|
+
[R] extends [never]
|
|
469
|
+
? never
|
|
470
|
+
: R extends { __c: [infer S2 extends string, infer D extends any[], infer A extends string] }
|
|
471
|
+
? StoDrive<StoWorker<S2, D, A, []>>
|
|
472
|
+
: R;
|
|
473
|
+
|
|
474
|
+
type StoWorker<
|
|
475
|
+
S extends string,
|
|
476
|
+
Depth extends any[] = [],
|
|
477
|
+
Acc extends string = "",
|
|
478
|
+
Steps extends any[] = []
|
|
479
|
+
> = Steps["length"] extends 80
|
|
480
|
+
? { __c: [S, Depth, Acc] }
|
|
481
|
+
: StoJump1<S, Depth, Acc, Steps>;
|
|
482
|
+
|
|
483
|
+
// Run-gate: a structural-char-free run is only operator-scanned at depth 0.
|
|
484
|
+
// At depth > 0 every char is data (`sum(x | y) + 1` must not abort on the
|
|
485
|
+
// nested `|`).
|
|
486
|
+
type StoRunGate<P extends string, Depth extends any[], Acc extends string, Tail extends string> =
|
|
487
|
+
Depth["length"] extends 0
|
|
488
|
+
? StoRunScan<P, Acc, Tail>
|
|
489
|
+
: never;
|
|
490
|
+
|
|
491
|
+
type StoJump1<
|
|
492
|
+
S extends string,
|
|
493
|
+
Depth extends any[],
|
|
494
|
+
Acc extends string,
|
|
495
|
+
Steps extends any[]
|
|
496
|
+
> = S extends `${infer P}'${infer R}`
|
|
497
|
+
? P extends `${string}"${string}` | `${string}(${string}` | `${string})${string}`
|
|
498
|
+
? StoJump2<S, Depth, Acc, Steps>
|
|
499
|
+
: StoRunGate<P, Depth, Acc, `'${R}`> extends infer RR
|
|
500
|
+
? [RR] extends [never]
|
|
501
|
+
? R extends `${infer Span}'${infer R2}`
|
|
502
|
+
? StoWorker<R2, Depth, `${Acc}${P}'${Span}'`, [any, ...Steps]>
|
|
503
|
+
: never
|
|
504
|
+
: RR
|
|
505
|
+
: never
|
|
506
|
+
: StoJump2<S, Depth, Acc, Steps>;
|
|
507
|
+
|
|
508
|
+
type StoJump2<
|
|
509
|
+
S extends string,
|
|
510
|
+
Depth extends any[],
|
|
511
|
+
Acc extends string,
|
|
512
|
+
Steps extends any[]
|
|
513
|
+
> = S extends `${infer P}"${infer R}`
|
|
514
|
+
? P extends `${string}(${string}` | `${string})${string}`
|
|
515
|
+
? StoJump3<S, Depth, Acc, Steps>
|
|
516
|
+
: StoRunGate<P, Depth, Acc, `"${R}`> extends infer RR
|
|
517
|
+
? [RR] extends [never]
|
|
518
|
+
? R extends `${infer Span}"${infer R2}`
|
|
519
|
+
? StoWorker<R2, Depth, `${Acc}${P}"${Span}"`, [any, ...Steps]>
|
|
520
|
+
: never
|
|
521
|
+
: RR
|
|
522
|
+
: never
|
|
523
|
+
: StoJump3<S, Depth, Acc, Steps>;
|
|
524
|
+
|
|
525
|
+
type StoJump3<
|
|
526
|
+
S extends string,
|
|
527
|
+
Depth extends any[],
|
|
528
|
+
Acc extends string,
|
|
529
|
+
Steps extends any[]
|
|
530
|
+
> = S extends `${infer P}(${infer R}`
|
|
531
|
+
? P extends `${string})${string}`
|
|
532
|
+
? StoJump4<S, Depth, Acc, Steps>
|
|
533
|
+
: StoRunGate<P, Depth, Acc, `(${R}`> extends infer RR
|
|
534
|
+
? [RR] extends [never]
|
|
535
|
+
? StoWorker<R, [any, ...Depth], `${Acc}${P}(`, [any, ...Steps]>
|
|
536
|
+
: RR
|
|
537
|
+
: never
|
|
538
|
+
: StoJump4<S, Depth, Acc, Steps>;
|
|
539
|
+
|
|
540
|
+
type StoJump4<
|
|
541
|
+
S extends string,
|
|
542
|
+
Depth extends any[],
|
|
543
|
+
Acc extends string,
|
|
544
|
+
Steps extends any[]
|
|
545
|
+
> = S extends `${infer P})${infer R}`
|
|
546
|
+
? StoRunGate<P, Depth, Acc, `)${R}`> extends infer RR
|
|
547
|
+
? [RR] extends [never]
|
|
548
|
+
// an unmatched `)` at depth 0 stays at depth 0 (pop of empty = empty)
|
|
549
|
+
? StoWorker<R, Depth extends [any, ...infer D] ? D : [], `${Acc}${P})`, [any, ...Steps]>
|
|
550
|
+
: RR
|
|
551
|
+
: never
|
|
552
|
+
: StoRunGate<S, Depth, Acc, "">;
|
|
553
|
+
|
|
554
|
+
// Leftmost modeled operator within a structural-free run `P` (no quotes or
|
|
555
|
+
// parens by construction). Pairwise narrowing, same invariant as
|
|
556
|
+
// `StlStructJump`: each level checks the matched prefix for every LATER
|
|
557
|
+
// class, so the level that fires is genuinely the leftmost. `Tail` is the
|
|
558
|
+
// untouched remainder of the whole expression after the run; the returned
|
|
559
|
+
// `right` re-attaches it.
|
|
560
|
+
type StoRunScan<P extends string, Acc extends string, Tail extends string> =
|
|
561
|
+
P extends `${infer A}+${infer B}`
|
|
562
|
+
? A extends `${string}${"-" | "*" | "/" | "%" | "|"}${string}`
|
|
563
|
+
? StoRunScan2<P, Acc, Tail>
|
|
564
|
+
: { __op: [`${Acc}${A}`, "+", `${B}${Tail}`] }
|
|
565
|
+
: StoRunScan2<P, Acc, Tail>;
|
|
566
|
+
|
|
567
|
+
type StoRunScan2<P extends string, Acc extends string, Tail extends string> =
|
|
568
|
+
P extends `${infer A}-${infer B}`
|
|
569
|
+
? A extends `${string}${"*" | "/" | "%" | "|"}${string}`
|
|
570
|
+
? StoRunScan3<P, Acc, Tail>
|
|
571
|
+
: B extends `>${infer B2}`
|
|
572
|
+
// `->` / `->>` JSON arrow: a unit, not subtraction — keep
|
|
573
|
+
// scanning the rest of the run (a leading `>` from `->>` is
|
|
574
|
+
// not an operator char and is skipped naturally). Non-tail
|
|
575
|
+
// recursion, but bounded by arrows-per-run (tiny in practice).
|
|
576
|
+
? StoRunScan<B2, `${Acc}${A}->`, Tail>
|
|
577
|
+
: { __op: [`${Acc}${A}`, "-", `${B}${Tail}`] }
|
|
578
|
+
: StoRunScan3<P, Acc, Tail>;
|
|
579
|
+
|
|
580
|
+
type StoRunScan3<P extends string, Acc extends string, Tail extends string> =
|
|
581
|
+
P extends `${infer A}*${infer B}`
|
|
582
|
+
? A extends `${string}${"/" | "%" | "|"}${string}`
|
|
583
|
+
? StoRunScan4<P, Acc, Tail>
|
|
584
|
+
: { __op: [`${Acc}${A}`, "*", `${B}${Tail}`] }
|
|
585
|
+
: StoRunScan4<P, Acc, Tail>;
|
|
586
|
+
|
|
587
|
+
type StoRunScan4<P extends string, Acc extends string, Tail extends string> =
|
|
588
|
+
P extends `${infer A}/${infer B}`
|
|
589
|
+
? A extends `${string}${"%" | "|"}${string}`
|
|
590
|
+
? StoRunScan5<P, Acc, Tail>
|
|
591
|
+
: { __op: [`${Acc}${A}`, "/", `${B}${Tail}`] }
|
|
592
|
+
: StoRunScan5<P, Acc, Tail>;
|
|
593
|
+
|
|
594
|
+
type StoRunScan5<P extends string, Acc extends string, Tail extends string> =
|
|
595
|
+
P extends `${infer A}%${infer B}`
|
|
596
|
+
? A extends `${string}|${string}`
|
|
597
|
+
? StoRunScan6<P, Acc, Tail>
|
|
598
|
+
: { __op: [`${Acc}${A}`, "%", `${B}${Tail}`] }
|
|
599
|
+
: StoRunScan6<P, Acc, Tail>;
|
|
600
|
+
|
|
601
|
+
type StoRunScan6<P extends string, Acc extends string, Tail extends string> =
|
|
602
|
+
P extends `${infer A}|${infer B}`
|
|
603
|
+
? B extends `|${infer B2}`
|
|
604
|
+
? B2 extends `/${string}`
|
|
605
|
+
// `||/` cube root — numeric prefix operator, NOT concat
|
|
606
|
+
? { __ab: true }
|
|
607
|
+
: Trim<`${Acc}${A}`> extends ""
|
|
608
|
+
// leading `||` with no left operand — unmodeled prefix op
|
|
609
|
+
? { __ab: true }
|
|
610
|
+
: { __op: [`${Acc}${A}`, "||", `${B2}${Tail}`] }
|
|
611
|
+
// single `|` (bitwise) at top level — unmodeled, conservative stop
|
|
612
|
+
: { __ab: true }
|
|
613
|
+
: never;
|
|
614
|
+
|
|
615
|
+
// `A op B` for op in {+, -, *, /, %} types `number` when BOTH operands type
|
|
616
|
+
// `number` (`| null` propagating from either side — SQL NULL arithmetic is
|
|
617
|
+
// NULL). number op number is numeric in Postgres; the interval/date hazards
|
|
618
|
+
// all require a non-number operand, which the schema types as non-number, so
|
|
619
|
+
// the both-number case is unambiguous and contract-legal. Any other operand
|
|
620
|
+
// type — including `never` — degrades to `unknown`: an operand the core path
|
|
621
|
+
// cannot resolve (a ref qualified by a joined-derived alias, a mis-split
|
|
622
|
+
// like `1e+5` -> `1e`) must NOT reject, or `ExprValid`'s never-gate would
|
|
623
|
+
// flip `ValidateSQL` to `false` on valid SQL; genuinely bogus columns are
|
|
624
|
+
// still rejected by the token-scan validators independently.
|
|
625
|
+
type ArithCombineType<
|
|
626
|
+
L extends string,
|
|
627
|
+
R extends string,
|
|
628
|
+
Tables extends string,
|
|
629
|
+
Aliases extends string,
|
|
630
|
+
S extends DatabaseSchema,
|
|
631
|
+
Steps extends any[]
|
|
632
|
+
> =
|
|
633
|
+
Trim<L> extends ""
|
|
634
|
+
? unknown
|
|
635
|
+
: ArithCombineTypes<ExprType<Trim<L>, Tables, Aliases, S, [any, ...Steps]>, R, Tables, Aliases, S, Steps>;
|
|
636
|
+
|
|
637
|
+
// Same, with the LEFT operand's type already computed (the Func-branch
|
|
638
|
+
// dispatcher gets it from `FunctionReturn` without re-parsing the call).
|
|
639
|
+
type ArithCombineTypes<
|
|
640
|
+
LT,
|
|
641
|
+
R extends string,
|
|
642
|
+
Tables extends string,
|
|
643
|
+
Aliases extends string,
|
|
644
|
+
S extends DatabaseSchema,
|
|
645
|
+
Steps extends any[]
|
|
646
|
+
> =
|
|
647
|
+
Trim<R> extends ""
|
|
648
|
+
? unknown
|
|
649
|
+
: ArithNumClass<LT> extends infer LN
|
|
650
|
+
? LN extends false
|
|
651
|
+
? unknown
|
|
652
|
+
: ArithNumClass<ExprType<Trim<R>, Tables, Aliases, S, [any, ...Steps]>> extends infer RN
|
|
653
|
+
? RN extends false
|
|
654
|
+
? unknown
|
|
655
|
+
: "nullable" extends LN | RN
|
|
656
|
+
? number | null
|
|
657
|
+
: number
|
|
658
|
+
: unknown
|
|
659
|
+
: unknown;
|
|
660
|
+
|
|
661
|
+
// `never` guarded FIRST — `[never]` matches the later arms too. A bare
|
|
662
|
+
// `null` operand classes as nullable (`price + null` -> number | null).
|
|
663
|
+
type ArithNumClass<T> =
|
|
664
|
+
[T] extends [never]
|
|
665
|
+
? false
|
|
666
|
+
: [T] extends [number]
|
|
667
|
+
? "num"
|
|
668
|
+
: [T] extends [number | null]
|
|
669
|
+
? "nullable"
|
|
670
|
+
: false;
|
|
671
|
+
|
|
672
|
+
// Scan-and-combine used where no cheaper dispatch is possible: the
|
|
673
|
+
// fallback-slot path and the op-char-in-Func-prefix path. `NoOp` is the
|
|
674
|
+
// result when the scan finds no top-level modeled operator (today's
|
|
675
|
+
// behavior at the call site); the abort marker is conservative `unknown`.
|
|
676
|
+
type ArithViaScan<
|
|
677
|
+
CE extends string,
|
|
678
|
+
Tables extends string,
|
|
679
|
+
Aliases extends string,
|
|
680
|
+
S extends DatabaseSchema,
|
|
681
|
+
Steps extends any[],
|
|
682
|
+
NoOp
|
|
683
|
+
> =
|
|
684
|
+
SplitTopLevelOp<CE> extends infer SR
|
|
685
|
+
? [SR] extends [never]
|
|
686
|
+
? NoOp
|
|
687
|
+
: SR extends { __op: [infer L extends string, infer Op extends string, infer R extends string] }
|
|
688
|
+
? Op extends "||"
|
|
689
|
+
? string
|
|
690
|
+
: ArithCombineType<L, R, Tables, Aliases, S, Steps>
|
|
691
|
+
: unknown
|
|
692
|
+
: never;
|
|
693
|
+
|
|
694
|
+
// Final-fallback-slot arithmetic (replaces Tier 1's DivByNumericLiteralType,
|
|
695
|
+
// which it subsumes: a numeric-literal divisor is just a `${number}` right
|
|
696
|
+
// operand). Sits after the column-ref branch fails, so common paths pay
|
|
697
|
+
// nothing; the char pre-gate skips the scan for operator-free expressions.
|
|
698
|
+
// `||` is unreachable here (the naive `${string}||${string}` branch runs
|
|
699
|
+
// earlier in the cascade), so the gate set is the five arithmetic chars.
|
|
700
|
+
type TopLevelArithType<
|
|
701
|
+
CE extends string,
|
|
702
|
+
Tables extends string,
|
|
703
|
+
Aliases extends string,
|
|
704
|
+
S extends DatabaseSchema,
|
|
705
|
+
Steps extends any[]
|
|
706
|
+
> =
|
|
707
|
+
CE extends `${string}${"+" | "-" | "*" | "/" | "%"}${string}`
|
|
708
|
+
? ArithViaScan<CE, Tables, Aliases, S, Steps, unknown>
|
|
709
|
+
: unknown;
|
|
710
|
+
|
|
711
|
+
// Func-branch dispatcher. The greedy `${Func}(${Args})` match anchors on the
|
|
712
|
+
// LAST `)`, so `sum(price) / count(id)` lands here with Func="sum",
|
|
713
|
+
// Args="price) / count(id" — a function call is only the WHOLE expression
|
|
714
|
+
// when its first paren group is also its last. Dispatch:
|
|
715
|
+
// - Func clean + no `)` in Args (the overwhelmingly common projection:
|
|
716
|
+
// `count(*)`, `sum(price)`, `upper(name)`, ops hidden inside quoted args)
|
|
717
|
+
// -> FunctionReturn directly, zero new cost.
|
|
718
|
+
// - Func clean + `)` in Args -> `SplitBalancedParen` (already-paid chunked
|
|
719
|
+
// primitive) resolves the first call's true extent WITHOUT a scan:
|
|
720
|
+
// rest "" means the call spans the whole expression (nested calls like
|
|
721
|
+
// `coalesce(min(x), 0)`); an operator-leading rest is arithmetic with the
|
|
722
|
+
// call as left operand; anything else (window `over (...)`, `filter`)
|
|
723
|
+
// keeps today's greedy FunctionReturn.
|
|
724
|
+
// - Operator char in Func (`price + count(id)`, `name || upper(b)`) -> the
|
|
725
|
+
// operator precedes the first paren; full top-level scan.
|
|
726
|
+
type FuncOrArithType<
|
|
727
|
+
CE extends string,
|
|
728
|
+
Func extends string,
|
|
729
|
+
Args extends string,
|
|
730
|
+
Tables extends string,
|
|
731
|
+
Aliases extends string,
|
|
732
|
+
S extends DatabaseSchema,
|
|
733
|
+
Steps extends any[]
|
|
734
|
+
> =
|
|
735
|
+
Func extends `${string}${"+" | "-" | "*" | "/" | "%" | "|"}${string}`
|
|
736
|
+
? ArithViaScan<CE, Tables, Aliases, S, Steps, FunctionReturn<CleanIdent<Func>, Args, Tables, Aliases, S, [any, ...Steps]>>
|
|
737
|
+
: Args extends `${string})${string}`
|
|
738
|
+
? CE extends `${string}(${infer AfterOpen}`
|
|
739
|
+
? SplitBalancedParen<`(${AfterOpen}`> extends { inner: infer Inner extends string; rest: infer Rest extends string }
|
|
740
|
+
? Trim<Rest> extends ""
|
|
741
|
+
? FunctionReturn<CleanIdent<Func>, Inner, Tables, Aliases, S, [any, ...Steps]>
|
|
742
|
+
: FuncRestDispatch<Trim<Rest>, Func, Args, Inner, Tables, Aliases, S, Steps>
|
|
743
|
+
: FunctionReturn<CleanIdent<Func>, Args, Tables, Aliases, S, [any, ...Steps]>
|
|
744
|
+
: FunctionReturn<CleanIdent<Func>, Args, Tables, Aliases, S, [any, ...Steps]>
|
|
745
|
+
: FunctionReturn<CleanIdent<Func>, Args, Tables, Aliases, S, [any, ...Steps]>;
|
|
746
|
+
|
|
747
|
+
// `RestT` (trimmed) is what follows the first balanced call. An arithmetic
|
|
748
|
+
// operator -> the call (typed via FunctionReturn on its TRUE arg list) is
|
|
749
|
+
// the left operand. `||` -> string (guarding the `||/` cube root). A `->`
|
|
750
|
+
// JSON arrow or any other shape (window clauses, …) -> today's greedy path.
|
|
751
|
+
type FuncRestDispatch<
|
|
752
|
+
RestT extends string,
|
|
753
|
+
Func extends string,
|
|
754
|
+
Args extends string,
|
|
755
|
+
Inner extends string,
|
|
756
|
+
Tables extends string,
|
|
757
|
+
Aliases extends string,
|
|
758
|
+
S extends DatabaseSchema,
|
|
759
|
+
Steps extends any[]
|
|
760
|
+
> =
|
|
761
|
+
RestT extends `||${infer R}`
|
|
762
|
+
? R extends `/${string}`
|
|
763
|
+
? unknown
|
|
764
|
+
: string
|
|
765
|
+
: RestT extends `+${infer R}`
|
|
766
|
+
? ArithCombineTypes<FunctionReturn<CleanIdent<Func>, Inner, Tables, Aliases, S, [any, ...Steps]>, R, Tables, Aliases, S, Steps>
|
|
767
|
+
: RestT extends `-${infer R}`
|
|
768
|
+
? R extends `>${string}`
|
|
769
|
+
? FunctionReturn<CleanIdent<Func>, Args, Tables, Aliases, S, [any, ...Steps]>
|
|
770
|
+
: ArithCombineTypes<FunctionReturn<CleanIdent<Func>, Inner, Tables, Aliases, S, [any, ...Steps]>, R, Tables, Aliases, S, Steps>
|
|
771
|
+
: RestT extends `*${infer R}`
|
|
772
|
+
? ArithCombineTypes<FunctionReturn<CleanIdent<Func>, Inner, Tables, Aliases, S, [any, ...Steps]>, R, Tables, Aliases, S, Steps>
|
|
773
|
+
: RestT extends `/${infer R}`
|
|
774
|
+
? ArithCombineTypes<FunctionReturn<CleanIdent<Func>, Inner, Tables, Aliases, S, [any, ...Steps]>, R, Tables, Aliases, S, Steps>
|
|
775
|
+
: RestT extends `%${infer R}`
|
|
776
|
+
? ArithCombineTypes<FunctionReturn<CleanIdent<Func>, Inner, Tables, Aliases, S, [any, ...Steps]>, R, Tables, Aliases, S, Steps>
|
|
777
|
+
: FunctionReturn<CleanIdent<Func>, Args, Tables, Aliases, S, [any, ...Steps]>;
|
|
778
|
+
|
|
297
779
|
// Strip a redundant fully-wrapping paren pair, repeatedly: `((expr))` and
|
|
298
780
|
// `((case ...)::text)` -> the inner expression whose parens wrap the WHOLE
|
|
299
781
|
// thing. Without this an outer operator hidden by a redundant wrap (e.g. the
|
|
@@ -363,9 +845,9 @@ export type ExprType<
|
|
|
363
845
|
? never
|
|
364
846
|
: SqlTypeToTs<CastTypeName>
|
|
365
847
|
: CE extends `${infer Func}(${infer Args})`
|
|
366
|
-
?
|
|
848
|
+
? FuncOrArithType<CE, Func, Args, Tables, Aliases, S, Steps>
|
|
367
849
|
: CE extends `${infer Func} (${infer Args})`
|
|
368
|
-
?
|
|
850
|
+
? FuncOrArithType<CE, Func, Args, Tables, Aliases, S, Steps>
|
|
369
851
|
: CE extends `${string}||${string}`
|
|
370
852
|
? string
|
|
371
853
|
: CE extends `${infer JBase}->>${string}`
|
|
@@ -394,12 +876,12 @@ export type ExprType<
|
|
|
394
876
|
? [Ref] extends [never]
|
|
395
877
|
? IsIdentifier<CE> extends true
|
|
396
878
|
? never
|
|
397
|
-
:
|
|
879
|
+
: TopLevelArithType<CE, Tables, Aliases, S, Steps>
|
|
398
880
|
: Ref extends ColumnRef<infer TableKey extends string, infer Column extends string>
|
|
399
881
|
? ColumnTypeFromTableKey<TableKey, Column, S>
|
|
400
882
|
: IsIdentifier<CE> extends true
|
|
401
883
|
? never
|
|
402
|
-
:
|
|
884
|
+
: TopLevelArithType<CE, Tables, Aliases, S, Steps>
|
|
403
885
|
: unknown
|
|
404
886
|
// A genuine TOP-LEVEL `::T` cast. As with the JSON-text
|
|
405
887
|
// operators, a `->>` / `#>>` to the right of the cast type
|
|
@@ -480,7 +962,18 @@ export type FunctionReturn<
|
|
|
480
962
|
? string
|
|
481
963
|
: Func extends "coalesce"
|
|
482
964
|
? UnionArgTypes<Args, Tables, Aliases, S, Steps>
|
|
483
|
-
|
|
965
|
+
// Postgres EXTRACT always returns a numeric
|
|
966
|
+
// value regardless of field/source, so typing
|
|
967
|
+
// it is unambiguous; it is NULL iff its source
|
|
968
|
+
// is NULL, so propagate the argument's
|
|
969
|
+
// nullability. An unmodeled argument types
|
|
970
|
+
// `unknown` (which may include null) → the
|
|
971
|
+
// conservative answer is `number | null`.
|
|
972
|
+
: Func extends "extract"
|
|
973
|
+
? null extends FirstArgType<Args, Tables, Aliases, S, Steps>
|
|
974
|
+
? number | null
|
|
975
|
+
: number
|
|
976
|
+
: unknown;
|
|
484
977
|
|
|
485
978
|
// Expression validation
|
|
486
979
|
|
|
@@ -608,7 +1101,7 @@ export type ExprQualifiedRefsValid<
|
|
|
608
1101
|
Tables extends string,
|
|
609
1102
|
Aliases extends string,
|
|
610
1103
|
S extends DatabaseSchema
|
|
611
|
-
> =
|
|
1104
|
+
> = QualifiedRefScan<E> extends infer Cols
|
|
612
1105
|
? AllTrue<Cols extends string ? ColumnRefValidLooseWith<Cols, Tables, Aliases, S> : true>
|
|
613
1106
|
: true;
|
|
614
1107
|
|
|
@@ -617,7 +1110,7 @@ export type ExprUnqualifiedRefsValid<
|
|
|
617
1110
|
Tables extends string,
|
|
618
1111
|
Aliases extends string,
|
|
619
1112
|
S extends DatabaseSchema
|
|
620
|
-
> =
|
|
1113
|
+
> = UnqualifiedRefScan<E, S, Tables, Aliases> extends infer Cols
|
|
621
1114
|
? AllTrue<Cols extends string ? UnqualifiedColumnValid<Cols, Tables, Aliases, S> : true>
|
|
622
1115
|
: true;
|
|
623
1116
|
|