@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
package/src/index.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Pure type-level SQL validation and result inference.
|
|
2
|
+
// The parser is intentionally shallow and uses safe fallbacks to avoid TS depth limits.
|
|
3
|
+
|
|
4
|
+
export type { DatabaseSchema } from "./schema.js";
|
|
5
|
+
|
|
6
|
+
import type { DatabaseSchema } from "./schema.js";
|
|
7
|
+
import type { NormalizeQuery } from "./parsing.js";
|
|
8
|
+
import type { DeleteTargetTable, InsertTargetTable, UpdateTargetTable } from "./tables.js";
|
|
9
|
+
import type { ValidateSQLNormalized, GetReturnTypeNormalized, QueryKind } from "./validation.js";
|
|
10
|
+
import type { RowTypeForTable } from "./schema.js";
|
|
11
|
+
|
|
12
|
+
// -----------------------------
|
|
13
|
+
// Public API
|
|
14
|
+
// -----------------------------
|
|
15
|
+
|
|
16
|
+
export type ValidateSQL<Query extends string, Schema extends DatabaseSchema> =
|
|
17
|
+
string extends Query
|
|
18
|
+
? false
|
|
19
|
+
: Query extends any
|
|
20
|
+
// Distribute over a union of query strings (e.g. a column drawn from
|
|
21
|
+
// a literal union) so each branch is validated independently; the
|
|
22
|
+
// result is the union of per-branch booleans.
|
|
23
|
+
? NormalizeQuery<Query> extends infer N extends string
|
|
24
|
+
? ValidateSQLNormalized<N, Schema>
|
|
25
|
+
: false
|
|
26
|
+
: false;
|
|
27
|
+
|
|
28
|
+
export type GetReturnType<Query extends string, Schema extends DatabaseSchema> =
|
|
29
|
+
string extends Query
|
|
30
|
+
? {}
|
|
31
|
+
: NormalizeQuery<Query> extends infer N extends string
|
|
32
|
+
? QueryKind<N> extends "unknown"
|
|
33
|
+
? {}
|
|
34
|
+
: GetReturnTypeNormalized<N, Schema>
|
|
35
|
+
: {};
|
|
36
|
+
|
|
37
|
+
// Compatibility aliases for adapted external tests
|
|
38
|
+
export type QueryResult<Query extends string, Schema extends DatabaseSchema> =
|
|
39
|
+
GetReturnType<Query, Schema>;
|
|
40
|
+
export type ValidateSelectSQL<Query extends string, Schema extends DatabaseSchema> =
|
|
41
|
+
ValidateSQL<Query, Schema>;
|
|
42
|
+
export type ValidateInsertSQL<Query extends string, Schema extends DatabaseSchema> =
|
|
43
|
+
ValidateSQL<Query, Schema>;
|
|
44
|
+
export type ValidateUpdateSQL<Query extends string, Schema extends DatabaseSchema> =
|
|
45
|
+
ValidateSQL<Query, Schema>;
|
|
46
|
+
export type ValidateDeleteSQL<Query extends string, Schema extends DatabaseSchema> =
|
|
47
|
+
ValidateSQL<Query, Schema>;
|
|
48
|
+
|
|
49
|
+
export type IsValidInsert<Query extends string, Schema extends DatabaseSchema> =
|
|
50
|
+
ValidateSQL<Query, Schema>;
|
|
51
|
+
export type IsValidUpdate<Query extends string, Schema extends DatabaseSchema> =
|
|
52
|
+
ValidateSQL<Query, Schema>;
|
|
53
|
+
export type IsValidDelete<Query extends string, Schema extends DatabaseSchema> =
|
|
54
|
+
ValidateSQL<Query, Schema>;
|
|
55
|
+
|
|
56
|
+
export type GetInsertTableColumns<Query extends string, Schema extends DatabaseSchema> =
|
|
57
|
+
InsertTargetTable<NormalizeQuery<Query>, Schema> extends infer TableKey extends string
|
|
58
|
+
? RowTypeForTable<TableKey, Schema>
|
|
59
|
+
: never;
|
|
60
|
+
export type GetUpdateTableColumns<Query extends string, Schema extends DatabaseSchema> =
|
|
61
|
+
UpdateTargetTable<NormalizeQuery<Query>, Schema> extends infer TableKey extends string
|
|
62
|
+
? RowTypeForTable<TableKey, Schema>
|
|
63
|
+
: never;
|
|
64
|
+
export type GetDeleteTableColumns<Query extends string, Schema extends DatabaseSchema> =
|
|
65
|
+
DeleteTargetTable<NormalizeQuery<Query>, Schema> extends infer TableKey extends string
|
|
66
|
+
? RowTypeForTable<TableKey, Schema>
|
|
67
|
+
: never;
|
|
68
|
+
|
|
69
|
+
// Partial (fragment) validation entry points — for the query builder.
|
|
70
|
+
export type {
|
|
71
|
+
ValidateFromPart,
|
|
72
|
+
ValidateJoinPart,
|
|
73
|
+
ValidateSelectPart,
|
|
74
|
+
ValidateWherePart,
|
|
75
|
+
ValidateHavingPart,
|
|
76
|
+
ValidateGroupByPart,
|
|
77
|
+
ValidateOrderByPart
|
|
78
|
+
} from "./partial.js";
|
|
79
|
+
|
|
80
|
+
// Runtime query builder (values + kept types).
|
|
81
|
+
export * from "./builder/index.js";
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
// SELECT/RETURNING/clause + column-list extraction.
|
|
2
|
+
import type { CleanIdent, FilterEmpty, HasSpecial, MapClean, Split, Trim, TrimLeft } from "./string-utils.js";
|
|
3
|
+
import type { ExtractBefore, ExtractBeforeFromTopLevel, SplitCommaSimple, SplitTopLevel, StripDistinct } from "./split.js";
|
|
4
|
+
// Select / returning list parsing
|
|
5
|
+
|
|
6
|
+
export type ExtractSelectList<N extends string> =
|
|
7
|
+
N extends `${infer _}select ${infer After}`
|
|
8
|
+
? StripDistinct<ExtractBeforeFromTopLevel<After>>
|
|
9
|
+
: N extends `${infer _}with ${string} select ${infer After}`
|
|
10
|
+
? StripDistinct<ExtractBeforeFromTopLevel<After>>
|
|
11
|
+
: "";
|
|
12
|
+
|
|
13
|
+
// The projection list after the REAL ` returning ` clause. A ` returning `
|
|
14
|
+
// substring can also appear inside a string literal — either assigned earlier in
|
|
15
|
+
// the statement (`SET title = ' returning x'`, round-12 R1) OR returned by the
|
|
16
|
+
// clause itself (`RETURNING ' returning bogus_col' AS marker`, round-13 R1). The
|
|
17
|
+
// real clause is the FIRST ` returning ` at TOP LEVEL (outside single-quoted
|
|
18
|
+
// literals / double-quoted identifiers); a quote-aware char-walk skips any
|
|
19
|
+
// ` returning ` sitting inside quotes. Neither "first raw" nor "last raw" is
|
|
20
|
+
// correct in general — the literal can sit on either side of the real clause.
|
|
21
|
+
// Step-bounded (mirrors `HasReturningQuoteAware`) so it never blows the budget.
|
|
22
|
+
export type ExtractReturningList<N extends string> =
|
|
23
|
+
FirstTopLevelReturningTail<N>;
|
|
24
|
+
|
|
25
|
+
export type FirstTopLevelReturningTail<
|
|
26
|
+
S extends string,
|
|
27
|
+
InString extends boolean = false,
|
|
28
|
+
InDString extends boolean = false,
|
|
29
|
+
Steps extends any[] = []
|
|
30
|
+
> = string extends S
|
|
31
|
+
? ""
|
|
32
|
+
: Steps["length"] extends 1200
|
|
33
|
+
? S extends `${string} returning ${infer After}` ? After : ""
|
|
34
|
+
: InString extends true
|
|
35
|
+
? S extends `${infer C}${infer Rest}`
|
|
36
|
+
? FirstTopLevelReturningTail<Rest, C extends "'" ? false : true, InDString, [any, ...Steps]>
|
|
37
|
+
: ""
|
|
38
|
+
: InDString extends true
|
|
39
|
+
? S extends `${infer C}${infer Rest}`
|
|
40
|
+
? FirstTopLevelReturningTail<Rest, InString, C extends `"` ? false : true, [any, ...Steps]>
|
|
41
|
+
: ""
|
|
42
|
+
: S extends ` returning ${infer After}`
|
|
43
|
+
? After
|
|
44
|
+
: S extends `${infer C}${infer Rest}`
|
|
45
|
+
? FirstTopLevelReturningTail<Rest, C extends "'" ? true : false, C extends `"` ? true : false, [any, ...Steps]>
|
|
46
|
+
: "";
|
|
47
|
+
|
|
48
|
+
// Given a string whose first non-skipped char is `(`, consume the first
|
|
49
|
+
// balanced parenthesised group (quote-aware) and return its inner content plus
|
|
50
|
+
// whatever follows the matching `)`. Naive template matching can't do this
|
|
51
|
+
// because `${infer Body})` is lazy and stops at the first `)` (e.g. inside
|
|
52
|
+
// `count(*)`).
|
|
53
|
+
//
|
|
54
|
+
// A single char-walk can only run ~1000 iterations before TS aborts with TS2589;
|
|
55
|
+
// a fixed cap (the previous 400) silently truncates long CTE/subquery bodies and
|
|
56
|
+
// derails the caller (e.g. multi-CTE OUTER-query extraction reads garbage). So
|
|
57
|
+
// the walk is split into a bounded worker that YIELDS its state (`{ __c: [...] }`)
|
|
58
|
+
// every CHUNK steps and a driver that re-invokes it with a fresh step counter —
|
|
59
|
+
// resetting TS's per-chain tail-recursion count, so arbitrarily long groups split
|
|
60
|
+
// losslessly. Mirrors the `SplitTopLevel` worker/driver above.
|
|
61
|
+
type SplitBalancedParenWorker<
|
|
62
|
+
S extends string,
|
|
63
|
+
Depth extends any[] = [],
|
|
64
|
+
Acc extends string = "",
|
|
65
|
+
InString extends boolean = false,
|
|
66
|
+
Steps extends any[] = []
|
|
67
|
+
> = Steps["length"] extends 350
|
|
68
|
+
? { __c: [S, Depth, Acc, InString] }
|
|
69
|
+
: S extends `${infer C}${infer Rest}`
|
|
70
|
+
? C extends "'"
|
|
71
|
+
? SplitBalancedParenWorker<Rest, Depth, `${Acc}${C}`, InString extends true ? false : true, [any, ...Steps]>
|
|
72
|
+
: InString extends true
|
|
73
|
+
? SplitBalancedParenWorker<Rest, Depth, `${Acc}${C}`, InString, [any, ...Steps]>
|
|
74
|
+
: C extends "("
|
|
75
|
+
? Depth["length"] extends 0
|
|
76
|
+
? SplitBalancedParenWorker<Rest, [any], Acc, InString, [any, ...Steps]>
|
|
77
|
+
: SplitBalancedParenWorker<Rest, [any, ...Depth], `${Acc}${C}`, InString, [any, ...Steps]>
|
|
78
|
+
: C extends ")"
|
|
79
|
+
? Depth extends [any, ...infer D extends any[]]
|
|
80
|
+
? D["length"] extends 0
|
|
81
|
+
? { inner: Acc; rest: Rest }
|
|
82
|
+
: SplitBalancedParenWorker<Rest, D, `${Acc}${C}`, InString, [any, ...Steps]>
|
|
83
|
+
: { inner: Acc; rest: Rest }
|
|
84
|
+
: SplitBalancedParenWorker<Rest, Depth, `${Acc}${C}`, InString, [any, ...Steps]>
|
|
85
|
+
: { inner: Acc; rest: "" };
|
|
86
|
+
|
|
87
|
+
export type SplitBalancedParen<S extends string> =
|
|
88
|
+
SplitBalancedParenDrive<SplitBalancedParenWorker<S>>;
|
|
89
|
+
|
|
90
|
+
type SplitBalancedParenDrive<R> =
|
|
91
|
+
R extends { __c: [infer S extends string, infer Depth extends any[], infer Acc extends string, infer InString extends boolean] }
|
|
92
|
+
? SplitBalancedParenDrive<SplitBalancedParenWorker<S, Depth, Acc, InString, []>>
|
|
93
|
+
: R;
|
|
94
|
+
|
|
95
|
+
// Remove every SUBQUERY parenthesised group (a balanced `(...)` whose body is a
|
|
96
|
+
// `select ...`) from a query, leaving a single space in its place. Function-call
|
|
97
|
+
// and grouping parens (`coalesce(...)`, `sum(...)`, `(a + b)`) are kept verbatim
|
|
98
|
+
// so a function name never degrades into a dangling bare identifier. Used by
|
|
99
|
+
// scope validation to recover the OUTER relations and refs of a query with its
|
|
100
|
+
// subquery bodies excised. Bounded against runaway (≤30 groups); on overflow the
|
|
101
|
+
// remainder is appended as-is.
|
|
102
|
+
export type StripSubqueries<
|
|
103
|
+
S extends string,
|
|
104
|
+
Acc extends string = "",
|
|
105
|
+
Steps extends any[] = []
|
|
106
|
+
> = Steps["length"] extends 30
|
|
107
|
+
? `${Acc}${S}`
|
|
108
|
+
: S extends `${infer Before}(${infer AfterOpen}`
|
|
109
|
+
? SplitBalancedParen<`(${AfterOpen}`> extends { inner: infer Inner extends string; rest: infer Rest extends string }
|
|
110
|
+
? Trim<Inner> extends `select ${string}`
|
|
111
|
+
? StripSubqueries<Rest, `${Acc}${Before} `, [any, ...Steps]>
|
|
112
|
+
: StripSubqueries<Rest, `${Acc}${Before}(${Inner})`, [any, ...Steps]>
|
|
113
|
+
: `${Acc}${S}`
|
|
114
|
+
: `${Acc}${S}`;
|
|
115
|
+
|
|
116
|
+
// Collect the inner contents of every `<marker>(...)` group (quote/paren-aware),
|
|
117
|
+
// space-joined. Used to surface columns sitting inside `over (...)` / `filter
|
|
118
|
+
// (...)` clauses, which live in the SELECT list (before the top-level FROM) and
|
|
119
|
+
// would otherwise escape column validation entirely. Bounded against runaway.
|
|
120
|
+
export type ExtractCallParenBodies<
|
|
121
|
+
S extends string,
|
|
122
|
+
Marker extends string,
|
|
123
|
+
Acc extends string = "",
|
|
124
|
+
Steps extends any[] = []
|
|
125
|
+
> = Steps["length"] extends 12
|
|
126
|
+
? Acc
|
|
127
|
+
: S extends `${infer _Head}${Marker}${infer AfterOpen}`
|
|
128
|
+
? SplitBalancedParen<`(${AfterOpen}`> extends { inner: infer Inner extends string; rest: infer Rest extends string }
|
|
129
|
+
? ExtractCallParenBodies<Rest, Marker, `${Acc} ${Inner}`, [any, ...Steps]>
|
|
130
|
+
: Acc
|
|
131
|
+
: Acc;
|
|
132
|
+
|
|
133
|
+
// The predicate after the LAST ` where ` (drop any trailing RETURNING). In an
|
|
134
|
+
// UPDATE the giant SET expression — including any subquery WHEREs — comes BEFORE
|
|
135
|
+
// the statement's own WHERE, so the text after the last ` where ` is the
|
|
136
|
+
// top-level predicate. Implemented by discarding each `${head} where ` prefix
|
|
137
|
+
// (no string rebuild, unlike SplitLast — that concatenation is what blew up
|
|
138
|
+
// TS2589/memory on the largest correlated updates). Capped so a pathological
|
|
139
|
+
// number of nested WHEREs can't run away; on bail the caller's subquery guard
|
|
140
|
+
// handles the (paren-bearing) remainder.
|
|
141
|
+
export type ExtractLastWhere<N extends string> =
|
|
142
|
+
ExtractBefore<Trim<LastWhereTail<N>>, " returning ">;
|
|
143
|
+
|
|
144
|
+
type LastWhereTail<S extends string, Steps extends any[] = []> =
|
|
145
|
+
Steps["length"] extends 16
|
|
146
|
+
? S
|
|
147
|
+
: S extends `${infer _Head} where ${infer Rest}`
|
|
148
|
+
? LastWhereTail<Rest, [any, ...Steps]>
|
|
149
|
+
: S;
|
|
150
|
+
|
|
151
|
+
// Everything after the top-level FROM (paren/quote-aware): the FROM clause and
|
|
152
|
+
// any trailing WHERE/GROUP/ORDER/etc. Used to detect derived tables.
|
|
153
|
+
export type ExtractFromClause<N extends string> =
|
|
154
|
+
N extends `${infer _}select ${infer After}`
|
|
155
|
+
? ExtractBeforeFromTopLevel<After> extends infer SL extends string
|
|
156
|
+
? After extends `${SL} from ${infer FromRest}`
|
|
157
|
+
? FromRest
|
|
158
|
+
: ""
|
|
159
|
+
: ""
|
|
160
|
+
: "";
|
|
161
|
+
|
|
162
|
+
// Select list helpers
|
|
163
|
+
|
|
164
|
+
export type SplitSelectList<S extends string> =
|
|
165
|
+
S extends "" ? [] : SplitTopLevel<S>;
|
|
166
|
+
|
|
167
|
+
// Insert / update parsing
|
|
168
|
+
|
|
169
|
+
export type ExtractInsertColumns<N extends string> =
|
|
170
|
+
N extends `${infer _}insert into ${infer Rest}`
|
|
171
|
+
? Rest extends `${infer _Table}(${infer Cols})${infer _}`
|
|
172
|
+
? SplitCommaSimple<Cols>
|
|
173
|
+
: Rest extends `${infer _Table} (${infer Cols})${infer _}`
|
|
174
|
+
? SplitCommaSimple<Cols>
|
|
175
|
+
: []
|
|
176
|
+
: [];
|
|
177
|
+
|
|
178
|
+
export type ExtractConflictColumns<N extends string> =
|
|
179
|
+
N extends `${string} on conflict ${infer Rest}`
|
|
180
|
+
? Rest extends `(${infer Cols})${string}`
|
|
181
|
+
? SplitCommaSimple<Cols>
|
|
182
|
+
: Rest extends ` (${infer Cols})${string}`
|
|
183
|
+
? SplitCommaSimple<Cols>
|
|
184
|
+
: []
|
|
185
|
+
: [];
|
|
186
|
+
|
|
187
|
+
export type ExtractUpdateSetColumns<N extends string> =
|
|
188
|
+
N extends `${infer _} set ${infer Rest}`
|
|
189
|
+
? ExtractBefore<Rest, " where "> extends infer Block1 extends string
|
|
190
|
+
? ExtractBefore<Block1, " returning "> extends infer Block2 extends string
|
|
191
|
+
? SplitAssignments<Block2>
|
|
192
|
+
: []
|
|
193
|
+
: []
|
|
194
|
+
: [];
|
|
195
|
+
|
|
196
|
+
export type ExtractConflictUpdateSetColumns<N extends string> =
|
|
197
|
+
N extends `${string} do update set ${infer Rest}`
|
|
198
|
+
? ExtractBefore<Rest, " where "> extends infer Block1 extends string
|
|
199
|
+
? ExtractBefore<Block1, " returning "> extends infer Block2 extends string
|
|
200
|
+
? SplitAssignments<Block2>
|
|
201
|
+
: []
|
|
202
|
+
: []
|
|
203
|
+
: [];
|
|
204
|
+
|
|
205
|
+
// The RHS of a `DO UPDATE SET col = excluded.<x>` assignment references the
|
|
206
|
+
// `excluded` pseudo-row, which mirrors the INSERT target table's columns. Collect
|
|
207
|
+
// the referenced `<x>` names from each simple `left = excluded.<x>` assignment so
|
|
208
|
+
// they can be existence-checked against the target table. Only the direct
|
|
209
|
+
// `excluded.<col>` form is captured — a column wrapped in a larger expression
|
|
210
|
+
// (`coalesce(excluded.x, ...)`) is left to the lenient path rather than risk a
|
|
211
|
+
// false reject.
|
|
212
|
+
export type ExtractConflictUpdateExcludedCols<N extends string> =
|
|
213
|
+
N extends `${string} do update set ${infer Rest}`
|
|
214
|
+
? ExtractBefore<Rest, " where "> extends infer Block1 extends string
|
|
215
|
+
? ExtractBefore<Block1, " returning "> extends infer Block2 extends string
|
|
216
|
+
? MapExcludedRHS<Split<Block2, ",">>
|
|
217
|
+
: []
|
|
218
|
+
: []
|
|
219
|
+
: [];
|
|
220
|
+
|
|
221
|
+
export type MapExcludedRHS<Parts extends string[], Acc extends string[] = []> =
|
|
222
|
+
Parts extends [infer P extends string, ...infer Rest extends string[]]
|
|
223
|
+
? P extends `${string}=${infer Right}`
|
|
224
|
+
? CleanIdent<Right> extends `excluded.${infer Col}`
|
|
225
|
+
? MapExcludedRHS<Rest, [...Acc, Col]>
|
|
226
|
+
: MapExcludedRHS<Rest, Acc>
|
|
227
|
+
: MapExcludedRHS<Rest, Acc>
|
|
228
|
+
: Acc;
|
|
229
|
+
|
|
230
|
+
// Update set list parsing
|
|
231
|
+
|
|
232
|
+
export type SplitAssignments<S extends string> =
|
|
233
|
+
TrimLeft<S> extends `(${string}`
|
|
234
|
+
// Row-assignment form `SET (a, b) = (v1, v2)`: the targets are the
|
|
235
|
+
// parenthesised column list, not a single `left = right` assignment.
|
|
236
|
+
// The naive comma split would break on the commas inside the tuples, so
|
|
237
|
+
// pull the first balanced `(...)` group and split its inner column list.
|
|
238
|
+
? ExtractRowAssignTargets<TrimLeft<S>>
|
|
239
|
+
: Split<S, ","> extends infer Parts extends string[]
|
|
240
|
+
? MapLeftSide<Parts>
|
|
241
|
+
: [];
|
|
242
|
+
|
|
243
|
+
export type ExtractRowAssignTargets<S extends string> =
|
|
244
|
+
SplitBalancedParen<S> extends { inner: infer Cols extends string; rest: infer _Rest extends string }
|
|
245
|
+
? FilterEmpty<MapClean<SplitCommaSimple<Cols>>>
|
|
246
|
+
: [];
|
|
247
|
+
|
|
248
|
+
export type MapLeftSide<Parts extends string[], Acc extends string[] = []> =
|
|
249
|
+
Parts extends [infer P extends string, ...infer Rest extends string[]]
|
|
250
|
+
? P extends `${infer Left}=${string}`
|
|
251
|
+
? Trim<Left> extends infer L extends string
|
|
252
|
+
? L extends ""
|
|
253
|
+
? MapLeftSide<Rest, Acc>
|
|
254
|
+
: HasSpecial<L> extends true
|
|
255
|
+
? MapLeftSide<Rest, Acc>
|
|
256
|
+
: MapLeftSide<Rest, [...Acc, L]>
|
|
257
|
+
: MapLeftSide<Rest, Acc>
|
|
258
|
+
: MapLeftSide<Rest, Acc>
|
|
259
|
+
: Acc;
|
|
260
|
+
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
// NormalizeQuery pipeline + quote-aware lowercasing.
|
|
2
|
+
import type { CollapseSpaces, Trim } from "./string-utils.js";
|
|
3
|
+
import type { NeutralizePgLiterals, RewriteExtractCall, StripComments } from "./pg-literals.js";
|
|
4
|
+
|
|
5
|
+
// The lowercaser walks char-by-char under a step cap (a proxy for TS's
|
|
6
|
+
// tail-recursion limit). Report-scale queries carry hundreds of indentation
|
|
7
|
+
// chars that exhaust that budget BEFORE the lowercaser reaches the tail of the
|
|
8
|
+
// SELECT list, forcing a blanket quote-UNAWARE `Lowercase<S>` bail that
|
|
9
|
+
// corrupts the case of single-quoted literals and double-quoted output aliases
|
|
10
|
+
// past the cap (e.g. `'GBP'`→`'gbp'`, `"currencyCount"`→key `currencycount`).
|
|
11
|
+
// Collapsing whitespace FIRST shrinks the input so the whole query stays under
|
|
12
|
+
// the cap (raising the cap instead blows TS2589 near the ~1000 recursion
|
|
13
|
+
// limit). The OUTER ReplaceWhitespace still runs on the now-lowercased string,
|
|
14
|
+
// so its `update`-clause detection is unchanged. The redundant outer
|
|
15
|
+
// ReplaceWhitespace/CollapseSpaces are cheap no-ops once whitespace is already
|
|
16
|
+
// normalized.
|
|
17
|
+
// The lowercaser preserves every character except case — it never introduces
|
|
18
|
+
// `\n\t\r` or extra spaces — so once the INNER `CollapseSpaces<ReplaceWhitespace>`
|
|
19
|
+
// has normalized whitespace, an OUTER pass is a no-op: `ReplaceWhitespace` sees no
|
|
20
|
+
// line breaks (cheap `HasLineBreaks` guard returns `S` without walking) and
|
|
21
|
+
// `CollapseSpaces` would re-walk an already single-spaced string. The outer
|
|
22
|
+
// `CollapseSpaces` is an 800-step char-walk that ran on every query for nothing, so
|
|
23
|
+
// it's dropped. The chunked `LowercaseOutsideQuotes` already handles arbitrary
|
|
24
|
+
// length, so the "collapse first to fit the lowercaser under its cap" rationale no
|
|
25
|
+
// longer requires a second collapse afterwards.
|
|
26
|
+
export type NormalizeQuery<S extends string> =
|
|
27
|
+
RewriteExtractCall<Trim<RemoveTrailingSemicolon<LowercaseOutsideQuotes<CollapseSpaces<ReplaceWhitespace<StripComments<NeutralizePgLiterals<S>>>>>>>>;
|
|
28
|
+
|
|
29
|
+
// Quote-aware lowercasing: SQL keywords/identifiers are case-insensitive, but
|
|
30
|
+
// single-quoted string literals and double-quoted identifiers keep their exact
|
|
31
|
+
// case — case-sensitive QUOTED OUTPUT ALIASES become projected row KEYS that MUST
|
|
32
|
+
// keep their case (e.g. `"linkHash"` → key `linkHash`, not `linkhash`).
|
|
33
|
+
//
|
|
34
|
+
// A single tail-recursive char-walk caps out at TS's ~1000-iteration limit, so it
|
|
35
|
+
// cannot lossly process the 1000+-char SELECT lists real reporting queries reach.
|
|
36
|
+
// The worker runs a bounded chunk and then YIELDS its state (`{ __c: [...] }`); the
|
|
37
|
+
// driver re-invokes it on the remainder with a fresh step counter, resetting TS's
|
|
38
|
+
// internal tail-recursion count per chunk. This processes arbitrarily long queries
|
|
39
|
+
// WITHOUT the old force-lowercase-the-tail bail that corrupted late aliases.
|
|
40
|
+
export type LowercaseOutsideQuotes<S extends string> =
|
|
41
|
+
string extends S
|
|
42
|
+
? string
|
|
43
|
+
: LowercaseOutsideQuotesDrive<LowercaseOutsideQuotesWorker<S, false, false, "", []>>;
|
|
44
|
+
|
|
45
|
+
type LowercaseOutsideQuotesDrive<R> =
|
|
46
|
+
R extends { __c: [infer S extends string, infer Q1 extends boolean, infer Q2 extends boolean, infer Acc extends string] }
|
|
47
|
+
? LowercaseOutsideQuotesDrive<LowercaseOutsideQuotesWorker<S, Q1, Q2, Acc, []>>
|
|
48
|
+
: R;
|
|
49
|
+
|
|
50
|
+
type LowercaseOutsideQuotesWorker<
|
|
51
|
+
S extends string,
|
|
52
|
+
InSingleQuote extends boolean,
|
|
53
|
+
InDoubleQuote extends boolean,
|
|
54
|
+
Acc extends string,
|
|
55
|
+
Steps extends any[]
|
|
56
|
+
> = Steps["length"] extends 450
|
|
57
|
+
? { __c: [S, InSingleQuote, InDoubleQuote, Acc] }
|
|
58
|
+
: S extends `${infer C}${infer Rest}`
|
|
59
|
+
? C extends "'"
|
|
60
|
+
? InDoubleQuote extends true
|
|
61
|
+
? LowercaseOutsideQuotesWorker<Rest, InSingleQuote, InDoubleQuote, `${Acc}${C}`, [any, ...Steps]>
|
|
62
|
+
: InSingleQuote extends true
|
|
63
|
+
? LowercaseOutsideQuotesWorker<Rest, false, InDoubleQuote, `${Acc}${C}`, [any, ...Steps]>
|
|
64
|
+
: LowercaseOutsideQuotesWorker<Rest, true, InDoubleQuote, `${Acc}${C}`, [any, ...Steps]>
|
|
65
|
+
: C extends `"`
|
|
66
|
+
? InSingleQuote extends true
|
|
67
|
+
? LowercaseOutsideQuotesWorker<Rest, InSingleQuote, InDoubleQuote, `${Acc}${C}`, [any, ...Steps]>
|
|
68
|
+
: InDoubleQuote extends true
|
|
69
|
+
? LowercaseOutsideQuotesWorker<Rest, InSingleQuote, false, `${Acc}${C}`, [any, ...Steps]>
|
|
70
|
+
: LowercaseOutsideQuotesWorker<Rest, InSingleQuote, true, `${Acc}${C}`, [any, ...Steps]>
|
|
71
|
+
: InSingleQuote extends true
|
|
72
|
+
? LowercaseOutsideQuotesWorker<Rest, InSingleQuote, InDoubleQuote, `${Acc}${C}`, [any, ...Steps]>
|
|
73
|
+
: InDoubleQuote extends true
|
|
74
|
+
? LowercaseOutsideQuotesWorker<Rest, InSingleQuote, InDoubleQuote, `${Acc}${C}`, [any, ...Steps]>
|
|
75
|
+
: LowercaseOutsideQuotesWorker<Rest, InSingleQuote, InDoubleQuote, `${Acc}${Lowercase<C>}`, [any, ...Steps]>
|
|
76
|
+
: Acc;
|
|
77
|
+
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// Param-name-preserving lowercaser (write/raw builder path only).
|
|
80
|
+
//
|
|
81
|
+
// Identical to LowercaseOutsideQuotes, but the identifier of a `:name` named
|
|
82
|
+
// parameter keeps its EXACT case. Named params become object keys in the
|
|
83
|
+
// builder's `withParams({ … })`, so folding `:teamId`→`:teamid` produced keys
|
|
84
|
+
// the caller could not write (F1). Quoted literals/identifiers are preserved as
|
|
85
|
+
// before; a `::cast` operator is consumed as a unit so the type name after it
|
|
86
|
+
// still lowercases and the second colon is never mistaken for a param start.
|
|
87
|
+
// The quote-aware char-walk below (`LcKeepDrive`→`LcKeepWorker`) exists only to
|
|
88
|
+
// preserve the case of `:name` params — its single consumer, `ExtractParams`, emits
|
|
89
|
+
// those as `withParams` keys. Everything else the walk preserves (quoted
|
|
90
|
+
// literals/identifiers) is INVISIBLE to the param→type result: literals are
|
|
91
|
+
// stripped/widened, quoted column refs are lowercased by `CleanIdent` anyway, and an
|
|
92
|
+
// aliased qualifier and its uses fold consistently. So when NO `:name` param
|
|
93
|
+
// identifier carries an uppercase letter, every param name is already lowercase and
|
|
94
|
+
// the `Lowercase<S>` intrinsic yields a byte-identical `ExtractParams` result —
|
|
95
|
+
// replacing the only un-truncated O(length) char-walk on the builder param-extraction
|
|
96
|
+
// hot path with a single depth-1 native operation. That trims this chain's
|
|
97
|
+
// instantiation COUNT and DEPTH (both TS2589 drivers), buying recursion-limit headroom
|
|
98
|
+
// at the cost of a little more compiler memory (the intrinsic interns the full
|
|
99
|
+
// lowercased literal) — a trade we take deliberately to make large builder queries
|
|
100
|
+
// less likely to hit the depth ceiling.
|
|
101
|
+
//
|
|
102
|
+
// `ParamsHaveUpper` is the gate. It jumps colon-to-colon (leftmost template match), so
|
|
103
|
+
// its depth is the NUMBER OF COLONS — a handful — not the query length, staying far
|
|
104
|
+
// cheaper than the walk it guards. `::cast` skips both colons (cast type names always
|
|
105
|
+
// lowercase safely). `Lowercase<Nm> extends Nm` is false exactly when the param name
|
|
106
|
+
// `Nm` carries an uppercase letter. It is conservative: a colon-dense overrun, or ANY
|
|
107
|
+
// uppercase param, falls back to the exact-fidelity walk — false positives only cost
|
|
108
|
+
// speed, never correctness.
|
|
109
|
+
type ParamsHaveUpper<S extends string, Steps extends any[] = []> =
|
|
110
|
+
Steps["length"] extends 64
|
|
111
|
+
? true
|
|
112
|
+
: S extends `${infer _Pre}:${infer Rest}`
|
|
113
|
+
? Rest extends `:${infer R2}`
|
|
114
|
+
? ParamsHaveUpper<R2, [any, ...Steps]>
|
|
115
|
+
: ReadParamIdent<Rest> extends { name: infer Nm extends string; rest: infer Rr extends string }
|
|
116
|
+
? Lowercase<Nm> extends Nm
|
|
117
|
+
? ParamsHaveUpper<Rr, [any, ...Steps]>
|
|
118
|
+
: true
|
|
119
|
+
: ParamsHaveUpper<Rest, [any, ...Steps]>
|
|
120
|
+
: false;
|
|
121
|
+
|
|
122
|
+
export type LowercaseOutsideQuotesKeepParams<S extends string> =
|
|
123
|
+
string extends S
|
|
124
|
+
? string
|
|
125
|
+
: ParamsHaveUpper<S> extends true
|
|
126
|
+
? LcKeepDrive<LcKeepWorker<S, false, false, "", []>>
|
|
127
|
+
: Lowercase<S>;
|
|
128
|
+
|
|
129
|
+
type LcKeepDrive<R> =
|
|
130
|
+
R extends { __c: [infer S extends string, infer Q1 extends boolean, infer Q2 extends boolean, infer Acc extends string] }
|
|
131
|
+
? LcKeepDrive<LcKeepWorker<S, Q1, Q2, Acc, []>>
|
|
132
|
+
: R;
|
|
133
|
+
|
|
134
|
+
// Chars that terminate a `:name` parameter identifier in a SQL fragment.
|
|
135
|
+
type ParamNameStop =
|
|
136
|
+
| " " | "\t" | "\n" | "," | ";" | ")" | "(" | "'" | '"' | ":" | "."
|
|
137
|
+
| "=" | "+" | "-" | "*" | "/" | "|" | "%" | ">" | "<" | "!" | "~"
|
|
138
|
+
| "@" | "#" | "&" | "^" | "[" | "]" | "{" | "}";
|
|
139
|
+
// Copy a param identifier verbatim (case-preserved) up to the first stop char.
|
|
140
|
+
// Capped so a pathological no-stop tail cannot blow the recursion budget.
|
|
141
|
+
type ReadParamIdent<S extends string, Acc extends string = "", N extends any[] = []> =
|
|
142
|
+
N["length"] extends 128 ? { name: Acc; rest: S }
|
|
143
|
+
: S extends `${infer C}${infer R}`
|
|
144
|
+
? C extends ParamNameStop ? { name: Acc; rest: S }
|
|
145
|
+
: ReadParamIdent<R, `${Acc}${C}`, [any, ...N]>
|
|
146
|
+
: { name: Acc; rest: S };
|
|
147
|
+
|
|
148
|
+
type LcKeepWorker<
|
|
149
|
+
S extends string,
|
|
150
|
+
InSingleQuote extends boolean,
|
|
151
|
+
InDoubleQuote extends boolean,
|
|
152
|
+
Acc extends string,
|
|
153
|
+
Steps extends any[]
|
|
154
|
+
> = Steps["length"] extends 450
|
|
155
|
+
? { __c: [S, InSingleQuote, InDoubleQuote, Acc] }
|
|
156
|
+
: S extends `${infer C}${infer Rest}`
|
|
157
|
+
? C extends "'"
|
|
158
|
+
? InDoubleQuote extends true
|
|
159
|
+
? LcKeepWorker<Rest, InSingleQuote, InDoubleQuote, `${Acc}${C}`, [any, ...Steps]>
|
|
160
|
+
: InSingleQuote extends true
|
|
161
|
+
? LcKeepWorker<Rest, false, InDoubleQuote, `${Acc}${C}`, [any, ...Steps]>
|
|
162
|
+
: LcKeepWorker<Rest, true, InDoubleQuote, `${Acc}${C}`, [any, ...Steps]>
|
|
163
|
+
: C extends `"`
|
|
164
|
+
? InSingleQuote extends true
|
|
165
|
+
? LcKeepWorker<Rest, InSingleQuote, InDoubleQuote, `${Acc}${C}`, [any, ...Steps]>
|
|
166
|
+
: InDoubleQuote extends true
|
|
167
|
+
? LcKeepWorker<Rest, InSingleQuote, false, `${Acc}${C}`, [any, ...Steps]>
|
|
168
|
+
: LcKeepWorker<Rest, InSingleQuote, true, `${Acc}${C}`, [any, ...Steps]>
|
|
169
|
+
: InSingleQuote extends true
|
|
170
|
+
? LcKeepWorker<Rest, InSingleQuote, InDoubleQuote, `${Acc}${C}`, [any, ...Steps]>
|
|
171
|
+
: InDoubleQuote extends true
|
|
172
|
+
? LcKeepWorker<Rest, InSingleQuote, InDoubleQuote, `${Acc}${C}`, [any, ...Steps]>
|
|
173
|
+
// outside quotes: a lone `:` begins a named param (case-
|
|
174
|
+
// preserved); `::` is a cast operator consumed as a unit.
|
|
175
|
+
: C extends ":"
|
|
176
|
+
? Rest extends `:${infer R2}`
|
|
177
|
+
? LcKeepWorker<R2, InSingleQuote, InDoubleQuote, `${Acc}::`, [any, ...Steps]>
|
|
178
|
+
: ReadParamIdent<Rest> extends { name: infer Nm extends string; rest: infer Rr extends string }
|
|
179
|
+
? LcKeepWorker<Rr, InSingleQuote, InDoubleQuote, `${Acc}:${Nm}`, [any, ...Steps]>
|
|
180
|
+
: LcKeepWorker<Rest, InSingleQuote, InDoubleQuote, `${Acc}:`, [any, ...Steps]>
|
|
181
|
+
: LcKeepWorker<Rest, InSingleQuote, InDoubleQuote, `${Acc}${Lowercase<C>}`, [any, ...Steps]>
|
|
182
|
+
: Acc;
|
|
183
|
+
|
|
184
|
+
// NormalizeQuery variant that preserves `:name` param case — used by the
|
|
185
|
+
// write/raw builder param extraction (ExtractParams) only.
|
|
186
|
+
export type NormalizeQueryKeepParams<S extends string> =
|
|
187
|
+
RewriteExtractCall<Trim<RemoveTrailingSemicolon<LowercaseOutsideQuotesKeepParams<CollapseSpaces<ReplaceWhitespace<StripComments<NeutralizePgLiterals<S>>>>>>>>;
|
|
188
|
+
|
|
189
|
+
// Convert every `\n` / `\t` / `\r` to a space. OCCURRENCE-based (like
|
|
190
|
+
// `CollapseSpaces`): each step splits at the FIRST remaining line break, so the
|
|
191
|
+
// `${infer A}` skips a whole run of non-whitespace and the cost is O(line breaks),
|
|
192
|
+
// NOT O(chars). The old per-char form (`ReplaceWhitespaceLimited`) capped at ~900
|
|
193
|
+
// CHARS, so a report-scale multi-line query left the TAIL of its SELECT list
|
|
194
|
+
// un-normalized — trailing projections kept raw newlines and degraded to
|
|
195
|
+
// `unknown`/dropped. The occurrence form covers the whole query under a step cap
|
|
196
|
+
// that stays far from TS's tail-recursion limit. (`HasLineBreaks` keeps the common
|
|
197
|
+
// single-line query a true no-op.)
|
|
198
|
+
export type ReplaceWhitespace<S extends string> =
|
|
199
|
+
HasLineBreaks<S> extends true
|
|
200
|
+
? ReplaceWhitespaceRuns<S>
|
|
201
|
+
: S;
|
|
202
|
+
|
|
203
|
+
type ReplaceWhitespaceRuns<S extends string, Steps extends any[] = []> =
|
|
204
|
+
string extends S
|
|
205
|
+
? S
|
|
206
|
+
: Steps["length"] extends 1500
|
|
207
|
+
? S
|
|
208
|
+
: S extends `${infer A}\n${infer B}`
|
|
209
|
+
? ReplaceWhitespaceRuns<`${A} ${B}`, [any, ...Steps]>
|
|
210
|
+
: S extends `${infer A}\t${infer B}`
|
|
211
|
+
? ReplaceWhitespaceRuns<`${A} ${B}`, [any, ...Steps]>
|
|
212
|
+
: S extends `${infer A}\r${infer B}`
|
|
213
|
+
? ReplaceWhitespaceRuns<`${A} ${B}`, [any, ...Steps]>
|
|
214
|
+
: S;
|
|
215
|
+
|
|
216
|
+
// Cheap "is this string longer than ~500 chars" check: drop 10 chars per step
|
|
217
|
+
// for up to 50 steps. If content survives all 50 drops the string exceeds the
|
|
218
|
+
// budget. Used to keep the expensive full validator off report-scale queries
|
|
219
|
+
// (which the high-complexity bypass protects) while still fully validating
|
|
220
|
+
// ordinary small queries. Chunked (10/step) so it stays far cheaper than a
|
|
221
|
+
// char-by-char walk.
|
|
222
|
+
export type ExceedsLengthBudget<S extends string, Steps extends any[] = []> =
|
|
223
|
+
string extends S
|
|
224
|
+
? true
|
|
225
|
+
: Steps["length"] extends 50
|
|
226
|
+
? S extends "" ? false : true
|
|
227
|
+
: S extends ""
|
|
228
|
+
? false
|
|
229
|
+
: ExceedsLengthBudget<Drop10Chars<S>, [any, ...Steps]>;
|
|
230
|
+
|
|
231
|
+
export type Drop10Chars<S extends string> =
|
|
232
|
+
S extends `${infer _A}${infer _B}${infer _C}${infer _D}${infer _E}${infer _F}${infer _G}${infer _H}${infer _I}${infer _J}${infer R}`
|
|
233
|
+
? R
|
|
234
|
+
: "";
|
|
235
|
+
|
|
236
|
+
export type HasLineBreaks<S extends string> =
|
|
237
|
+
S extends `${string}\n${string}` ? true :
|
|
238
|
+
S extends `${string}\t${string}` ? true :
|
|
239
|
+
S extends `${string}\r${string}` ? true :
|
|
240
|
+
false;
|
|
241
|
+
|
|
242
|
+
export type RemoveTrailingSemicolon<S extends string> =
|
|
243
|
+
Trim<S> extends `${infer R};` ? Trim<R> : Trim<S>;
|