@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,43 @@
|
|
|
1
|
+
// src/builder/write-state.ts
|
|
2
|
+
import type { DriverParamValue } from "./scanner.js";
|
|
3
|
+
|
|
4
|
+
export interface InsertValueEntry { readonly col: string; readonly text: string; }
|
|
5
|
+
export interface RuntimeInsertState {
|
|
6
|
+
readonly table: string;
|
|
7
|
+
readonly values: ReadonlyArray<InsertValueEntry>;
|
|
8
|
+
// INSERT...SELECT form: explicit column list and a free-text SELECT body that
|
|
9
|
+
// replaces the VALUES clause. When `fromSelect` is set, the value fragments are
|
|
10
|
+
// ignored and the SELECT form is emitted instead (see assembleInsertSQL).
|
|
11
|
+
readonly columns?: string;
|
|
12
|
+
readonly fromSelect?: string;
|
|
13
|
+
readonly conflict?: string;
|
|
14
|
+
readonly returning?: string;
|
|
15
|
+
readonly namedParams: Record<string, DriverParamValue>;
|
|
16
|
+
}
|
|
17
|
+
export interface RuntimeUpdateState {
|
|
18
|
+
readonly table: string;
|
|
19
|
+
// Optional table alias, e.g. `update orders o set ...`. Emitted after the
|
|
20
|
+
// table name when present so aliased WHERE/SET references resolve.
|
|
21
|
+
readonly alias?: string;
|
|
22
|
+
// Optional CTEs prepended as a `with ... ` clause before `update`. The body
|
|
23
|
+
// text may contain `:params`; because it precedes the UPDATE in the final
|
|
24
|
+
// SQL, those params get the lowest positional numbers.
|
|
25
|
+
readonly ctes?: ReadonlyArray<{ name: string; body: string; materialized: boolean }>;
|
|
26
|
+
readonly sets: ReadonlyArray<string>;
|
|
27
|
+
readonly froms: ReadonlyArray<string>;
|
|
28
|
+
readonly wheres: ReadonlyArray<string>;
|
|
29
|
+
readonly returning?: string;
|
|
30
|
+
readonly namedParams: Record<string, DriverParamValue>;
|
|
31
|
+
}
|
|
32
|
+
export interface RuntimeDeleteState {
|
|
33
|
+
readonly table: string;
|
|
34
|
+
readonly usings: ReadonlyArray<string>;
|
|
35
|
+
readonly wheres: ReadonlyArray<string>;
|
|
36
|
+
readonly returning?: string;
|
|
37
|
+
readonly namedParams: Record<string, DriverParamValue>;
|
|
38
|
+
}
|
|
39
|
+
// `columns: ""` mirrors the type-level EmptyInsertTag default so a `.fromSelect(...)`
|
|
40
|
+
// reached without `.columns(...)` emits an honest `()` rather than `(undefined)`.
|
|
41
|
+
export const EMPTY_INSERT_STATE: RuntimeInsertState = { table: "", values: [], columns: "", namedParams: {} };
|
|
42
|
+
export const EMPTY_UPDATE_STATE: RuntimeUpdateState = { table: "", sets: [], froms: [], wheres: [], namedParams: {} };
|
|
43
|
+
export const EMPTY_DELETE_STATE: RuntimeDeleteState = { table: "", usings: [], wheres: [], namedParams: {} };
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// src/builder/write-tag.ts
|
|
2
|
+
import type { DatabaseSchema } from "../schema.js";
|
|
3
|
+
import type { ExtractParams, ExtractReturning } from "./extract-params.js";
|
|
4
|
+
|
|
5
|
+
export type WriteMode = "max" | "req";
|
|
6
|
+
|
|
7
|
+
export interface ValueFrag { readonly col: string; readonly text: string; readonly cond: boolean; }
|
|
8
|
+
export interface ClauseFrag { readonly text: string; readonly cond: boolean; }
|
|
9
|
+
|
|
10
|
+
export interface InsertTag {
|
|
11
|
+
readonly kind: "insert";
|
|
12
|
+
readonly table: string;
|
|
13
|
+
readonly values: readonly ValueFrag[];
|
|
14
|
+
// INSERT...SELECT: explicit column list + free-text SELECT body. Empty string
|
|
15
|
+
// ("") means "not set" — the VALUES form is rendered instead.
|
|
16
|
+
readonly columns: string;
|
|
17
|
+
readonly fromSelect: string;
|
|
18
|
+
readonly conflict: string | null;
|
|
19
|
+
readonly wheres: readonly ClauseFrag[]; // unused for insert; kept uniform
|
|
20
|
+
readonly using: readonly ClauseFrag[];
|
|
21
|
+
readonly from: readonly ClauseFrag[];
|
|
22
|
+
readonly returning: string | null;
|
|
23
|
+
}
|
|
24
|
+
export interface CteFrag { readonly name: string; readonly body: string; readonly materialized: boolean; }
|
|
25
|
+
export interface UpdateTag {
|
|
26
|
+
readonly kind: "update";
|
|
27
|
+
readonly table: string;
|
|
28
|
+
readonly alias: string;
|
|
29
|
+
readonly ctes: readonly CteFrag[];
|
|
30
|
+
readonly sets: readonly ClauseFrag[];
|
|
31
|
+
readonly from: readonly ClauseFrag[];
|
|
32
|
+
readonly wheres: readonly ClauseFrag[];
|
|
33
|
+
readonly returning: string | null;
|
|
34
|
+
}
|
|
35
|
+
export interface DeleteTag {
|
|
36
|
+
readonly kind: "delete";
|
|
37
|
+
readonly table: string;
|
|
38
|
+
readonly using: readonly ClauseFrag[];
|
|
39
|
+
readonly wheres: readonly ClauseFrag[];
|
|
40
|
+
readonly returning: string | null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// keep only the fragments live for a mode ("req" drops cond=true).
|
|
44
|
+
type ForMode<List extends readonly { cond: boolean }[], Mode extends WriteMode> =
|
|
45
|
+
Mode extends "max" ? List : DropCond<List>;
|
|
46
|
+
type DropCond<List extends readonly { cond: boolean }[]> =
|
|
47
|
+
List extends readonly [infer H extends { cond: boolean }, ...infer R extends readonly { cond: boolean }[]]
|
|
48
|
+
? H["cond"] extends true ? DropCond<R> : readonly [H, ...DropCond<R>]
|
|
49
|
+
: readonly [];
|
|
50
|
+
|
|
51
|
+
type ColList<List extends readonly ValueFrag[], Acc extends string = ""> =
|
|
52
|
+
List extends readonly [infer H extends ValueFrag, ...infer R extends readonly ValueFrag[]]
|
|
53
|
+
? ColList<R, Acc extends "" ? H["col"] : `${Acc}, ${H["col"]}`> : Acc;
|
|
54
|
+
type ValList<List extends readonly ValueFrag[], Acc extends string = ""> =
|
|
55
|
+
List extends readonly [infer H extends ValueFrag, ...infer R extends readonly ValueFrag[]]
|
|
56
|
+
? ValList<R, Acc extends "" ? H["text"] : `${Acc}, ${H["text"]}`> : Acc;
|
|
57
|
+
type JoinText<List extends readonly { text: string }[], Sep extends string, Acc extends string = ""> =
|
|
58
|
+
List extends readonly [infer H extends { text: string }, ...infer R extends readonly { text: string }[]]
|
|
59
|
+
? JoinText<R, Sep, Acc extends "" ? H["text"] : `${Acc}${Sep}${H["text"]}`> : Acc;
|
|
60
|
+
|
|
61
|
+
type Conflict<C extends string | null> = C extends string ? ` on conflict ${C}` : "";
|
|
62
|
+
type Returning<R extends string | null> = R extends string ? ` returning ${R}` : "";
|
|
63
|
+
type FromClause<L extends readonly ClauseFrag[]> = L extends readonly [] ? "" : ` from ${JoinText<L, ", ">}`;
|
|
64
|
+
type UsingClause<L extends readonly ClauseFrag[]> = L extends readonly [] ? "" : ` using ${JoinText<L, ", ">}`;
|
|
65
|
+
type WhereClause<L extends readonly ClauseFrag[]> = L extends readonly [] ? "" : ` where ${JoinText<L, " and ">}`;
|
|
66
|
+
|
|
67
|
+
// INSERT...SELECT form: `insert into <table> (<columns>) <fromSelect>[ on conflict
|
|
68
|
+
// <c>][ returning <r>]`. The fromSelect text carries its own `:params`, which
|
|
69
|
+
// ExtractParams scans positionally from the built string.
|
|
70
|
+
export type BuildInsertSelectSQL<T extends InsertTag> =
|
|
71
|
+
`insert into ${T["table"]} (${T["columns"]}) ${T["fromSelect"]}${Conflict<T["conflict"]>}${Returning<T["returning"]>}`;
|
|
72
|
+
|
|
73
|
+
export type BuildInsertSQL<T extends InsertTag, Mode extends WriteMode> =
|
|
74
|
+
// Branch on whether a SELECT body was supplied (non-empty fromSelect).
|
|
75
|
+
T["fromSelect"] extends "" ? BuildInsertValuesSQL<T, Mode> : BuildInsertSelectSQL<T>;
|
|
76
|
+
|
|
77
|
+
// Original VALUES rendering, used when no fromSelect is present.
|
|
78
|
+
type BuildInsertValuesSQL<T extends InsertTag, Mode extends WriteMode> =
|
|
79
|
+
ForMode<T["values"], Mode> extends infer V extends readonly ValueFrag[]
|
|
80
|
+
? `insert into ${T["table"]} (${ColList<V>}) values (${ValList<V>})${Conflict<T["conflict"]>}${Returning<T["returning"]>}`
|
|
81
|
+
: never;
|
|
82
|
+
|
|
83
|
+
// Head is `table` alone, or `table alias` when an alias was supplied.
|
|
84
|
+
type UpdateHead<T extends UpdateTag> = T["alias"] extends "" ? T["table"] : `${T["table"]} ${T["alias"]}`;
|
|
85
|
+
|
|
86
|
+
// Render one CTE: `name as [materialized ](body)`.
|
|
87
|
+
type RenderCte<C extends CteFrag> = `${C["name"]} as ${C["materialized"] extends true ? "materialized " : ""}(${C["body"]})`;
|
|
88
|
+
// Join CTEs with ", " (comma-separated), mirroring JoinText but per-CTE rendered.
|
|
89
|
+
type JoinCtes<L extends readonly CteFrag[], Acc extends string = ""> =
|
|
90
|
+
L extends readonly [infer H extends CteFrag, ...infer R extends readonly CteFrag[]]
|
|
91
|
+
? JoinCtes<R, Acc extends "" ? RenderCte<H> : `${Acc}, ${RenderCte<H>}`> : Acc;
|
|
92
|
+
// Leading `with ... ` clause (trailing space) when CTEs exist, else empty. The
|
|
93
|
+
// body text is embedded so ExtractParams picks up its `:params` ahead of the rest.
|
|
94
|
+
type WithClause<L extends readonly CteFrag[]> = L extends readonly [] ? "" : `with ${JoinCtes<L>} `;
|
|
95
|
+
|
|
96
|
+
export type BuildUpdateSQL<T extends UpdateTag, Mode extends WriteMode> =
|
|
97
|
+
`${WithClause<T["ctes"]>}update ${UpdateHead<T>} set ${JoinText<ForMode<T["sets"], Mode>, ", ">}${FromClause<ForMode<T["from"], Mode>>}${WhereClause<ForMode<T["wheres"], Mode>>}${Returning<T["returning"]>}`;
|
|
98
|
+
|
|
99
|
+
export type BuildDeleteSQL<T extends DeleteTag, Mode extends WriteMode> =
|
|
100
|
+
`delete from ${T["table"]}${UsingClause<ForMode<T["using"], Mode>>}${WhereClause<ForMode<T["wheres"], Mode>>}${Returning<T["returning"]>}`;
|
|
101
|
+
|
|
102
|
+
type BuildSQL<T, Mode extends WriteMode> =
|
|
103
|
+
T extends InsertTag ? BuildInsertSQL<T, Mode>
|
|
104
|
+
: T extends UpdateTag ? BuildUpdateSQL<T, Mode>
|
|
105
|
+
: T extends DeleteTag ? BuildDeleteSQL<T, Mode>
|
|
106
|
+
: never;
|
|
107
|
+
|
|
108
|
+
// Required iff present in the req-mode params; value type taken from max-mode
|
|
109
|
+
// (full §6.2 intersection over all occurrences). Mirrors return-type.ts Partition.
|
|
110
|
+
type Partition<Max, Req> =
|
|
111
|
+
& { [K in keyof Max as K extends keyof Req ? K : never]: Max[K] }
|
|
112
|
+
& { [K in keyof Max as K extends keyof Req ? never : K]?: Max[K] };
|
|
113
|
+
|
|
114
|
+
export type WriteParamsFor<T, S extends DatabaseSchema> =
|
|
115
|
+
Partition<ExtractParams<BuildSQL<T, "max">, S>, ExtractParams<BuildSQL<T, "req">, S>> extends infer P
|
|
116
|
+
? { [K in keyof P]: P[K] } : never;
|
|
117
|
+
|
|
118
|
+
export type WriteReturnFor<T, S extends DatabaseSchema> =
|
|
119
|
+
ExtractReturning<BuildSQL<T, "max"> & string, S>;
|
package/src/columns.ts
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DatabaseSchema,
|
|
3
|
+
ColumnExists,
|
|
4
|
+
ColumnExistsInAnyTable,
|
|
5
|
+
ResolveColumnName,
|
|
6
|
+
TableExists
|
|
7
|
+
} from "./schema.js";
|
|
8
|
+
import type { AliasesInQuery, AliasNames, IsAliasName, TableKeyFromToken, TableKeyValid, TablesInQuery } from "./tables.js";
|
|
9
|
+
import type {
|
|
10
|
+
CanPrecedeColumn,
|
|
11
|
+
CleanExpr,
|
|
12
|
+
CleanIdent,
|
|
13
|
+
HasSpecial,
|
|
14
|
+
IsParamPlaceholder,
|
|
15
|
+
IsQualifiedRefCandidate,
|
|
16
|
+
IsRuntimeStringFragment,
|
|
17
|
+
IsSqlConstant,
|
|
18
|
+
OperatorToken,
|
|
19
|
+
ReplaceAll,
|
|
20
|
+
SqlReserved,
|
|
21
|
+
SplitOnDotClean
|
|
22
|
+
} from "./parsing.js";
|
|
23
|
+
import type { IsNever } from "./utils.js";
|
|
24
|
+
|
|
25
|
+
// Column references
|
|
26
|
+
|
|
27
|
+
export type ColumnRef<TableKey extends string, Column extends string> = {
|
|
28
|
+
tableKey: TableKey;
|
|
29
|
+
column: Column;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type BuildColumnRef<
|
|
33
|
+
TableKey extends string,
|
|
34
|
+
Column extends string,
|
|
35
|
+
S extends DatabaseSchema
|
|
36
|
+
> =
|
|
37
|
+
ResolveColumnName<TableKey, Column, S> extends infer Resolved extends string
|
|
38
|
+
? ColumnRef<TableKey, Resolved>
|
|
39
|
+
: never;
|
|
40
|
+
|
|
41
|
+
export type StripDoubleQuotes<S extends string> = ReplaceAll<S, `"`, "">;
|
|
42
|
+
|
|
43
|
+
// Parse column references from expressions
|
|
44
|
+
// - supports schema.table.column, table.column, and column
|
|
45
|
+
|
|
46
|
+
// A qualified ref whose table/alias prefix is a double-quoted identifier
|
|
47
|
+
// containing operator punctuation (`"u-1".id`) breaks the generic path: stripping
|
|
48
|
+
// the quotes yields `u-1`, which `IsSimpleRefPart`/`HasSpecial` reject as an
|
|
49
|
+
// arithmetic-looking token, so the ref collapses to `never` and the projected key
|
|
50
|
+
// is lost. A double-quoted identifier is by definition a single opaque name, so
|
|
51
|
+
// when the prefix carries punctuation we resolve it directly (alias/table lookup)
|
|
52
|
+
// without the special-char gate. The no-punctuation case (`"u1".id`) stays on the
|
|
53
|
+
// proven generic path; the column part must be a plain name (no dot/quote/star).
|
|
54
|
+
export type ParseColumnRef<
|
|
55
|
+
Expr extends string,
|
|
56
|
+
Tables extends string,
|
|
57
|
+
Aliases extends string,
|
|
58
|
+
S extends DatabaseSchema
|
|
59
|
+
> =
|
|
60
|
+
CleanExpr<Expr> extends `"${infer P}".${infer Col}`
|
|
61
|
+
? IsQuotedPunctPrefix<P, Col> extends true
|
|
62
|
+
? ResolveQuotedPrefixRef<CleanIdent<P>, CleanIdent<Col>, Tables, Aliases, S>
|
|
63
|
+
: ParseColumnRefGeneric<Expr, Tables, Aliases, S>
|
|
64
|
+
: ParseColumnRefGeneric<Expr, Tables, Aliases, S>;
|
|
65
|
+
|
|
66
|
+
export type IsQuotedPunctPrefix<P extends string, Col extends string> =
|
|
67
|
+
Col extends `${string}.${string}` ? false :
|
|
68
|
+
Col extends `${string}"${string}` ? false :
|
|
69
|
+
Col extends `${string}*${string}` ? false :
|
|
70
|
+
HasSpecial<P> extends true ? true :
|
|
71
|
+
false;
|
|
72
|
+
|
|
73
|
+
export type ResolveQuotedPrefixRef<
|
|
74
|
+
P extends string,
|
|
75
|
+
Col extends string,
|
|
76
|
+
Tables extends string,
|
|
77
|
+
Aliases extends string,
|
|
78
|
+
S extends DatabaseSchema
|
|
79
|
+
> =
|
|
80
|
+
[ResolveTableKey<P, Tables, Aliases, S>] extends [infer TK extends string]
|
|
81
|
+
? [TK] extends [never]
|
|
82
|
+
? [ResolveTableKeyForUnqualified<Tables, Aliases, S, Col>] extends [infer TKF extends string]
|
|
83
|
+
? [TKF] extends [never]
|
|
84
|
+
? never
|
|
85
|
+
: BuildColumnRef<TKF, Col, S>
|
|
86
|
+
: never
|
|
87
|
+
: BuildColumnRef<TK, Col, S>
|
|
88
|
+
: never;
|
|
89
|
+
|
|
90
|
+
export type ParseColumnRefGeneric<
|
|
91
|
+
Expr extends string,
|
|
92
|
+
Tables extends string,
|
|
93
|
+
Aliases extends string,
|
|
94
|
+
S extends DatabaseSchema
|
|
95
|
+
> =
|
|
96
|
+
SplitOnDotClean<StripDoubleQuotes<CleanExpr<Expr>>> extends [infer A extends string, infer B extends string, infer C extends string]
|
|
97
|
+
? IsSimpleRefPart<A> extends true
|
|
98
|
+
? IsSimpleRefPart<B> extends true
|
|
99
|
+
? IsSimpleRefPart<C> extends true
|
|
100
|
+
? BuildColumnRef<`${A}.${B}`, C, S>
|
|
101
|
+
: never
|
|
102
|
+
: never
|
|
103
|
+
: never
|
|
104
|
+
: SplitOnDotClean<StripDoubleQuotes<CleanExpr<Expr>>> extends [infer A extends string, infer B extends string]
|
|
105
|
+
? IsSimpleRefPart<A> extends true
|
|
106
|
+
? IsSimpleRefPart<B> extends true
|
|
107
|
+
? IsRuntimeStringFragment<A> extends true
|
|
108
|
+
? [ResolveTableKeyForUnqualified<Tables, Aliases, S, B>] extends [infer TKRuntime extends string]
|
|
109
|
+
? [TKRuntime] extends [never]
|
|
110
|
+
? never
|
|
111
|
+
: BuildColumnRef<TKRuntime, B, S>
|
|
112
|
+
: never
|
|
113
|
+
: CleanIdent<A> extends "excluded"
|
|
114
|
+
? [ResolveTableKeyForUnqualified<Tables, Aliases, S, B>] extends [infer TKExcluded extends string]
|
|
115
|
+
? [TKExcluded] extends [never]
|
|
116
|
+
? never
|
|
117
|
+
: BuildColumnRef<TKExcluded, B, S>
|
|
118
|
+
: never
|
|
119
|
+
: [ResolveTableKey<A, Tables, Aliases, S>] extends [infer TK extends string]
|
|
120
|
+
? [TK] extends [never]
|
|
121
|
+
? [ResolveTableKeyForUnqualified<Tables, Aliases, S, B>] extends [infer TKFallback extends string]
|
|
122
|
+
? [TKFallback] extends [never]
|
|
123
|
+
? never
|
|
124
|
+
: BuildColumnRef<TKFallback, B, S>
|
|
125
|
+
: never
|
|
126
|
+
: BuildColumnRef<TK, B, S>
|
|
127
|
+
: never
|
|
128
|
+
: never
|
|
129
|
+
: never
|
|
130
|
+
: SplitOnDotClean<StripDoubleQuotes<CleanExpr<Expr>>> extends [infer A extends string]
|
|
131
|
+
? IsSimpleRefPart<A> extends true
|
|
132
|
+
? [ResolveTableKeyForUnqualified<Tables, Aliases, S, A>] extends [infer TK2 extends string]
|
|
133
|
+
? [TK2] extends [never]
|
|
134
|
+
? never
|
|
135
|
+
: BuildColumnRef<TK2, A, S>
|
|
136
|
+
: never
|
|
137
|
+
: never
|
|
138
|
+
: never;
|
|
139
|
+
|
|
140
|
+
export type IsSimpleRefPart<S extends string> =
|
|
141
|
+
S extends "" ? false :
|
|
142
|
+
HasSpecial<S> extends true ? false :
|
|
143
|
+
true;
|
|
144
|
+
|
|
145
|
+
// Validate a column reference string like a.b or a.b.c
|
|
146
|
+
|
|
147
|
+
export type ColumnRefValid<ColRef extends string, N extends string, S extends DatabaseSchema> =
|
|
148
|
+
TablesInQuery<N, S> extends infer Tables extends string
|
|
149
|
+
? AliasesInQuery<N, S> extends infer Aliases extends string
|
|
150
|
+
? ColumnRefValidWith<ColRef, Tables, Aliases, S>
|
|
151
|
+
: true
|
|
152
|
+
: true;
|
|
153
|
+
|
|
154
|
+
export type ColumnRefValidWith<
|
|
155
|
+
ColRef extends string,
|
|
156
|
+
Tables extends string,
|
|
157
|
+
Aliases extends string,
|
|
158
|
+
S extends DatabaseSchema
|
|
159
|
+
> =
|
|
160
|
+
ColRef extends `${infer Prefix}.*`
|
|
161
|
+
? [ResolveAlias<CleanIdent<Prefix>, Aliases>] extends [infer AliasKey extends string]
|
|
162
|
+
? [AliasKey] extends [never]
|
|
163
|
+
? TableKeysByName<CleanIdent<Prefix>, Tables> extends infer TableKey extends string
|
|
164
|
+
? [TableKey] extends [never]
|
|
165
|
+
? CleanIdent<Prefix> extends `${infer Schema}.${infer Table}`
|
|
166
|
+
? TableExists<S, Schema, Table> extends true
|
|
167
|
+
? TableKeyValid<`${Schema}.${Table}`, S>
|
|
168
|
+
: false
|
|
169
|
+
: false
|
|
170
|
+
: TableKeyValid<TableKey, S>
|
|
171
|
+
: false
|
|
172
|
+
: TableKeyValid<AliasKey, S>
|
|
173
|
+
: false
|
|
174
|
+
: ParseColumnRef<ColRef, Tables, Aliases, S> extends infer Ref
|
|
175
|
+
? [Ref] extends [never]
|
|
176
|
+
? true
|
|
177
|
+
: [Ref] extends [ColumnRef<infer TableKey extends string, infer Column extends string>]
|
|
178
|
+
? ColumnExists<TableKey, Column, S>
|
|
179
|
+
: true
|
|
180
|
+
: true;
|
|
181
|
+
|
|
182
|
+
export type ColumnRefValidLoose<ColRef extends string, N extends string, S extends DatabaseSchema> =
|
|
183
|
+
TablesInQuery<N, S> extends infer Tables extends string
|
|
184
|
+
? AliasesInQuery<N, S> extends infer Aliases extends string
|
|
185
|
+
? ColumnRefValidLooseWith<ColRef, Tables, Aliases, S>
|
|
186
|
+
: ColumnRefValid<ColRef, N, S>
|
|
187
|
+
: ColumnRefValid<ColRef, N, S>;
|
|
188
|
+
|
|
189
|
+
export type ColumnRefValidLooseWith<
|
|
190
|
+
ColRef extends string,
|
|
191
|
+
Tables extends string,
|
|
192
|
+
Aliases extends string,
|
|
193
|
+
S extends DatabaseSchema
|
|
194
|
+
> = IsNever<Tables> extends true
|
|
195
|
+
? ColumnRefValidNoTables<ColRef, S>
|
|
196
|
+
: ColumnRefValidWith<ColRef, Tables, Aliases, S>;
|
|
197
|
+
|
|
198
|
+
export type ColumnRefValidNoTables<ColRef extends string, S extends DatabaseSchema> =
|
|
199
|
+
SplitOnDotClean<ColRef> extends [infer A extends string, infer B extends string, infer C extends string]
|
|
200
|
+
? TableExists<S, A, B> extends true
|
|
201
|
+
? ColumnExists<`${A}.${B}`, C, S>
|
|
202
|
+
: false
|
|
203
|
+
: SplitOnDotClean<ColRef> extends [infer A extends string, infer B extends string]
|
|
204
|
+
? TableExists<S, S["defaultSchema"], A> extends true
|
|
205
|
+
? ColumnExists<`${S["defaultSchema"]}.${A}`, B, S>
|
|
206
|
+
: ColumnExistsInAnyTable<B, S>
|
|
207
|
+
: ColumnExistsInAnyTable<CleanIdent<ColRef>, S>;
|
|
208
|
+
|
|
209
|
+
// Best-effort qualified column references across the query
|
|
210
|
+
|
|
211
|
+
export type QualifiedColumnRefs<
|
|
212
|
+
Tokens extends string[],
|
|
213
|
+
S extends DatabaseSchema,
|
|
214
|
+
Tables extends string,
|
|
215
|
+
Aliases extends string,
|
|
216
|
+
Acc extends string = never,
|
|
217
|
+
Prev extends string = "",
|
|
218
|
+
Steps extends any[] = []
|
|
219
|
+
> = Steps["length"] extends 900
|
|
220
|
+
? Acc
|
|
221
|
+
: Tokens extends [infer T extends string, ...infer Rest extends string[]]
|
|
222
|
+
? T extends `${string}.${string}`
|
|
223
|
+
? Prev extends "from" | "join" | "update" | "into" | "delete"
|
|
224
|
+
? QualifiedColumnRefs<Rest, S, Tables, Aliases, Acc, T, [any, ...Steps]>
|
|
225
|
+
: IsQualifiedRefCandidate<T> extends true
|
|
226
|
+
? QualifiedColumnRefs<Rest, S, Tables, Aliases, Acc | T, T, [any, ...Steps]>
|
|
227
|
+
: QualifiedColumnRefs<Rest, S, Tables, Aliases, Acc, T, [any, ...Steps]>
|
|
228
|
+
: QualifiedColumnRefs<Rest, S, Tables, Aliases, Acc, T, [any, ...Steps]>
|
|
229
|
+
: Acc;
|
|
230
|
+
|
|
231
|
+
// Best-effort unqualified column references across the query
|
|
232
|
+
|
|
233
|
+
export type UnqualifiedColumnRefs<
|
|
234
|
+
Tokens extends string[],
|
|
235
|
+
S extends DatabaseSchema,
|
|
236
|
+
Tables extends string,
|
|
237
|
+
Aliases extends string,
|
|
238
|
+
Acc extends string = never,
|
|
239
|
+
Prev extends string = "",
|
|
240
|
+
Steps extends any[] = []
|
|
241
|
+
> = Steps["length"] extends 900
|
|
242
|
+
? Acc
|
|
243
|
+
: Tokens extends [infer T extends string, infer Next extends string, ...infer Rest extends string[]]
|
|
244
|
+
? IsUnqualifiedColumnCandidate<T, Prev, Next, Tables, Aliases, S> extends true
|
|
245
|
+
? UnqualifiedColumnRefs<[Next, ...Rest], S, Tables, Aliases, Acc | T, T, [any, ...Steps]>
|
|
246
|
+
: UnqualifiedColumnRefs<[Next, ...Rest], S, Tables, Aliases, Acc, T, [any, ...Steps]>
|
|
247
|
+
: Tokens extends [infer T extends string]
|
|
248
|
+
? IsUnqualifiedColumnCandidate<T, Prev, "", Tables, Aliases, S> extends true
|
|
249
|
+
? Acc | T
|
|
250
|
+
: Acc
|
|
251
|
+
: Acc;
|
|
252
|
+
|
|
253
|
+
export type UnqualifiedColumnValid<
|
|
254
|
+
Col extends string,
|
|
255
|
+
Tables extends string,
|
|
256
|
+
Aliases extends string,
|
|
257
|
+
S extends DatabaseSchema
|
|
258
|
+
> = IsNever<Tables> extends true
|
|
259
|
+
? ColumnExistsInAnyTable<CleanIdent<Col>, S>
|
|
260
|
+
: [ResolveTableKeyForUnqualified<Tables, Aliases, S, CleanIdent<Col>>] extends [never]
|
|
261
|
+
? false
|
|
262
|
+
: true;
|
|
263
|
+
|
|
264
|
+
export type IsUnqualifiedColumnCandidate<
|
|
265
|
+
Token extends string,
|
|
266
|
+
Prev extends string,
|
|
267
|
+
Next extends string,
|
|
268
|
+
Tables extends string,
|
|
269
|
+
Aliases extends string,
|
|
270
|
+
S extends DatabaseSchema
|
|
271
|
+
> =
|
|
272
|
+
Token extends "" ? false :
|
|
273
|
+
IsRuntimeStringFragment<Token> extends true ? false :
|
|
274
|
+
Token extends OperatorToken ? false :
|
|
275
|
+
Token extends `${string}.${string}` ? false :
|
|
276
|
+
Token extends `'${string}'` ? false :
|
|
277
|
+
Token extends `${string}'${string}` ? false :
|
|
278
|
+
Token extends `${number}` ? false :
|
|
279
|
+
IsParamPlaceholder<Token> extends true ? false :
|
|
280
|
+
IsSqlConstant<Token> extends true ? false :
|
|
281
|
+
HasSpecial<Token> extends true ? false :
|
|
282
|
+
CleanIdent<Token> extends SqlReserved ? false :
|
|
283
|
+
IsAliasName<Token, Aliases> extends true ? false :
|
|
284
|
+
Prev extends "as" ? false :
|
|
285
|
+
Prev extends "from" | "join" | "update" | "into" | "delete" ? false :
|
|
286
|
+
Next extends "(" ? false :
|
|
287
|
+
// A bareword immediately followed by a string literal is a PostgreSQL typed
|
|
288
|
+
// string literal's type prefix (`DATE '...'`, `TIMESTAMP '...'`), never a
|
|
289
|
+
// column — a real column is never directly adjacent to a quote (round-12 L1/L2).
|
|
290
|
+
Next extends `'${string}` ? false :
|
|
291
|
+
// A table name following a comma is another FROM source in the comma
|
|
292
|
+
// cross-join `from a, b` (the `,` plays the role `join` does), not a column.
|
|
293
|
+
Prev extends "," ? (IsTableName<Token, S> extends true ? false : CanPrecedeColumn<Prev>) :
|
|
294
|
+
IsNever<Tables> extends true
|
|
295
|
+
? IsTableName<Token, S> extends true
|
|
296
|
+
? false
|
|
297
|
+
: CanPrecedeColumn<Prev>
|
|
298
|
+
: CanPrecedeColumn<Prev>;
|
|
299
|
+
|
|
300
|
+
export type IsTableName<Token extends string, S extends DatabaseSchema> =
|
|
301
|
+
TableKeyFromToken<Token, S> extends infer TableKey extends string
|
|
302
|
+
? TableKeyValid<TableKey, S>
|
|
303
|
+
: false;
|
|
304
|
+
|
|
305
|
+
// Utilities for table/column resolution
|
|
306
|
+
|
|
307
|
+
export type ResolveTableKey<Name extends string, Tables extends string, Aliases extends string, S extends DatabaseSchema> =
|
|
308
|
+
ResolveAlias<Name, Aliases> | TableKeysByName<Name, Tables> | `${S["defaultSchema"]}.${Name}`;
|
|
309
|
+
|
|
310
|
+
export type ResolveTableKeyForUnqualified<
|
|
311
|
+
Tables extends string,
|
|
312
|
+
Aliases extends string,
|
|
313
|
+
S extends DatabaseSchema,
|
|
314
|
+
Column extends string
|
|
315
|
+
> = TablesWithColumn<Tables, Column, S> extends infer TK extends string ? TK : never;
|
|
316
|
+
|
|
317
|
+
export type ResolveAlias<Name extends string, Aliases extends string> =
|
|
318
|
+
Aliases extends `${infer A}=>${infer T}`
|
|
319
|
+
? A extends Name
|
|
320
|
+
? T
|
|
321
|
+
: never
|
|
322
|
+
: never;
|
|
323
|
+
|
|
324
|
+
export type TableKeysByName<Name extends string, Tables extends string> =
|
|
325
|
+
Tables extends `${infer Schema}.${infer Table}`
|
|
326
|
+
? Table extends Name
|
|
327
|
+
? `${Schema}.${Table}`
|
|
328
|
+
: never
|
|
329
|
+
: never;
|
|
330
|
+
|
|
331
|
+
export type TablesWithColumn<Tables extends string, Column extends string, S extends DatabaseSchema> =
|
|
332
|
+
Tables extends `${infer Schema}.${infer Table}`
|
|
333
|
+
? ColumnExists<`${Schema}.${Table}`, Column, S> extends true
|
|
334
|
+
? `${Schema}.${Table}`
|
|
335
|
+
: never
|
|
336
|
+
: never;
|