@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,289 @@
|
|
|
1
|
+
// Postgres literal neutralization, EXTRACT rewrite, comment stripping.
|
|
2
|
+
import type { Trim } from "./string-utils.js";
|
|
3
|
+
import type { ExceedsLengthBudget } from "./normalize.js";
|
|
4
|
+
import type { SplitBalancedParen } from "./extract.js";
|
|
5
|
+
|
|
6
|
+
// PostgreSQL string literals beyond the plain `'...'` form: dollar-quoted
|
|
7
|
+
// strings (`$$...$$`, `$tag$...$tag$`) and escape-string constants (`E'...'`).
|
|
8
|
+
// Their bodies are opaque string DATA — never SQL structure or column refs — yet
|
|
9
|
+
// clause-looking text inside them (`over (bogus_col)`, ` returning x`, `from y`)
|
|
10
|
+
// would otherwise be scanned as real SQL. Rewrite each whole span to a canonical
|
|
11
|
+
// blank literal `''` BEFORE any case-folding / comment-stripping / structural
|
|
12
|
+
// scan runs. Everything downstream already treats `'...'` as an opaque string
|
|
13
|
+
// literal (typed `string`, skipped by the column-existence scan), so this single
|
|
14
|
+
// rewrite makes BOTH result inference and validation correct in one place.
|
|
15
|
+
// Runs innermost so a `--`/`/* */` or keyword inside such a literal is neutered
|
|
16
|
+
// before StripComments / the table+column collectors ever see it. Gated behind a
|
|
17
|
+
// precise pre-check so only queries that actually contain such a literal pay for
|
|
18
|
+
// the walk; ordinary `$n` params and plain `'...'`/`"..."` literals are untouched.
|
|
19
|
+
export type NeutralizePgLiterals<S extends string> =
|
|
20
|
+
string extends S
|
|
21
|
+
? S
|
|
22
|
+
: NeedsPgNeutralize<S> extends true
|
|
23
|
+
? NeutralizePgDrive<NeutralizePgWorker<S, false, false, false, "", []>>
|
|
24
|
+
: S;
|
|
25
|
+
|
|
26
|
+
type NeedsPgNeutralize<S extends string> =
|
|
27
|
+
HasPairedDollar<S> extends true
|
|
28
|
+
? true
|
|
29
|
+
: HasEStringOpener<S>;
|
|
30
|
+
|
|
31
|
+
// A genuine dollar-quote needs the SAME `$tag$` delimiter twice (tag may be
|
|
32
|
+
// empty → `$$`). `$n` params can never form two matching `$tag$` delimiters
|
|
33
|
+
// (a param `$1` has no trailing `$`), so this never fires on parameter lists.
|
|
34
|
+
// Inferred in two steps because a back-reference within one template pattern
|
|
35
|
+
// (`…$${infer T}$…$${T}$…`) is not legal — `Tag` is only usable once resolved
|
|
36
|
+
// in the true branch, where the nested check re-matches it as a known literal.
|
|
37
|
+
type HasPairedDollar<S extends string> =
|
|
38
|
+
S extends `${string}$${infer Tag}$${infer Rest}`
|
|
39
|
+
? Rest extends `${string}$${Tag}$${string}`
|
|
40
|
+
? true
|
|
41
|
+
: false
|
|
42
|
+
: false;
|
|
43
|
+
|
|
44
|
+
// `E'`/`e'` only opens an escape string at a token boundary (start, or after
|
|
45
|
+
// whitespace / `(` / `,`); a trailing `…e'` inside a `'...'` literal is just the
|
|
46
|
+
// closing quote, not an opener — restricting the gate keeps the walk off ordinary
|
|
47
|
+
// string literals that merely end in `e`/`E` (e.g. `'sale'`, `'ACTIVE'`).
|
|
48
|
+
type HasEStringOpener<S extends string> =
|
|
49
|
+
S extends `E'${string}` ? true
|
|
50
|
+
: S extends `e'${string}` ? true
|
|
51
|
+
: S extends `${string} E'${string}` ? true
|
|
52
|
+
: S extends `${string} e'${string}` ? true
|
|
53
|
+
: S extends `${string}(E'${string}` ? true
|
|
54
|
+
: S extends `${string}(e'${string}` ? true
|
|
55
|
+
: S extends `${string},E'${string}` ? true
|
|
56
|
+
: S extends `${string},e'${string}` ? true
|
|
57
|
+
: false;
|
|
58
|
+
|
|
59
|
+
type NeutralizePgDrive<R> =
|
|
60
|
+
R extends { __c: [infer S extends string, infer A extends boolean, infer B extends boolean, infer C extends boolean, infer Acc extends string] }
|
|
61
|
+
? NeutralizePgDrive<NeutralizePgWorker<S, A, B, C, Acc, []>>
|
|
62
|
+
: R;
|
|
63
|
+
|
|
64
|
+
// A dollar-quote tag follows unquoted-identifier rules (letters/digits/`_`, no
|
|
65
|
+
// leading digit) or is empty (`$$`). This is what distinguishes a real opener
|
|
66
|
+
// from a positional param run: `$2, $3` is NOT `$<tag>$` (the "tag" `2, ` has a
|
|
67
|
+
// leading digit, a space and a comma), so the walk must not treat it as a
|
|
68
|
+
// dollar-quoted span and eat the query body between two such params.
|
|
69
|
+
type DollarTagLower = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z";
|
|
70
|
+
type DollarTagStart = DollarTagLower | Uppercase<DollarTagLower> | "_";
|
|
71
|
+
type DollarTagChar = DollarTagStart | "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
|
|
72
|
+
|
|
73
|
+
type IsValidDollarTag<Tag extends string> =
|
|
74
|
+
Tag extends ""
|
|
75
|
+
? true
|
|
76
|
+
: Tag extends `${infer F}${infer R}`
|
|
77
|
+
? F extends DollarTagStart
|
|
78
|
+
? AllDollarTagChars<R>
|
|
79
|
+
: false
|
|
80
|
+
: false;
|
|
81
|
+
|
|
82
|
+
type AllDollarTagChars<S extends string, Steps extends any[] = []> =
|
|
83
|
+
S extends ""
|
|
84
|
+
? true
|
|
85
|
+
: Steps["length"] extends 64
|
|
86
|
+
? false
|
|
87
|
+
: S extends `${infer F}${infer R}`
|
|
88
|
+
? F extends DollarTagChar
|
|
89
|
+
? AllDollarTagChars<R, [any, ...Steps]>
|
|
90
|
+
: false
|
|
91
|
+
: false;
|
|
92
|
+
|
|
93
|
+
// Char-walk with three independent "inside a literal" states. A dollar-quote or
|
|
94
|
+
// E-string opener is only honored OUTSIDE a `'...'`/`"..."` literal, so a `$$`
|
|
95
|
+
// sitting inside a single-quoted string is copied verbatim. Chunked-driver
|
|
96
|
+
// pattern: yields `{ __c: [...] }` at the step cap so the driver re-invokes with
|
|
97
|
+
// a fresh counter, keeping arbitrarily long queries under TS2589.
|
|
98
|
+
type NeutralizePgWorker<
|
|
99
|
+
S extends string,
|
|
100
|
+
InS extends boolean, // inside '...'
|
|
101
|
+
InD extends boolean, // inside "..."
|
|
102
|
+
InE extends boolean, // inside E'...' (body blanked)
|
|
103
|
+
Acc extends string,
|
|
104
|
+
Steps extends any[]
|
|
105
|
+
> = Steps["length"] extends 400
|
|
106
|
+
? { __c: [S, InS, InD, InE, Acc] }
|
|
107
|
+
: InE extends true
|
|
108
|
+
// escape-string body: blank content; `\x` escapes the next char; `'` closes.
|
|
109
|
+
? S extends `\\${infer _Esc}${infer R}`
|
|
110
|
+
? NeutralizePgWorker<R, InS, InD, true, Acc, [any, ...Steps]>
|
|
111
|
+
: S extends `'${infer R}`
|
|
112
|
+
? NeutralizePgWorker<R, false, false, false, `${Acc}'`, [any, ...Steps]>
|
|
113
|
+
: S extends `${infer _C}${infer R}`
|
|
114
|
+
? NeutralizePgWorker<R, InS, InD, true, Acc, [any, ...Steps]>
|
|
115
|
+
: `${Acc}'` // unterminated → close it off
|
|
116
|
+
: InS extends true
|
|
117
|
+
? S extends `${infer C}${infer R}`
|
|
118
|
+
? NeutralizePgWorker<R, C extends "'" ? false : true, InD, false, `${Acc}${C}`, [any, ...Steps]>
|
|
119
|
+
: Acc
|
|
120
|
+
: InD extends true
|
|
121
|
+
? S extends `${infer C}${infer R}`
|
|
122
|
+
? NeutralizePgWorker<R, InS, C extends `"` ? false : true, false, `${Acc}${C}`, [any, ...Steps]>
|
|
123
|
+
: Acc
|
|
124
|
+
// outside any literal:
|
|
125
|
+
: S extends `E'${infer R}`
|
|
126
|
+
? NeutralizePgWorker<R, false, false, true, `${Acc}'`, [any, ...Steps]>
|
|
127
|
+
: S extends `e'${infer R}`
|
|
128
|
+
? NeutralizePgWorker<R, false, false, true, `${Acc}'`, [any, ...Steps]>
|
|
129
|
+
: S extends `$${infer Tag}$${infer Rest}`
|
|
130
|
+
? IsValidDollarTag<Tag> extends true
|
|
131
|
+
? Rest extends `${infer _Body}$${Tag}$${infer After}`
|
|
132
|
+
? NeutralizePgWorker<After, false, false, false, `${Acc}''`, [any, ...Steps]>
|
|
133
|
+
// valid tag but no matching close → not a real span; emit one `$`, advance.
|
|
134
|
+
: S extends `$${infer R2}`
|
|
135
|
+
? NeutralizePgWorker<R2, false, false, false, `${Acc}$`, [any, ...Steps]>
|
|
136
|
+
: Acc
|
|
137
|
+
// invalid tag (e.g. a `$n` param run): emit one `$`, advance one char.
|
|
138
|
+
: S extends `$${infer R3}`
|
|
139
|
+
? NeutralizePgWorker<R3, false, false, false, `${Acc}$`, [any, ...Steps]>
|
|
140
|
+
: Acc
|
|
141
|
+
: S extends `'${infer R}`
|
|
142
|
+
? NeutralizePgWorker<R, true, false, false, `${Acc}'`, [any, ...Steps]>
|
|
143
|
+
: S extends `"${infer R}`
|
|
144
|
+
? NeutralizePgWorker<R, false, true, false, `${Acc}"`, [any, ...Steps]>
|
|
145
|
+
: S extends `${infer C}${infer R}`
|
|
146
|
+
? NeutralizePgWorker<R, false, false, false, `${Acc}${C}`, [any, ...Steps]>
|
|
147
|
+
: Acc;
|
|
148
|
+
|
|
149
|
+
// `EXTRACT(field FROM source)` uses keyword grammar: the `field` token (year,
|
|
150
|
+
// month, day, ...) is a date-part keyword, NOT a column, and the inner ` from `
|
|
151
|
+
// is function-local — it is NOT a top-level FROM clause. Left untouched, the
|
|
152
|
+
// token-level table collector treats `from source` as a real FROM source (so the
|
|
153
|
+
// source column is mistaken for a table) and the function-arg validator would
|
|
154
|
+
// flag the date-part `field` as an unknown column. Rewrite each
|
|
155
|
+
// `extract(field from source)` to `extract(source)`: this drops the inner ` from `
|
|
156
|
+
// (so the table collector / ref-scan never see it) and exempts the date-part
|
|
157
|
+
// field, while the source column flows through ordinary function-arg validation.
|
|
158
|
+
// Gated behind a cheap pre-check so only queries containing ` extract(` pay for
|
|
159
|
+
// the walk. Space-anchored so `date_extract(` and similar are left alone.
|
|
160
|
+
export type RewriteExtractCall<S extends string> =
|
|
161
|
+
S extends `${string} extract(${string}`
|
|
162
|
+
// Only small queries that actually contain a single quote risk an
|
|
163
|
+
// ` extract(` sitting inside a string literal (round-12 E1); they take the
|
|
164
|
+
// quote-aware walk. Everything else (no quotes, or report-scale where a
|
|
165
|
+
// parity char-walk would blow the depth budget) uses the plain rewrite.
|
|
166
|
+
? S extends `${string}'${string}`
|
|
167
|
+
? ExceedsLengthBudget<S> extends true
|
|
168
|
+
? RewriteExtractWalk<S>
|
|
169
|
+
: RewriteExtractWalkQuoteAware<S>
|
|
170
|
+
: RewriteExtractWalk<S>
|
|
171
|
+
: S;
|
|
172
|
+
|
|
173
|
+
export type RewriteExtractWalk<S extends string, Steps extends any[] = []> =
|
|
174
|
+
Steps["length"] extends 24
|
|
175
|
+
? S
|
|
176
|
+
: S extends `${infer Pre} extract(${infer AfterOpen}`
|
|
177
|
+
? SplitBalancedParen<`(${AfterOpen}`> extends { inner: infer Inner extends string; rest: infer Rest extends string }
|
|
178
|
+
? Inner extends `${infer _Field} from ${infer Source}`
|
|
179
|
+
? `${RewriteExtractWalk<Pre, [any, ...Steps]>} extract(${Trim<Source>})${RewriteExtractWalk<Rest, [any, ...Steps]>}`
|
|
180
|
+
: `${RewriteExtractWalk<Pre, [any, ...Steps]>} extract(${Inner})${RewriteExtractWalk<Rest, [any, ...Steps]>}`
|
|
181
|
+
: S
|
|
182
|
+
: S;
|
|
183
|
+
|
|
184
|
+
// As `RewriteExtractWalk`, but an ` extract(` whose prefix has an odd number of
|
|
185
|
+
// single quotes sits INSIDE a string literal and is left verbatim (the literal's
|
|
186
|
+
// text must survive into the result-row value type — round-12 E1). The prefix is
|
|
187
|
+
// still recursed so a genuine EXTRACT earlier in the query is rewritten.
|
|
188
|
+
export type RewriteExtractWalkQuoteAware<S extends string, Steps extends any[] = []> =
|
|
189
|
+
Steps["length"] extends 12
|
|
190
|
+
? S
|
|
191
|
+
: S extends `${infer Pre} extract(${infer AfterOpen}`
|
|
192
|
+
? Pre extends `${string}'${string}`
|
|
193
|
+
? OddSingleQuotes<Pre> extends true
|
|
194
|
+
? AfterOpen extends `${infer Lit}'${infer Tail}`
|
|
195
|
+
? `${RewriteExtractWalkQuoteAware<Pre, [any, ...Steps]>} extract(${Lit}'${RewriteExtractWalkQuoteAware<Tail, [any, ...Steps]>}`
|
|
196
|
+
: S
|
|
197
|
+
: RewriteExtractRewriteOne<Pre, AfterOpen, Steps>
|
|
198
|
+
: RewriteExtractRewriteOne<Pre, AfterOpen, Steps>
|
|
199
|
+
: S;
|
|
200
|
+
|
|
201
|
+
export type RewriteExtractRewriteOne<Pre extends string, AfterOpen extends string, Steps extends any[]> =
|
|
202
|
+
SplitBalancedParen<`(${AfterOpen}`> extends { inner: infer Inner extends string; rest: infer Rest extends string }
|
|
203
|
+
? Inner extends `${infer _Field} from ${infer Source}`
|
|
204
|
+
? `${RewriteExtractWalkQuoteAware<Pre, [any, ...Steps]>} extract(${Trim<Source>})${RewriteExtractWalkQuoteAware<Rest, [any, ...Steps]>}`
|
|
205
|
+
: `${RewriteExtractWalkQuoteAware<Pre, [any, ...Steps]>} extract(${Inner})${RewriteExtractWalkQuoteAware<Rest, [any, ...Steps]>}`
|
|
206
|
+
: `${Pre} extract(${AfterOpen}`;
|
|
207
|
+
|
|
208
|
+
// True when `S` contains an odd number of single quotes — i.e. its end is inside
|
|
209
|
+
// an unterminated single-quoted string literal. Bounded; on bail returns the
|
|
210
|
+
// best-effort parity so far.
|
|
211
|
+
export type OddSingleQuotes<S extends string, Flag extends boolean = false, Steps extends any[] = []> =
|
|
212
|
+
string extends S
|
|
213
|
+
? false
|
|
214
|
+
: Steps["length"] extends 400
|
|
215
|
+
? Flag
|
|
216
|
+
: S extends `${infer C}${infer R}`
|
|
217
|
+
? OddSingleQuotes<R, C extends "'" ? (Flag extends true ? false : true) : Flag, [any, ...Steps]>
|
|
218
|
+
: Flag;
|
|
219
|
+
|
|
220
|
+
// Strip `/* ... */` block comments AND `-- ...` line comments before any other
|
|
221
|
+
// parsing so a comment between projection items (or anywhere else) doesn't
|
|
222
|
+
// survive into an expression and collapse the projected row to `never`. Runs
|
|
223
|
+
// FIRST in the pipeline, while line breaks are still present, so a line comment
|
|
224
|
+
// can be cut at its terminating newline.
|
|
225
|
+
//
|
|
226
|
+
// Quote-aware: comment markers inside a single-quoted string literal
|
|
227
|
+
// (`'/* not a comment */'`, `'-- nope'`) OR a double-quoted identifier
|
|
228
|
+
// (`"kept /* marker */ name"`) are preserved verbatim. The walk is the expensive
|
|
229
|
+
// part, so it is gated behind a cheap pre-check: only queries that actually
|
|
230
|
+
// contain a `/*` or `--` pay for the char-walk; everything else (the
|
|
231
|
+
// overwhelming common case) passes straight through untouched.
|
|
232
|
+
export type StripComments<S extends string> =
|
|
233
|
+
string extends S
|
|
234
|
+
? S
|
|
235
|
+
: S extends `${string}/*${string}`
|
|
236
|
+
? StripCommentsWalk<S>
|
|
237
|
+
: S extends `${string}--${string}`
|
|
238
|
+
? StripCommentsWalk<S>
|
|
239
|
+
: S;
|
|
240
|
+
|
|
241
|
+
// Char-walk implementation. Each block comment is replaced by a single space;
|
|
242
|
+
// each line comment is dropped up to (but not including) its newline so the
|
|
243
|
+
// surrounding structure is preserved for `ReplaceWhitespace`/`CollapseSpaces`.
|
|
244
|
+
// An unterminated `/*` drops everything from the `/*` onward. Bounded at 500
|
|
245
|
+
// steps — on bail the remainder is appended as-is, mirroring the truncation
|
|
246
|
+
// `LowercaseOutsideQuotes` already accepts for report-scale strings.
|
|
247
|
+
// `InString` tracks a single-quoted string literal; `InDString` tracks a
|
|
248
|
+
// double-quoted identifier. While inside EITHER, characters (including `/*` and
|
|
249
|
+
// `--`) are copied verbatim — only outside both are comment markers honoured.
|
|
250
|
+
export type StripCommentsWalk<
|
|
251
|
+
S extends string,
|
|
252
|
+
InString extends boolean = false,
|
|
253
|
+
Acc extends string = "",
|
|
254
|
+
Steps extends any[] = [],
|
|
255
|
+
InDString extends boolean = false
|
|
256
|
+
> = string extends S
|
|
257
|
+
? S
|
|
258
|
+
: Steps["length"] extends 500
|
|
259
|
+
? `${Acc}${S}`
|
|
260
|
+
: InString extends true
|
|
261
|
+
? S extends `${infer C}${infer Rest}`
|
|
262
|
+
? StripCommentsWalk<Rest, C extends "'" ? false : true, `${Acc}${C}`, [any, ...Steps], InDString>
|
|
263
|
+
: Acc
|
|
264
|
+
: InDString extends true
|
|
265
|
+
? S extends `${infer C}${infer Rest}`
|
|
266
|
+
? StripCommentsWalk<Rest, InString, `${Acc}${C}`, [any, ...Steps], C extends `"` ? false : true>
|
|
267
|
+
: Acc
|
|
268
|
+
: S extends `/*${infer AfterOpen}`
|
|
269
|
+
? AfterOpen extends `${infer _Body}*/${infer Tail}`
|
|
270
|
+
? StripCommentsWalk<Tail, false, `${Acc} `, [any, ...Steps], false>
|
|
271
|
+
: `${Acc} `
|
|
272
|
+
: S extends `--${infer AfterDash}`
|
|
273
|
+
? StripCommentsWalk<LineCommentTail<AfterDash>, false, `${Acc} `, [any, ...Steps], false>
|
|
274
|
+
: S extends `${infer C}${infer Rest}`
|
|
275
|
+
? StripCommentsWalk<Rest, C extends "'" ? true : false, `${Acc}${C}`, [any, ...Steps], C extends `"` ? true : false>
|
|
276
|
+
: Acc;
|
|
277
|
+
|
|
278
|
+
// Skip a line comment body, returning the tail starting at the first newline
|
|
279
|
+
// (which is kept so words on either side of the comment can't merge). A comment
|
|
280
|
+
// that runs to the end of the string yields `""`. Bounded against runaway.
|
|
281
|
+
export type LineCommentTail<S extends string, Steps extends any[] = []> =
|
|
282
|
+
Steps["length"] extends 1000
|
|
283
|
+
? S
|
|
284
|
+
: S extends `${infer C}${infer Rest}`
|
|
285
|
+
? C extends "\n" | "\r"
|
|
286
|
+
? S
|
|
287
|
+
: LineCommentTail<Rest, [any, ...Steps]>
|
|
288
|
+
: "";
|
|
289
|
+
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
// Top-level / paren-aware splitting + alias extraction.
|
|
2
|
+
import type { CleanIdent, HasSpecial, SplitLast, Trim } from "./string-utils.js";
|
|
3
|
+
import type { SplitBalancedParen } from "./extract.js";
|
|
4
|
+
import type { SqlReserved } from "./tokenize.js";
|
|
5
|
+
|
|
6
|
+
// Split by commas at top level (paren-aware). Safe fallbacks for deep strings.
|
|
7
|
+
|
|
8
|
+
// The per-character worker. A single tail-recursive conditional type can only
|
|
9
|
+
// run ~1000 iterations before TS aborts with TS2589 ("excessively deep") — well
|
|
10
|
+
// below the string lengths real reporting SELECT lists reach (1000+ chars). So
|
|
11
|
+
// instead of one unbounded walk, the worker runs a bounded chunk (CHUNK steps,
|
|
12
|
+
// kept under TS's tail-recursion limit) and then YIELDS its full state as a
|
|
13
|
+
// `{ __c: [...] }` marker. `SplitTopLevel` (the driver below) re-invokes the
|
|
14
|
+
// worker on the remainder with a fresh step counter, which resets TS's internal
|
|
15
|
+
// tail-recursion count per chunk — so arbitrarily long lists split losslessly.
|
|
16
|
+
type SplitTopLevelWorker<
|
|
17
|
+
S extends string,
|
|
18
|
+
Depth extends any[] = [],
|
|
19
|
+
Acc extends string[] = [],
|
|
20
|
+
Cur extends string = "",
|
|
21
|
+
Steps extends any[] = [],
|
|
22
|
+
InQ extends boolean = false,
|
|
23
|
+
InDQ extends boolean = false
|
|
24
|
+
> = Steps["length"] extends 450
|
|
25
|
+
? { __c: [S, Depth, Acc, Cur, InQ, InDQ] }
|
|
26
|
+
: string extends CleanIdent<S>
|
|
27
|
+
? [...Acc, `${Cur}string`]
|
|
28
|
+
: S extends `${infer C}${infer Rest}`
|
|
29
|
+
? C extends "'"
|
|
30
|
+
// A single quote toggles "inside string literal": commas, parens and
|
|
31
|
+
// path braces inside a '...' literal are kept verbatim, not split.
|
|
32
|
+
// Suppressed while inside a double-quoted identifier.
|
|
33
|
+
? InDQ extends true
|
|
34
|
+
? SplitTopLevelWorker<Rest, Depth, Acc, `${Cur}${C}`, [any, ...Steps], InQ, InDQ>
|
|
35
|
+
: SplitTopLevelWorker<Rest, Depth, Acc, `${Cur}${C}`, [any, ...Steps], InQ extends true ? false : true, InDQ>
|
|
36
|
+
: InQ extends true
|
|
37
|
+
? SplitTopLevelWorker<Rest, Depth, Acc, `${Cur}${C}`, [any, ...Steps], InQ, InDQ>
|
|
38
|
+
: C extends `"`
|
|
39
|
+
// A double quote toggles "inside quoted identifier": commas and
|
|
40
|
+
// parens inside `"..."` are part of the identifier, kept verbatim.
|
|
41
|
+
? SplitTopLevelWorker<Rest, Depth, Acc, `${Cur}${C}`, [any, ...Steps], InQ, InDQ extends true ? false : true>
|
|
42
|
+
: InDQ extends true
|
|
43
|
+
? SplitTopLevelWorker<Rest, Depth, Acc, `${Cur}${C}`, [any, ...Steps], InQ, InDQ>
|
|
44
|
+
: C extends "("
|
|
45
|
+
? SplitTopLevelWorker<Rest, [any, ...Depth], Acc, `${Cur}${C}`, [any, ...Steps], InQ, InDQ>
|
|
46
|
+
: C extends ")"
|
|
47
|
+
? SplitTopLevelWorker<Rest, Depth extends [any, ...infer D] ? D : [], Acc, `${Cur}${C}`, [any, ...Steps], InQ, InDQ>
|
|
48
|
+
: C extends ","
|
|
49
|
+
? Depth["length"] extends 0
|
|
50
|
+
? SplitTopLevelWorker<Rest, Depth, [...Acc, Cur], "", [any, ...Steps], InQ, InDQ>
|
|
51
|
+
: SplitTopLevelWorker<Rest, Depth, Acc, `${Cur}${C}`, [any, ...Steps], InQ, InDQ>
|
|
52
|
+
: SplitTopLevelWorker<Rest, Depth, Acc, `${Cur}${C}`, [any, ...Steps], InQ, InDQ>
|
|
53
|
+
: [...Acc, Cur];
|
|
54
|
+
|
|
55
|
+
// Driver: run the worker chunk-by-chunk until it returns the finished `string[]`.
|
|
56
|
+
// Each `{ __c: state }` yield is fed back with a fresh step counter, so no single
|
|
57
|
+
// worker instantiation chain exceeds the per-chunk budget. Driver recursion depth
|
|
58
|
+
// is `length / CHUNK` (≈ a handful for realistic queries), itself well under the
|
|
59
|
+
// tail-recursion limit.
|
|
60
|
+
export type SplitTopLevel<S extends string> =
|
|
61
|
+
SplitTopLevelDrive<SplitTopLevelWorker<S, [], [], "", [], false, false>>;
|
|
62
|
+
|
|
63
|
+
type SplitTopLevelDrive<R> =
|
|
64
|
+
R extends { __c: [infer S extends string, infer D extends any[], infer A extends string[], infer Cur extends string, infer InQ extends boolean, infer InDQ extends boolean] }
|
|
65
|
+
? SplitTopLevelDrive<SplitTopLevelWorker<S, D, A, Cur, [], InQ, InDQ>>
|
|
66
|
+
: R;
|
|
67
|
+
|
|
68
|
+
// Extract select list before top-level FROM (paren- and quote-aware).
|
|
69
|
+
|
|
70
|
+
// Quote-aware on BOTH single quotes (`'...'` string literals) and double quotes
|
|
71
|
+
// (`"..."` quoted identifiers): a ` from ` token sitting inside a double-quoted
|
|
72
|
+
// output alias (`id as "came from import"`) is part of the identifier, not the
|
|
73
|
+
// SELECT/FROM boundary, so it must not split the list. `InString` tracks single
|
|
74
|
+
// quotes; `InDString` tracks double quotes. Parens and the ` from ` boundary are
|
|
75
|
+
// only honoured when outside BOTH kinds of quote.
|
|
76
|
+
export type ExtractBeforeFromTopLevel<
|
|
77
|
+
S extends string,
|
|
78
|
+
Depth extends any[] = [],
|
|
79
|
+
InString extends boolean = false,
|
|
80
|
+
Acc extends string = "",
|
|
81
|
+
Steps extends any[] = [],
|
|
82
|
+
InDString extends boolean = false
|
|
83
|
+
> = Steps["length"] extends 350
|
|
84
|
+
? `${Acc}${ExtractBefore<S, " from ">}`
|
|
85
|
+
: InString extends true
|
|
86
|
+
? S extends `${infer C}${infer Rest}`
|
|
87
|
+
? C extends "'"
|
|
88
|
+
? ExtractBeforeFromTopLevel<Rest, Depth, false, `${Acc}${C}`, [any, ...Steps], InDString>
|
|
89
|
+
: ExtractBeforeFromTopLevel<Rest, Depth, InString, `${Acc}${C}`, [any, ...Steps], InDString>
|
|
90
|
+
: Acc
|
|
91
|
+
: InDString extends true
|
|
92
|
+
? S extends `${infer C}${infer Rest}`
|
|
93
|
+
? C extends `"`
|
|
94
|
+
? ExtractBeforeFromTopLevel<Rest, Depth, InString, `${Acc}${C}`, [any, ...Steps], false>
|
|
95
|
+
: ExtractBeforeFromTopLevel<Rest, Depth, InString, `${Acc}${C}`, [any, ...Steps], true>
|
|
96
|
+
: Acc
|
|
97
|
+
: Depth["length"] extends 0
|
|
98
|
+
? S extends ` from ${string}`
|
|
99
|
+
? Acc
|
|
100
|
+
: S extends `${infer C}${infer Rest}`
|
|
101
|
+
? C extends "'"
|
|
102
|
+
? ExtractBeforeFromTopLevel<Rest, Depth, true, `${Acc}${C}`, [any, ...Steps], InDString>
|
|
103
|
+
: C extends `"`
|
|
104
|
+
? ExtractBeforeFromTopLevel<Rest, Depth, InString, `${Acc}${C}`, [any, ...Steps], true>
|
|
105
|
+
: C extends "("
|
|
106
|
+
? ExtractBeforeFromTopLevel<Rest, [any, ...Depth], InString, `${Acc}${C}`, [any, ...Steps], InDString>
|
|
107
|
+
: C extends ")"
|
|
108
|
+
? ExtractBeforeFromTopLevel<Rest, Depth extends [any, ...infer D] ? D : [], InString, `${Acc}${C}`, [any, ...Steps], InDString>
|
|
109
|
+
: ExtractBeforeFromTopLevel<Rest, Depth, InString, `${Acc}${C}`, [any, ...Steps], InDString>
|
|
110
|
+
: Acc
|
|
111
|
+
: S extends `${infer C}${infer Rest}`
|
|
112
|
+
? C extends "'"
|
|
113
|
+
? ExtractBeforeFromTopLevel<Rest, Depth, true, `${Acc}${C}`, [any, ...Steps], InDString>
|
|
114
|
+
: C extends `"`
|
|
115
|
+
? ExtractBeforeFromTopLevel<Rest, Depth, InString, `${Acc}${C}`, [any, ...Steps], true>
|
|
116
|
+
: C extends "("
|
|
117
|
+
? ExtractBeforeFromTopLevel<Rest, [any, ...Depth], InString, `${Acc}${C}`, [any, ...Steps], InDString>
|
|
118
|
+
: C extends ")"
|
|
119
|
+
? ExtractBeforeFromTopLevel<Rest, Depth extends [any, ...infer D] ? D : [], InString, `${Acc}${C}`, [any, ...Steps], InDString>
|
|
120
|
+
: ExtractBeforeFromTopLevel<Rest, Depth, InString, `${Acc}${C}`, [any, ...Steps], InDString>
|
|
121
|
+
: Acc;
|
|
122
|
+
|
|
123
|
+
// Simple comma split (no paren awareness)
|
|
124
|
+
|
|
125
|
+
export type SplitCommaSimple<S extends string> = SplitTopLevel<S>;
|
|
126
|
+
|
|
127
|
+
// Extract before delimiter if present
|
|
128
|
+
|
|
129
|
+
export type ExtractBefore<S extends string, Delim extends string> =
|
|
130
|
+
S extends `${infer Head}${Delim}${infer _}` ? Head : S;
|
|
131
|
+
|
|
132
|
+
// Distinct
|
|
133
|
+
|
|
134
|
+
export type StripDistinct<S extends string> =
|
|
135
|
+
// `DISTINCT ON (cols)` — drop the whole ON-list so it can't leak into the
|
|
136
|
+
// projection. The list is parenthesised; keep everything after the `)`.
|
|
137
|
+
Trim<S> extends `distinct on (${infer _On})${infer Rest}` ? Trim<Rest> :
|
|
138
|
+
Trim<S> extends `distinct on(${infer _On})${infer Rest}` ? Trim<Rest> :
|
|
139
|
+
Trim<S> extends `distinct ${infer R}` ? R :
|
|
140
|
+
Trim<S> extends `all ${infer R}` ? R :
|
|
141
|
+
Trim<S>;
|
|
142
|
+
|
|
143
|
+
// Alias
|
|
144
|
+
|
|
145
|
+
// An implicit output alias written as a trailing double-quoted identifier with
|
|
146
|
+
// no `AS` keyword: `id "implicit id"`. Postgres accepts this. We only recognize
|
|
147
|
+
// the QUOTED form (a bare `expr alias` is too ambiguous to tell from
|
|
148
|
+
// compound/function syntax). The match requires a non-empty expression before
|
|
149
|
+
// the ` "..."` and an alias body with no embedded `"`.
|
|
150
|
+
export type IsImplicitQuotedAlias<E extends string> =
|
|
151
|
+
E extends `${infer Expr} "${infer Alias}"`
|
|
152
|
+
? Alias extends `${string}"${string}`
|
|
153
|
+
? false
|
|
154
|
+
: Trim<Expr> extends ""
|
|
155
|
+
? false
|
|
156
|
+
: true
|
|
157
|
+
: false;
|
|
158
|
+
|
|
159
|
+
// A bare implicit output alias written as `expr alias` with NO `AS` keyword and
|
|
160
|
+
// NO quotes: `id user_id`, `count(*) total`. Postgres accepts this. It is
|
|
161
|
+
// deliberately CONSERVATIVE — only recognized when:
|
|
162
|
+
// - `alias` (the last space-delimited token) is a simple identifier (no dot,
|
|
163
|
+
// quotes, operators, parens) and is NOT a SQL reserved word; AND
|
|
164
|
+
// - the remaining `expr` is EITHER a single simple column reference
|
|
165
|
+
// (unqualified or `a.b` qualified, no spaces/operators) OR a single
|
|
166
|
+
// function-call `fn(...)` with balanced parens and nothing after the `)`.
|
|
167
|
+
// Anything compound/ambiguous (operators, multiple tokens, CASE, etc.) is NOT
|
|
168
|
+
// treated as an implicit alias.
|
|
169
|
+
export type BareImplicitAliasParts<E extends string> =
|
|
170
|
+
Trim<E> extends `${infer Head} ${infer LastTok}`
|
|
171
|
+
? SplitLast<Trim<E>, " "> extends [infer HExpr extends string, infer HAlias extends string]
|
|
172
|
+
? { expr: Trim<HExpr>; alias: Trim<HAlias> }
|
|
173
|
+
: { expr: Trim<Head>; alias: Trim<LastTok> }
|
|
174
|
+
: { expr: Trim<E>; alias: "" };
|
|
175
|
+
|
|
176
|
+
export type IsBareImplicitAlias<E extends string> =
|
|
177
|
+
BareImplicitAliasParts<E> extends { expr: infer Expr extends string; alias: infer Alias extends string }
|
|
178
|
+
? Alias extends ""
|
|
179
|
+
? false
|
|
180
|
+
: IsSimpleAliasToken<Alias> extends true
|
|
181
|
+
? IsImplicitAliasExpr<Expr> extends true
|
|
182
|
+
? true
|
|
183
|
+
: false
|
|
184
|
+
: false
|
|
185
|
+
: false;
|
|
186
|
+
|
|
187
|
+
// The trailing token is a valid bare alias only when it is a plain identifier
|
|
188
|
+
// with no special chars/dots, and is not a SQL reserved word (so `as`, `from`,
|
|
189
|
+
// `where`, `order`, etc. never get mistaken for an alias).
|
|
190
|
+
export type IsSimpleAliasToken<A extends string> =
|
|
191
|
+
A extends "" ? false :
|
|
192
|
+
A extends `${string}.${string}` ? false :
|
|
193
|
+
A extends `${string}"${string}` ? false :
|
|
194
|
+
A extends `${string}'${string}` ? false :
|
|
195
|
+
A extends `${string}*${string}` ? false :
|
|
196
|
+
HasSpecial<A> extends true ? false :
|
|
197
|
+
CleanIdent<A> extends SqlReserved ? false :
|
|
198
|
+
true;
|
|
199
|
+
|
|
200
|
+
// The head expression of a bare implicit alias must be a SINGLE simple token:
|
|
201
|
+
// either a plain (possibly qualified `a.b`) column reference with no spaces or
|
|
202
|
+
// operators, OR a single function call `fn(...)` whose parens are balanced and
|
|
203
|
+
// with nothing trailing the closing `)`.
|
|
204
|
+
export type IsImplicitAliasExpr<Expr extends string> =
|
|
205
|
+
Trim<Expr> extends ""
|
|
206
|
+
? false
|
|
207
|
+
: Trim<Expr> extends `${infer Func}(${infer AfterOpen}`
|
|
208
|
+
? IsSimpleFuncName<Trim<Func>> extends true
|
|
209
|
+
? SplitBalancedParen<`(${AfterOpen}`> extends { rest: infer Rest extends string }
|
|
210
|
+
? Trim<Rest> extends ""
|
|
211
|
+
? true
|
|
212
|
+
: false
|
|
213
|
+
: false
|
|
214
|
+
: false
|
|
215
|
+
// Not a function call: must be a bare/qualified column ref — a single
|
|
216
|
+
// token with no spaces, operators, parens, or quotes.
|
|
217
|
+
: Trim<Expr> extends `${string} ${string}` ? false
|
|
218
|
+
: Trim<Expr> extends `${string}'${string}` ? false
|
|
219
|
+
: Trim<Expr> extends `${string}"${string}` ? false
|
|
220
|
+
: Trim<Expr> extends `${string}(${string}` ? false
|
|
221
|
+
: Trim<Expr> extends `${string})${string}` ? false
|
|
222
|
+
: Trim<Expr> extends `${string}+${string}` ? false
|
|
223
|
+
: Trim<Expr> extends `${string}-${string}` ? false
|
|
224
|
+
: Trim<Expr> extends `${string}*${string}` ? false
|
|
225
|
+
: Trim<Expr> extends `${string}/${string}` ? false
|
|
226
|
+
: Trim<Expr> extends `${string}=${string}` ? false
|
|
227
|
+
: Trim<Expr> extends `${string}<${string}` ? false
|
|
228
|
+
: Trim<Expr> extends `${string}>${string}` ? false
|
|
229
|
+
: Trim<Expr> extends `${string},${string}` ? false
|
|
230
|
+
: Trim<Expr> extends `${string}::${string}` ? false
|
|
231
|
+
: Trim<Expr> extends `${string}||${string}` ? false
|
|
232
|
+
: CleanIdent<Expr> extends SqlReserved ? false
|
|
233
|
+
: true;
|
|
234
|
+
|
|
235
|
+
// A function name preceding `(` must be a single simple identifier (`count`,
|
|
236
|
+
// `sum`, `coalesce`), not an operator-bearing expression.
|
|
237
|
+
export type IsSimpleFuncName<F extends string> =
|
|
238
|
+
F extends "" ? false :
|
|
239
|
+
F extends `${string} ${string}` ? false :
|
|
240
|
+
HasSpecial<F> extends true ? false :
|
|
241
|
+
true;
|
|
242
|
+
|
|
243
|
+
// A quoted identifier alias `"..."` may legally contain SQL punctuation,
|
|
244
|
+
// including `)`, `,`, etc. — those are part of the identifier, not structure.
|
|
245
|
+
export type IsQuotedIdentifier<S extends string> =
|
|
246
|
+
Trim<S> extends `"${string}"` ? true : false;
|
|
247
|
+
|
|
248
|
+
export type ExtractAlias<E extends string> =
|
|
249
|
+
SplitLast<Trim<E>, " as "> extends [infer Expr extends string, infer Alias extends string]
|
|
250
|
+
? Alias extends ""
|
|
251
|
+
? IsImplicitQuotedAlias<Trim<E>> extends true
|
|
252
|
+
? Trim<E> extends `${infer IExpr} "${infer IAlias}"`
|
|
253
|
+
? { expr: Trim<IExpr>; alias: CleanIdent<IAlias> }
|
|
254
|
+
: { expr: Trim<E>; alias: never }
|
|
255
|
+
: IsBareImplicitAlias<Trim<E>> extends true
|
|
256
|
+
? BareImplicitAliasParts<Trim<E>> extends { expr: infer BExpr extends string; alias: infer BAlias extends string }
|
|
257
|
+
? { expr: BExpr; alias: CleanIdent<BAlias> }
|
|
258
|
+
: { expr: Trim<E>; alias: never }
|
|
259
|
+
: { expr: Trim<E>; alias: never }
|
|
260
|
+
: IsQuotedIdentifier<Alias> extends true
|
|
261
|
+
? { expr: Trim<Expr>; alias: CleanIdent<Alias> }
|
|
262
|
+
: Alias extends `${string})${string}`
|
|
263
|
+
? { expr: Trim<E>; alias: never }
|
|
264
|
+
: { expr: Trim<Expr>; alias: CleanIdent<Alias> }
|
|
265
|
+
: { expr: Trim<E>; alias: never };
|
|
266
|
+
|
|
267
|
+
export type AliasResultKey<S extends string> =
|
|
268
|
+
Trim<S> extends `"${infer Q}"` ? Q : CleanIdent<S>;
|
|
269
|
+
|
|
270
|
+
export type ExtractAliasResult<E extends string> =
|
|
271
|
+
SplitLast<Trim<E>, " as "> extends [infer Expr extends string, infer Alias extends string]
|
|
272
|
+
? Alias extends ""
|
|
273
|
+
? IsImplicitQuotedAlias<Trim<E>> extends true
|
|
274
|
+
? Trim<E> extends `${infer IExpr} "${infer IAlias}"`
|
|
275
|
+
? { expr: Trim<IExpr>; alias: IAlias }
|
|
276
|
+
: { expr: Trim<E>; alias: never }
|
|
277
|
+
: IsBareImplicitAlias<Trim<E>> extends true
|
|
278
|
+
? BareImplicitAliasParts<Trim<E>> extends { expr: infer BExpr extends string; alias: infer BAlias extends string }
|
|
279
|
+
? { expr: BExpr; alias: AliasResultKey<BAlias> }
|
|
280
|
+
: { expr: Trim<E>; alias: never }
|
|
281
|
+
: { expr: Trim<E>; alias: never }
|
|
282
|
+
: IsQuotedIdentifier<Alias> extends true
|
|
283
|
+
? { expr: Trim<Expr>; alias: AliasResultKey<Alias> }
|
|
284
|
+
: Alias extends `${string})${string}`
|
|
285
|
+
? { expr: Trim<E>; alias: never }
|
|
286
|
+
: { expr: Trim<Expr>; alias: AliasResultKey<Alias> }
|
|
287
|
+
: { expr: Trim<E>; alias: never };
|
|
288
|
+
|