@kuindji/typed-sql 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +227 -0
- package/dist/builder/assemble.d.ts +13 -0
- package/dist/builder/assemble.d.ts.map +1 -0
- package/dist/builder/assemble.js +86 -0
- package/dist/builder/assemble.js.map +1 -0
- package/dist/builder/condition-tree.d.ts +27 -0
- package/dist/builder/condition-tree.d.ts.map +1 -0
- package/dist/builder/condition-tree.js +91 -0
- package/dist/builder/condition-tree.js.map +1 -0
- package/dist/builder/conditional-sql.d.ts +80 -0
- package/dist/builder/conditional-sql.d.ts.map +1 -0
- package/dist/builder/conditional-sql.js +88 -0
- package/dist/builder/conditional-sql.js.map +1 -0
- package/dist/builder/db.d.ts +76 -0
- package/dist/builder/db.d.ts.map +1 -0
- package/dist/builder/db.js +12 -0
- package/dist/builder/db.js.map +1 -0
- package/dist/builder/delete.d.ts +39 -0
- package/dist/builder/delete.d.ts.map +1 -0
- package/dist/builder/delete.js +33 -0
- package/dist/builder/delete.js.map +1 -0
- package/dist/builder/extract-params.d.ts +97 -0
- package/dist/builder/extract-params.d.ts.map +1 -0
- package/dist/builder/extract-params.js +2 -0
- package/dist/builder/extract-params.js.map +1 -0
- package/dist/builder/index.d.ts +23 -0
- package/dist/builder/index.d.ts.map +1 -0
- package/dist/builder/index.js +14 -0
- package/dist/builder/index.js.map +1 -0
- package/dist/builder/insert.d.ts +51 -0
- package/dist/builder/insert.d.ts.map +1 -0
- package/dist/builder/insert.js +39 -0
- package/dist/builder/insert.js.map +1 -0
- package/dist/builder/mutate.d.ts +28 -0
- package/dist/builder/mutate.d.ts.map +1 -0
- package/dist/builder/mutate.js +17 -0
- package/dist/builder/mutate.js.map +1 -0
- package/dist/builder/params.d.ts +22 -0
- package/dist/builder/params.d.ts.map +1 -0
- package/dist/builder/params.js +65 -0
- package/dist/builder/params.js.map +1 -0
- package/dist/builder/return-type.d.ts +39 -0
- package/dist/builder/return-type.d.ts.map +1 -0
- package/dist/builder/return-type.js +2 -0
- package/dist/builder/return-type.js.map +1 -0
- package/dist/builder/scanner.d.ts +49 -0
- package/dist/builder/scanner.d.ts.map +1 -0
- package/dist/builder/scanner.js +240 -0
- package/dist/builder/scanner.js.map +1 -0
- package/dist/builder/select.d.ts +76 -0
- package/dist/builder/select.d.ts.map +1 -0
- package/dist/builder/select.js +240 -0
- package/dist/builder/select.js.map +1 -0
- package/dist/builder/sql-tag.d.ts +319 -0
- package/dist/builder/sql-tag.d.ts.map +1 -0
- package/dist/builder/sql-tag.js +3 -0
- package/dist/builder/sql-tag.js.map +1 -0
- package/dist/builder/sql.d.ts +17 -0
- package/dist/builder/sql.d.ts.map +1 -0
- package/dist/builder/sql.js +36 -0
- package/dist/builder/sql.js.map +1 -0
- package/dist/builder/state.d.ts +53 -0
- package/dist/builder/state.d.ts.map +1 -0
- package/dist/builder/state.js +18 -0
- package/dist/builder/state.js.map +1 -0
- package/dist/builder/update.d.ts +60 -0
- package/dist/builder/update.d.ts.map +1 -0
- package/dist/builder/update.js +40 -0
- package/dist/builder/update.js.map +1 -0
- package/dist/builder/write-assemble.d.ts +5 -0
- package/dist/builder/write-assemble.d.ts.map +1 -0
- package/dist/builder/write-assemble.js +57 -0
- package/dist/builder/write-assemble.js.map +1 -0
- package/dist/builder/write-state.d.ts +39 -0
- package/dist/builder/write-state.d.ts.map +1 -0
- package/dist/builder/write-state.js +6 -0
- package/dist/builder/write-state.js.map +1 -0
- package/dist/builder/write-tag.d.ts +91 -0
- package/dist/builder/write-tag.d.ts.map +1 -0
- package/dist/builder/write-tag.js +2 -0
- package/dist/builder/write-tag.js.map +1 -0
- package/dist/columns.d.ts +33 -0
- package/dist/columns.d.ts.map +1 -0
- package/dist/columns.js +2 -0
- package/dist/columns.js.map +1 -0
- package/dist/expressions.d.ts +71 -0
- package/dist/expressions.d.ts.map +1 -0
- package/dist/expressions.js +2 -0
- package/dist/expressions.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/parsing/extract.d.ts +47 -0
- package/dist/parsing/extract.d.ts.map +1 -0
- package/dist/parsing/extract.js +2 -0
- package/dist/parsing/extract.js.map +1 -0
- package/dist/parsing/normalize.d.ts +44 -0
- package/dist/parsing/normalize.d.ts.map +1 -0
- package/dist/parsing/normalize.js +2 -0
- package/dist/parsing/normalize.js.map +1 -0
- package/dist/parsing/pg-literals.d.ts +37 -0
- package/dist/parsing/pg-literals.d.ts.map +1 -0
- package/dist/parsing/pg-literals.js +2 -0
- package/dist/parsing/pg-literals.js.map +1 -0
- package/dist/parsing/split.d.ts +100 -0
- package/dist/parsing/split.d.ts.map +1 -0
- package/dist/parsing/split.js +2 -0
- package/dist/parsing/split.js.map +1 -0
- package/dist/parsing/string-utils.d.ts +29 -0
- package/dist/parsing/string-utils.d.ts.map +1 -0
- package/dist/parsing/string-utils.js +2 -0
- package/dist/parsing/string-utils.js.map +1 -0
- package/dist/parsing/tokenize.d.ts +27 -0
- package/dist/parsing/tokenize.d.ts.map +1 -0
- package/dist/parsing/tokenize.js +2 -0
- package/dist/parsing/tokenize.js.map +1 -0
- package/dist/parsing.d.ts +7 -0
- package/dist/parsing.d.ts.map +1 -0
- package/dist/parsing.js +9 -0
- package/dist/parsing.js.map +1 -0
- package/dist/partial.d.ts +30 -0
- package/dist/partial.d.ts.map +1 -0
- package/dist/partial.js +10 -0
- package/dist/partial.js.map +1 -0
- package/dist/schema.d.ts +28 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +2 -0
- package/dist/schema.js.map +1 -0
- package/dist/tables.d.ts +34 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +2 -0
- package/dist/tables.js.map +1 -0
- package/dist/utils.d.ts +13 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +3 -0
- package/dist/utils.js.map +1 -0
- package/dist/validation/cte.d.ts +54 -0
- package/dist/validation/cte.d.ts.map +1 -0
- package/dist/validation/cte.js +2 -0
- package/dist/validation/cte.js.map +1 -0
- package/dist/validation/dispatch.d.ts +31 -0
- package/dist/validation/dispatch.d.ts.map +1 -0
- package/dist/validation/dispatch.js +2 -0
- package/dist/validation/dispatch.js.map +1 -0
- package/dist/validation/joins.d.ts +16 -0
- package/dist/validation/joins.d.ts.map +1 -0
- package/dist/validation/joins.js +2 -0
- package/dist/validation/joins.js.map +1 -0
- package/dist/validation/return-derived.d.ts +67 -0
- package/dist/validation/return-derived.d.ts.map +1 -0
- package/dist/validation/return-derived.js +5 -0
- package/dist/validation/return-derived.js.map +1 -0
- package/dist/validation/return-types.d.ts +41 -0
- package/dist/validation/return-types.d.ts.map +1 -0
- package/dist/validation/return-types.js +2 -0
- package/dist/validation/return-types.js.map +1 -0
- package/dist/validation/validate-columns.d.ts +63 -0
- package/dist/validation/validate-columns.d.ts.map +1 -0
- package/dist/validation/validate-columns.js +2 -0
- package/dist/validation/validate-columns.js.map +1 -0
- package/dist/validation.d.ts +7 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +9 -0
- package/dist/validation.js.map +1 -0
- package/package.json +64 -0
- package/src/builder/assemble.ts +100 -0
- package/src/builder/condition-tree.ts +162 -0
- package/src/builder/conditional-sql.ts +325 -0
- package/src/builder/db.ts +281 -0
- package/src/builder/delete.ts +57 -0
- package/src/builder/extract-params.ts +507 -0
- package/src/builder/index.ts +58 -0
- package/src/builder/insert.ts +75 -0
- package/src/builder/mutate.ts +55 -0
- package/src/builder/params.ts +95 -0
- package/src/builder/return-type.ts +66 -0
- package/src/builder/scanner.ts +254 -0
- package/src/builder/select.ts +470 -0
- package/src/builder/sql-tag.ts +422 -0
- package/src/builder/sql.ts +51 -0
- package/src/builder/state.ts +55 -0
- package/src/builder/update.ts +77 -0
- package/src/builder/write-assemble.ts +52 -0
- package/src/builder/write-state.ts +43 -0
- package/src/builder/write-tag.ts +119 -0
- package/src/columns.ts +336 -0
- package/src/expressions.ts +745 -0
- package/src/index.ts +81 -0
- package/src/parsing/extract.ts +260 -0
- package/src/parsing/normalize.ts +243 -0
- package/src/parsing/pg-literals.ts +289 -0
- package/src/parsing/split.ts +288 -0
- package/src/parsing/string-utils.ts +172 -0
- package/src/parsing/tokenize.ts +321 -0
- package/src/parsing.ts +8 -0
- package/src/partial.ts +241 -0
- package/src/schema.ts +130 -0
- package/src/tables.ts +278 -0
- package/src/utils.ts +43 -0
- package/src/validation/cte.ts +198 -0
- package/src/validation/dispatch.ts +312 -0
- package/src/validation/joins.ts +198 -0
- package/src/validation/return-derived.ts +253 -0
- package/src/validation/return-types.ts +271 -0
- package/src/validation/validate-columns.ts +489 -0
- package/src/validation.ts +8 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// Low-level string primitives, predicates, and token cleaners.
|
|
2
|
+
import type { SqlConstant, OperatorToken } from "./tokenize.js";
|
|
3
|
+
|
|
4
|
+
export type ReplaceAll<S extends string, From extends string, To extends string> =
|
|
5
|
+
From extends ""
|
|
6
|
+
? S
|
|
7
|
+
: ReplaceAllImpl<S, From, To>;
|
|
8
|
+
|
|
9
|
+
export type ReplaceAllImpl<
|
|
10
|
+
S extends string,
|
|
11
|
+
From extends string,
|
|
12
|
+
To extends string,
|
|
13
|
+
Steps extends any[] = []
|
|
14
|
+
> = Steps["length"] extends 250
|
|
15
|
+
? S
|
|
16
|
+
: S extends `${infer Head}${From}${infer Tail}`
|
|
17
|
+
? `${Head}${To}${ReplaceAllImpl<Tail, From, To, [any, ...Steps]>}`
|
|
18
|
+
: S;
|
|
19
|
+
|
|
20
|
+
// Collapse runs of consecutive spaces to a single space. A ladder of multi-space
|
|
21
|
+
// patterns removes up to 15 spaces per recursion step, so a deeply-indented
|
|
22
|
+
// report-scale query collapses in O(runs) rather than O(spaces) steps — keeping the
|
|
23
|
+
// downstream `Split<N, " ">` token walk well under TypeScript's tail-recursion
|
|
24
|
+
// limit. (A query whose normalized form carried 1000+ spaces previously overflowed
|
|
25
|
+
// `Split` with TS2589.) Tail-recursive; cap high enough for multi-thousand-space
|
|
26
|
+
// analytics queries.
|
|
27
|
+
export type CollapseSpaces<S extends string, Steps extends any[] = []> =
|
|
28
|
+
Steps["length"] extends 800
|
|
29
|
+
? S
|
|
30
|
+
: S extends `${infer A} ${infer B}` // 16 spaces -> 1
|
|
31
|
+
? CollapseSpaces<`${A} ${B}`, [any, ...Steps]>
|
|
32
|
+
: S extends `${infer A} ${infer B}` // 4 spaces -> 1
|
|
33
|
+
? CollapseSpaces<`${A} ${B}`, [any, ...Steps]>
|
|
34
|
+
: S extends `${infer A} ${infer B}` // 2 spaces -> 1
|
|
35
|
+
? CollapseSpaces<`${A} ${B}`, [any, ...Steps]>
|
|
36
|
+
: S;
|
|
37
|
+
|
|
38
|
+
export type TrimLeft<S extends string> = S extends `${Whitespace}${infer R}` ? TrimLeft<R> : S;
|
|
39
|
+
|
|
40
|
+
export type TrimRight<S extends string> = S extends `${infer R}${Whitespace}` ? TrimRight<R> : S;
|
|
41
|
+
|
|
42
|
+
export type Trim<S extends string> = TrimLeft<TrimRight<S>>;
|
|
43
|
+
|
|
44
|
+
export type Whitespace = " " | "\n" | "\t" | "\r";
|
|
45
|
+
|
|
46
|
+
// Clean identifiers
|
|
47
|
+
|
|
48
|
+
export type CleanIdent<S extends string> = Lowercase<Unquote<TrimPunctuation<Trim<S>>>>;
|
|
49
|
+
|
|
50
|
+
export type CleanExpr<S extends string> = Trim<S> extends infer T extends string ? T : S;
|
|
51
|
+
|
|
52
|
+
export type IsIdentifier<S extends string> =
|
|
53
|
+
HasSpecial<S> extends true ? false : true;
|
|
54
|
+
|
|
55
|
+
export type IsRuntimeStringFragment<S extends string> =
|
|
56
|
+
string extends S
|
|
57
|
+
? true
|
|
58
|
+
: `${Lowercase<string>}` extends S
|
|
59
|
+
? true
|
|
60
|
+
: string extends CleanIdent<S>
|
|
61
|
+
? true
|
|
62
|
+
: false;
|
|
63
|
+
|
|
64
|
+
export type HasSpecial<S extends string> =
|
|
65
|
+
S extends `${string} ${string}` ? true :
|
|
66
|
+
S extends `${string}(${string}` ? true :
|
|
67
|
+
S extends `${string})${string}` ? true :
|
|
68
|
+
S extends `${string}+${string}` ? true :
|
|
69
|
+
S extends `${string}-${string}` ? true :
|
|
70
|
+
S extends `${string}*${string}` ? true :
|
|
71
|
+
S extends `${string}/${string}` ? true :
|
|
72
|
+
S extends `${string}=${string}` ? true :
|
|
73
|
+
S extends `${string}<${string}` ? true :
|
|
74
|
+
S extends `${string}>${string}` ? true :
|
|
75
|
+
S extends `${string},${string}` ? true :
|
|
76
|
+
S extends `${string}::${string}` ? true :
|
|
77
|
+
S extends `${string}||${string}` ? true :
|
|
78
|
+
false;
|
|
79
|
+
|
|
80
|
+
export type IsParamPlaceholder<S extends string> =
|
|
81
|
+
S extends `$${string}` ? true :
|
|
82
|
+
S extends `:${string}` ? true :
|
|
83
|
+
S extends "?" ? true :
|
|
84
|
+
false;
|
|
85
|
+
|
|
86
|
+
export type IsQualifiedRefCandidate<S extends string> =
|
|
87
|
+
S extends `'${string}'` ? false :
|
|
88
|
+
S extends `${number}.${number}` ? false :
|
|
89
|
+
true;
|
|
90
|
+
|
|
91
|
+
export type IsSqlConstant<S extends string> =
|
|
92
|
+
CleanIdent<S> extends SqlConstant ? true : false;
|
|
93
|
+
|
|
94
|
+
export type SqlConstantType<S extends string> =
|
|
95
|
+
CleanIdent<S> extends "current_date" ? string :
|
|
96
|
+
CleanIdent<S> extends "current_time" ? string :
|
|
97
|
+
CleanIdent<S> extends "current_timestamp" ? string :
|
|
98
|
+
CleanIdent<S> extends "localtime" ? string :
|
|
99
|
+
CleanIdent<S> extends "localtimestamp" ? string :
|
|
100
|
+
CleanIdent<S> extends "current_user" ? string :
|
|
101
|
+
CleanIdent<S> extends "session_user" ? string :
|
|
102
|
+
CleanIdent<S> extends "current_schema" ? string :
|
|
103
|
+
string;
|
|
104
|
+
|
|
105
|
+
export type Unquote<S extends string> =
|
|
106
|
+
S extends `"${infer R}"` ? R :
|
|
107
|
+
S extends `\`${infer R}\`` ? R :
|
|
108
|
+
S;
|
|
109
|
+
|
|
110
|
+
export type TrimPunctuation<S extends string> =
|
|
111
|
+
S extends `${Punct}${infer R}` ? TrimPunctuation<R> :
|
|
112
|
+
S extends `${infer R}${Punct}` ? TrimPunctuation<R> :
|
|
113
|
+
S;
|
|
114
|
+
|
|
115
|
+
export type Punct = "," | ";" | "(" | ")";
|
|
116
|
+
|
|
117
|
+
// Split helpers
|
|
118
|
+
|
|
119
|
+
export type Split<
|
|
120
|
+
S extends string,
|
|
121
|
+
Delim extends string,
|
|
122
|
+
Acc extends string[] = [],
|
|
123
|
+
Steps extends any[] = []
|
|
124
|
+
> = Steps["length"] extends 2000
|
|
125
|
+
? [...Acc, S]
|
|
126
|
+
: S extends `${infer Head}${Delim}${infer Tail}`
|
|
127
|
+
? Split<Tail, Delim, [...Acc, Head], [any, ...Steps]>
|
|
128
|
+
: [...Acc, S];
|
|
129
|
+
|
|
130
|
+
export type SplitLast<S extends string, Delim extends string> =
|
|
131
|
+
S extends `${infer Head}${Delim}${infer Tail}`
|
|
132
|
+
? Tail extends `${string}${Delim}${string}`
|
|
133
|
+
? SplitLast<Tail, Delim> extends [infer H2 extends string, infer T2 extends string]
|
|
134
|
+
? [`${Head}${Delim}${H2}`, T2]
|
|
135
|
+
: [Head, Tail]
|
|
136
|
+
: [Head, Tail]
|
|
137
|
+
: [S, ""];
|
|
138
|
+
|
|
139
|
+
export type SplitOnDot<S extends string> =
|
|
140
|
+
S extends `${infer A}.${infer B}` ? [A, ...SplitOnDot<B>] : [S];
|
|
141
|
+
|
|
142
|
+
export type SplitOnDotClean<S extends string> =
|
|
143
|
+
SplitOnDot<S> extends [infer A extends string, infer B extends string, infer C extends string]
|
|
144
|
+
? [CleanIdent<A>, CleanIdent<B>, CleanIdent<C>]
|
|
145
|
+
: SplitOnDot<S> extends [infer A extends string, infer B extends string]
|
|
146
|
+
? [CleanIdent<A>, CleanIdent<B>]
|
|
147
|
+
: SplitOnDot<S> extends [infer A extends string]
|
|
148
|
+
? [CleanIdent<A>]
|
|
149
|
+
: [];
|
|
150
|
+
|
|
151
|
+
export type MapClean<Tokens extends string[], Acc extends string[] = []> =
|
|
152
|
+
Tokens extends [infer H extends string, ...infer R extends string[]]
|
|
153
|
+
? MapClean<R, [...Acc, CleanIdent<H> extends "" ? "" : TrimPunctuation<Trim<H>>]>
|
|
154
|
+
: Acc;
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
export type MapCleanLoose<Tokens extends string[], Acc extends string[] = []> =
|
|
158
|
+
Tokens extends [infer H extends string, ...infer R extends string[]]
|
|
159
|
+
? MapCleanLoose<R, [...Acc, CleanLooseToken<H>]>
|
|
160
|
+
: Acc;
|
|
161
|
+
|
|
162
|
+
export type CleanLooseToken<S extends string> =
|
|
163
|
+
S extends OperatorToken
|
|
164
|
+
? S
|
|
165
|
+
: CleanIdent<S> extends ""
|
|
166
|
+
? ""
|
|
167
|
+
: CleanIdent<S>;
|
|
168
|
+
|
|
169
|
+
export type FilterEmpty<Tokens extends string[], Acc extends string[] = []> =
|
|
170
|
+
Tokens extends [infer H extends string, ...infer R extends string[]]
|
|
171
|
+
? H extends "" ? FilterEmpty<R, Acc> : FilterEmpty<R, [...Acc, H]>
|
|
172
|
+
: Acc;
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
// Tokenization, sentinels, operators, and SQL keyword sets.
|
|
2
|
+
import type { CollapseSpaces, FilterEmpty, MapClean, MapCleanLoose, ReplaceAll, Split } from "./string-utils.js";
|
|
3
|
+
import type { ExceedsLengthBudget, HasLineBreaks } from "./normalize.js";
|
|
4
|
+
// Tokenization & parsing helpers
|
|
5
|
+
|
|
6
|
+
export type Tokenize<N extends string> = FilterEmpty<MapClean<Split<N, " ">>>;
|
|
7
|
+
|
|
8
|
+
// Sentinel token standing in for a TOP-LEVEL comma. It survives `MapClean`
|
|
9
|
+
// (no stripped punctuation, non-empty identifier) whereas a bare `,` does not,
|
|
10
|
+
// so it cleanly distinguishes a FROM-source separator from a comma nested in
|
|
11
|
+
// parens / a string literal — which must still be dropped as before.
|
|
12
|
+
export type CommaSep = "__tsqlcomma__";
|
|
13
|
+
|
|
14
|
+
// Replace only TOP-LEVEL commas (paren depth 0, outside single OR double quotes)
|
|
15
|
+
// with the `CommaSep` sentinel (space-padded so it tokenizes on its own). Commas
|
|
16
|
+
// nested inside parens (`count(a, b)`, FROM subqueries, `insert (x, y)`, value
|
|
17
|
+
// tuples), string literals, or quoted identifiers (`users as "u,1"`) are left
|
|
18
|
+
// verbatim and get stripped by `MapClean` as today. The `InDString` arm tracks
|
|
19
|
+
// double-quoted identifiers so a comma inside a quoted table/column alias is not
|
|
20
|
+
// mistaken for a FROM-source separator. Char-walk mirrors `SplitTopLevel` /
|
|
21
|
+
// `StripComments`; step-bounded.
|
|
22
|
+
export type MarkTopLevelCommas<
|
|
23
|
+
S extends string,
|
|
24
|
+
Depth extends any[] = [],
|
|
25
|
+
InString extends boolean = false,
|
|
26
|
+
Acc extends string = "",
|
|
27
|
+
Steps extends any[] = [],
|
|
28
|
+
InDString extends boolean = false
|
|
29
|
+
> = string extends S
|
|
30
|
+
? S
|
|
31
|
+
: Steps["length"] extends 1500
|
|
32
|
+
? `${Acc}${S}`
|
|
33
|
+
: S extends `${infer C}${infer Rest}`
|
|
34
|
+
? InDString extends true
|
|
35
|
+
? MarkTopLevelCommas<Rest, Depth, InString, `${Acc}${C}`, [any, ...Steps], C extends `"` ? false : true>
|
|
36
|
+
: C extends "'"
|
|
37
|
+
? MarkTopLevelCommas<Rest, Depth, InString extends true ? false : true, `${Acc}${C}`, [any, ...Steps], InDString>
|
|
38
|
+
: InString extends true
|
|
39
|
+
? MarkTopLevelCommas<Rest, Depth, InString, `${Acc}${C}`, [any, ...Steps], InDString>
|
|
40
|
+
: C extends `"`
|
|
41
|
+
? MarkTopLevelCommas<Rest, Depth, InString, `${Acc}${C}`, [any, ...Steps], true>
|
|
42
|
+
: C extends "("
|
|
43
|
+
? MarkTopLevelCommas<Rest, [any, ...Depth], InString, `${Acc}${C}`, [any, ...Steps], InDString>
|
|
44
|
+
: C extends ")"
|
|
45
|
+
? MarkTopLevelCommas<Rest, Depth extends [any, ...infer D] ? D : [], InString, `${Acc}${C}`, [any, ...Steps], InDString>
|
|
46
|
+
: C extends ","
|
|
47
|
+
? Depth["length"] extends 0
|
|
48
|
+
? MarkTopLevelCommas<Rest, Depth, InString, `${Acc} ${CommaSep} `, [any, ...Steps], InDString>
|
|
49
|
+
: MarkTopLevelCommas<Rest, Depth, InString, `${Acc}${C}`, [any, ...Steps], InDString>
|
|
50
|
+
: MarkTopLevelCommas<Rest, Depth, InString, `${Acc}${C}`, [any, ...Steps], InDString>
|
|
51
|
+
: Acc;
|
|
52
|
+
|
|
53
|
+
// Token stream for the table/alias collectors: identical to `Tokenize` except
|
|
54
|
+
// top-level commas survive as `CommaSep` tokens (so `from a, b` exposes its
|
|
55
|
+
// source boundary). Used ONLY by `TablesInQuery` / `AliasesInQuery`.
|
|
56
|
+
//
|
|
57
|
+
// Report-scale queries (multi-line, or very long) skip the comma-marking
|
|
58
|
+
// char-walk and fall back to plain `Tokenize` — the same big-query light path
|
|
59
|
+
// `ValidateSQLNormalizedLightSelect` already takes. A comma cross-join in such a
|
|
60
|
+
// query is negligibly rare, and avoiding the extra instantiation depth keeps the
|
|
61
|
+
// largest analytics queries under the TS recursion limit.
|
|
62
|
+
export type TokenizeTables<N extends string> =
|
|
63
|
+
HasLineBreaks<N> extends true
|
|
64
|
+
? Tokenize<N>
|
|
65
|
+
: ExceedsLengthBudget<N> extends true
|
|
66
|
+
? Tokenize<N>
|
|
67
|
+
: FilterEmpty<MapClean<RestoreDQuotedSpaces<Split<MaybeMarkDQuotedSpaces<MarkTopLevelCommas<N>>, " ">>>>;
|
|
68
|
+
|
|
69
|
+
export type TokenizeLoose<N extends string> =
|
|
70
|
+
FilterEmpty<MapCleanLoose<RestoreDQuotedSpaces<
|
|
71
|
+
Split<CollapseSpaces<RestoreWildcards<PadOperators<ProtectWildcards<MaybeMarkDQuotedSpaces<MaybeStripDQuotedPunct<N>>>>>>, " ">
|
|
72
|
+
>>> extends infer Toks extends string[]
|
|
73
|
+
? N extends `${string}distinct ${string}`
|
|
74
|
+
? DropDistinctFrom<Toks>
|
|
75
|
+
: Toks
|
|
76
|
+
: [];
|
|
77
|
+
|
|
78
|
+
// `IS [NOT] DISTINCT FROM` is a comparison operator: its `from` is operator text,
|
|
79
|
+
// NOT a FROM clause / table-source boundary. The column ref-scanner skips a token
|
|
80
|
+
// whose `Prev` is `from` (treating it as a table source), so the RHS expression of
|
|
81
|
+
// the operator (`price IS DISTINCT FROM bogus_col`) escapes validation entirely
|
|
82
|
+
// (round-13 D1/D2). Drop the operator `from` — the one directly preceded by
|
|
83
|
+
// `distinct` — from the token list so the RHS's `Prev` becomes `distinct`, which
|
|
84
|
+
// `CanPrecedeColumn` already blesses, and the column is validated like any other.
|
|
85
|
+
// `distinct` is immediately followed by the bare token `from` ONLY in this
|
|
86
|
+
// operator, so the rewrite is unambiguous. The real FROM-clause `from` is untouched.
|
|
87
|
+
export type DropDistinctFrom<
|
|
88
|
+
Tokens extends string[],
|
|
89
|
+
Acc extends string[] = [],
|
|
90
|
+
Prev extends string = "",
|
|
91
|
+
Steps extends any[] = []
|
|
92
|
+
> = Steps["length"] extends 400
|
|
93
|
+
? [...Acc, ...Tokens]
|
|
94
|
+
: Tokens extends [infer H extends string, ...infer R extends string[]]
|
|
95
|
+
? H extends "from"
|
|
96
|
+
? Prev extends "distinct"
|
|
97
|
+
? DropDistinctFrom<R, Acc, "from", [any, ...Steps]>
|
|
98
|
+
: DropDistinctFrom<R, [...Acc, H], H, [any, ...Steps]>
|
|
99
|
+
: DropDistinctFrom<R, [...Acc, H], H, [any, ...Steps]>
|
|
100
|
+
: Acc;
|
|
101
|
+
|
|
102
|
+
// Operator/comma characters that `PadOperators` would split on. Inside a
|
|
103
|
+
// double-quoted identifier (`"u,1"`) these are part of the identifier, not
|
|
104
|
+
// structure, so splitting on them leaks bogus tokens (`u`, `1`) into the column
|
|
105
|
+
// ref-scan and falsely rejects an otherwise valid query. We drop them from inside
|
|
106
|
+
// double-quoted spans before padding so the identifier stays a single token.
|
|
107
|
+
export type DQuotedPunct =
|
|
108
|
+
"(" | ")" | "," | "=" | "<" | ">" | "+" | "-" | "*" | "/" | "|" | "&" | "!" | "?";
|
|
109
|
+
|
|
110
|
+
// Only pay for the char-walk when there is actually a double quote to handle —
|
|
111
|
+
// the overwhelmingly common no-quote query short-circuits to identity.
|
|
112
|
+
export type MaybeStripDQuotedPunct<S extends string> =
|
|
113
|
+
S extends `${string}"${string}` ? StripDQuotedPunct<S> : S;
|
|
114
|
+
|
|
115
|
+
// Quote-aware walk that removes `DQuotedPunct` characters located INSIDE a
|
|
116
|
+
// double-quoted span while leaving the quote characters and everything outside
|
|
117
|
+
// the quotes untouched. `"u,1"` -> `"u1"`; `"u1".id` (no inner punctuation) is
|
|
118
|
+
// unchanged. Step-bounded against runaway.
|
|
119
|
+
export type StripDQuotedPunct<
|
|
120
|
+
S extends string,
|
|
121
|
+
InDQ extends boolean = false,
|
|
122
|
+
Acc extends string = "",
|
|
123
|
+
Steps extends any[] = []
|
|
124
|
+
> = string extends S
|
|
125
|
+
? S
|
|
126
|
+
: Steps["length"] extends 1500
|
|
127
|
+
? `${Acc}${S}`
|
|
128
|
+
: S extends `${infer C}${infer Rest}`
|
|
129
|
+
? C extends `"`
|
|
130
|
+
? StripDQuotedPunct<Rest, InDQ extends true ? false : true, `${Acc}${C}`, [any, ...Steps]>
|
|
131
|
+
: InDQ extends true
|
|
132
|
+
? C extends DQuotedPunct
|
|
133
|
+
? StripDQuotedPunct<Rest, InDQ, Acc, [any, ...Steps]>
|
|
134
|
+
: StripDQuotedPunct<Rest, InDQ, `${Acc}${C}`, [any, ...Steps]>
|
|
135
|
+
: StripDQuotedPunct<Rest, InDQ, `${Acc}${C}`, [any, ...Steps]>
|
|
136
|
+
: Acc;
|
|
137
|
+
|
|
138
|
+
// Sentinel standing in for a SPACE located INSIDE a double-quoted identifier.
|
|
139
|
+
// `Split<_, " ">` would otherwise break a quoted identifier that contains spaces
|
|
140
|
+
// (`"Order ID"`, `"user alias"`) into several tokens, so a quoted ORDER BY alias
|
|
141
|
+
// fails to resolve and a quoted table alias is mistaken for multiple table-source
|
|
142
|
+
// tokens. Marking the inner spaces keeps the identifier a single token through
|
|
143
|
+
// the space-split; `RestoreDQuotedSpaces` turns each sentinel back into a real
|
|
144
|
+
// space per-token before `CleanIdent`/`MapClean` runs. Mirrors `StripDQuotedPunct`.
|
|
145
|
+
export type DQuoteSpaceSentinel = "__tsqldqsp__";
|
|
146
|
+
|
|
147
|
+
// Only pay for the char-walk when there is actually a double quote present — the
|
|
148
|
+
// overwhelmingly common no-quote query short-circuits to identity.
|
|
149
|
+
export type MaybeMarkDQuotedSpaces<S extends string> =
|
|
150
|
+
S extends `${string}"${string}` ? MarkDQuotedSpaces<S> : S;
|
|
151
|
+
|
|
152
|
+
export type MarkDQuotedSpaces<
|
|
153
|
+
S extends string,
|
|
154
|
+
InDQ extends boolean = false,
|
|
155
|
+
Acc extends string = "",
|
|
156
|
+
Steps extends any[] = []
|
|
157
|
+
> = string extends S
|
|
158
|
+
? S
|
|
159
|
+
: Steps["length"] extends 1500
|
|
160
|
+
? `${Acc}${S}`
|
|
161
|
+
: S extends `${infer C}${infer Rest}`
|
|
162
|
+
? C extends `"`
|
|
163
|
+
? MarkDQuotedSpaces<Rest, InDQ extends true ? false : true, `${Acc}${C}`, [any, ...Steps]>
|
|
164
|
+
: InDQ extends true
|
|
165
|
+
? C extends " "
|
|
166
|
+
? MarkDQuotedSpaces<Rest, InDQ, `${Acc}${DQuoteSpaceSentinel}`, [any, ...Steps]>
|
|
167
|
+
: MarkDQuotedSpaces<Rest, InDQ, `${Acc}${C}`, [any, ...Steps]>
|
|
168
|
+
: MarkDQuotedSpaces<Rest, InDQ, `${Acc}${C}`, [any, ...Steps]>
|
|
169
|
+
: Acc;
|
|
170
|
+
|
|
171
|
+
// Restore the space sentinel to a real space in each token of a token list, so a
|
|
172
|
+
// quoted identifier that survived the space-split as one token (`"Order ID"`,
|
|
173
|
+
// `"user alias".id`) is cleaned to its true value (`order id`, `"user alias".id`).
|
|
174
|
+
export type RestoreDQuotedSpaces<Tokens extends string[], Acc extends string[] = []> =
|
|
175
|
+
Tokens extends [infer H extends string, ...infer R extends string[]]
|
|
176
|
+
? RestoreDQuotedSpaces<R, [...Acc, ReplaceAll<H, DQuoteSpaceSentinel, " ">]>
|
|
177
|
+
: Acc;
|
|
178
|
+
|
|
179
|
+
// A validation-only view of a query: blank the CONTENTS of every single-quoted
|
|
180
|
+
// string literal (`'anything'` -> `''`) and mask the interior spaces of every
|
|
181
|
+
// double-quoted identifier. Used SOLELY on the ValidateSQL path and computed once
|
|
182
|
+
// at the top (before dispatch), so the char-walk completes to a concrete string
|
|
183
|
+
// and never compounds with validation's own instantiation depth. The result path
|
|
184
|
+
// is untouched, so literal value types still infer from the original text (E1).
|
|
185
|
+
//
|
|
186
|
+
// String literal contents are never column/table references, yet their interior
|
|
187
|
+
// words survive tokenization as bare tokens once `PadOperators` splits on the
|
|
188
|
+
// `(`/`)`/`,` they may contain (`' over (bogus_col)'` -> ... `bogus_col` ...),
|
|
189
|
+
// and the raw space-anchored clause-marker scans (` over (` / ` filter (` /
|
|
190
|
+
// ` within group (` / ` distinct on (` / ` using (`) likewise match inside them.
|
|
191
|
+
// Blanking the literal removes both problems at once (round-12 S1–S5). Masking
|
|
192
|
+
// double-quoted spaces stops the same markers matching inside a quoted output
|
|
193
|
+
// alias (round-12 A1) while leaving the identifier intact for ref validation
|
|
194
|
+
// (`TokenizeLoose` restores the sentinel). The caller gates this behind a quote
|
|
195
|
+
// and within-budget pre-check so report-scale queries never run the walk.
|
|
196
|
+
export type ValidationScanView<S extends string> =
|
|
197
|
+
S extends `${string}'${string}`
|
|
198
|
+
? MaybeMarkDQuotedSpaces<BlankSingleQuotedLiterals<S>>
|
|
199
|
+
: MaybeMarkDQuotedSpaces<S>;
|
|
200
|
+
|
|
201
|
+
export type BlankSingleQuotedLiterals<
|
|
202
|
+
S extends string,
|
|
203
|
+
InString extends boolean = false,
|
|
204
|
+
Acc extends string = "",
|
|
205
|
+
Steps extends any[] = []
|
|
206
|
+
> = string extends S
|
|
207
|
+
? S
|
|
208
|
+
: Steps["length"] extends 600
|
|
209
|
+
? `${Acc}${S}`
|
|
210
|
+
: InString extends true
|
|
211
|
+
? S extends `${infer C}${infer R}`
|
|
212
|
+
? C extends "'"
|
|
213
|
+
? BlankSingleQuotedLiterals<R, false, `${Acc}'`, [any, ...Steps]>
|
|
214
|
+
: BlankSingleQuotedLiterals<R, true, Acc, [any, ...Steps]>
|
|
215
|
+
: `${Acc}'`
|
|
216
|
+
: S extends `${infer C}${infer R}`
|
|
217
|
+
? C extends "'"
|
|
218
|
+
? BlankSingleQuotedLiterals<R, true, `${Acc}'`, [any, ...Steps]>
|
|
219
|
+
: BlankSingleQuotedLiterals<R, false, `${Acc}${C}`, [any, ...Steps]>
|
|
220
|
+
: Acc;
|
|
221
|
+
|
|
222
|
+
export type OperatorToken =
|
|
223
|
+
| "(" | ")" | "," | "=" | "<" | ">" | "+" | "-" | "*" | "/" | "|" | "&" | "!" | "?"
|
|
224
|
+
// `~` / `!~` are PostgreSQL regex-match operators; `[` / `]` delimit array
|
|
225
|
+
// literals/subscripts. Treating them as operators makes `CanPrecedeColumn`
|
|
226
|
+
// bless the RHS expression so a column ref there is validated (e.g.
|
|
227
|
+
// `title ~ bogus_col`, `tags @> array[bogus_col]`), and keeps the operator
|
|
228
|
+
// tokens themselves from being mistaken for columns.
|
|
229
|
+
| "~" | "[" | "]";
|
|
230
|
+
|
|
231
|
+
export type PadOperator<S extends string, Op extends string> =
|
|
232
|
+
ReplaceAll<S, Op, ` ${Op} `>;
|
|
233
|
+
|
|
234
|
+
export type ProtectWildcards<S extends string> =
|
|
235
|
+
ReplaceAll<S, ".*", ".__wildcard__">;
|
|
236
|
+
|
|
237
|
+
export type RestoreWildcards<S extends string> =
|
|
238
|
+
ReplaceAll<S, ".__wildcard__", ".*">;
|
|
239
|
+
|
|
240
|
+
export type PadOperators<S extends string> =
|
|
241
|
+
PadOperator<
|
|
242
|
+
PadOperator<
|
|
243
|
+
PadOperator<
|
|
244
|
+
PadOperator<
|
|
245
|
+
PadOperator<
|
|
246
|
+
PadOperator<
|
|
247
|
+
PadOperator<
|
|
248
|
+
PadOperator<
|
|
249
|
+
PadOperator<
|
|
250
|
+
PadOperator<
|
|
251
|
+
PadOperator<
|
|
252
|
+
PadOperator<
|
|
253
|
+
PadOperator<
|
|
254
|
+
PadOperator<
|
|
255
|
+
PadOperator<
|
|
256
|
+
PadOperator<S, "(">,
|
|
257
|
+
")">,
|
|
258
|
+
",">,
|
|
259
|
+
"=">,
|
|
260
|
+
"<">,
|
|
261
|
+
">">,
|
|
262
|
+
"+">,
|
|
263
|
+
"-">,
|
|
264
|
+
"*">,
|
|
265
|
+
"/">,
|
|
266
|
+
"|">,
|
|
267
|
+
"&">,
|
|
268
|
+
"!">,
|
|
269
|
+
// Split array literal/subscript brackets so the contents
|
|
270
|
+
// (`array[bogus_col]`) tokenize and inner column refs are validated.
|
|
271
|
+
"[">,
|
|
272
|
+
"]">,
|
|
273
|
+
"?">;
|
|
274
|
+
|
|
275
|
+
// SQL keywords (minimal)
|
|
276
|
+
|
|
277
|
+
export type SqlKeyword =
|
|
278
|
+
| "as" | "on" | "where" | "join" | "left" | "right" | "inner" | "outer" | "full" | "cross"
|
|
279
|
+
| "group" | "order" | "by" | "having" | "limit" | "offset" | "union" | "select" | "from"
|
|
280
|
+
| "update" | "insert" | "into" | "values" | "set" | "delete" | "returning" | "distinct";
|
|
281
|
+
|
|
282
|
+
export type SqlReserved =
|
|
283
|
+
| SqlKeyword
|
|
284
|
+
| "and" | "or" | "not" | "is" | "null" | "true" | "false"
|
|
285
|
+
| "like" | "ilike" | "in" | "between" | "exists"
|
|
286
|
+
// Pattern-matching keywords: `SIMILAR TO <pattern>` and `LIKE ... ESCAPE
|
|
287
|
+
// <char>`. The keywords themselves are never column references.
|
|
288
|
+
| "similar" | "to" | "escape"
|
|
289
|
+
| "case" | "when" | "then" | "else" | "end"
|
|
290
|
+
// `ARRAY` in an array literal (`ARRAY[1, 2]`) is a constructor keyword, never
|
|
291
|
+
// a column reference, so it must not be flagged as an invalid column.
|
|
292
|
+
| "array"
|
|
293
|
+
| "asc" | "desc" | "all"
|
|
294
|
+
| "interval" | "nulls" | "first" | "last"
|
|
295
|
+
// Window / frame clause keywords (inside OVER(...) / FILTER(...)): these are
|
|
296
|
+
// never column references, so they must not be flagged as invalid columns.
|
|
297
|
+
| "over" | "filter" | "partition" | "window" | "within"
|
|
298
|
+
| "range" | "rows" | "groups" | "preceding" | "following" | "unbounded";
|
|
299
|
+
|
|
300
|
+
export type SqlConstant =
|
|
301
|
+
| "current_date"
|
|
302
|
+
| "current_time"
|
|
303
|
+
| "current_timestamp"
|
|
304
|
+
| "localtime"
|
|
305
|
+
| "localtimestamp"
|
|
306
|
+
| "current_user"
|
|
307
|
+
| "session_user"
|
|
308
|
+
| "current_schema";
|
|
309
|
+
|
|
310
|
+
export type CanPrecedeColumn<Token extends string> =
|
|
311
|
+
Token extends "" ? true :
|
|
312
|
+
Token extends OperatorToken ? (Token extends ")" ? false : true) :
|
|
313
|
+
Token extends "select" | "where" | "on" | "and" | "or" | "by" | "having" | "set" | "values"
|
|
314
|
+
| "returning" | "distinct" | "case" | "when" | "then" | "else" | "not" | "is" | "in"
|
|
315
|
+
| "between" | "like" | "ilike"
|
|
316
|
+
// RHS of `SIMILAR TO <pattern>` (the `to`) and `... ESCAPE <char>`
|
|
317
|
+
// (the `escape`) are column-bearing expression positions, so an unknown
|
|
318
|
+
// bareword there must be validated like any other RHS column.
|
|
319
|
+
| "to" | "escape"
|
|
320
|
+
? true
|
|
321
|
+
: false;
|
package/src/parsing.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Barrel for the type-level SQL parser. Real declarations live in ./parsing/*.
|
|
2
|
+
// Importers keep using `from "./parsing.js"`; this file freezes that path.
|
|
3
|
+
export * from "./parsing/string-utils.js";
|
|
4
|
+
export * from "./parsing/pg-literals.js";
|
|
5
|
+
export * from "./parsing/normalize.js";
|
|
6
|
+
export * from "./parsing/split.js";
|
|
7
|
+
export * from "./parsing/extract.js";
|
|
8
|
+
export * from "./parsing/tokenize.js";
|