@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,470 @@
|
|
|
1
|
+
// src/builder/select.ts
|
|
2
|
+
import type { DatabaseSchema } from "../schema.js";
|
|
3
|
+
import { assembleSelectSQL } from "./assemble.js";
|
|
4
|
+
import { collectParamValues, type QueryParamInput, type QueryParamValue } from "./params.js";
|
|
5
|
+
import { EMPTY_RUNTIME_STATE, type RuntimeSelectState } from "./state.js";
|
|
6
|
+
import { ConditionTreeBuilder } from "./condition-tree.js";
|
|
7
|
+
import type {
|
|
8
|
+
EmptySqlTag,
|
|
9
|
+
ResolveId,
|
|
10
|
+
SelFrag,
|
|
11
|
+
SqlTag,
|
|
12
|
+
WithFrom,
|
|
13
|
+
WithGroupBy,
|
|
14
|
+
WithHaving,
|
|
15
|
+
WithJoin,
|
|
16
|
+
WithLimit,
|
|
17
|
+
WithOffset,
|
|
18
|
+
WithOrderBy,
|
|
19
|
+
WithSelect,
|
|
20
|
+
WithWhere,
|
|
21
|
+
WithoutGroupBy,
|
|
22
|
+
WithoutHaving,
|
|
23
|
+
WithoutJoin,
|
|
24
|
+
WithoutOrderBy,
|
|
25
|
+
WithoutSelect,
|
|
26
|
+
WithoutWhere,
|
|
27
|
+
} from "./sql-tag.js";
|
|
28
|
+
import type { BuilderResultBrand } from "./return-type.js";
|
|
29
|
+
|
|
30
|
+
// Text a condition contributes to the tag: a tree's Expr literal, or the string.
|
|
31
|
+
type CondText<C> = C extends ConditionTreeBuilder<any, infer E extends string> ? E
|
|
32
|
+
: C extends string ? C
|
|
33
|
+
: string;
|
|
34
|
+
|
|
35
|
+
// Render a columns argument (string | readonly string[]) to its joined text.
|
|
36
|
+
type ColsText<Cols> = Cols extends readonly string[] ? JoinArr<Cols>
|
|
37
|
+
: Cols extends string ? Cols
|
|
38
|
+
: string;
|
|
39
|
+
type JoinArr<A extends readonly string[], Acc extends string = ""> =
|
|
40
|
+
A extends readonly [infer H extends string, ...infer R extends readonly string[]]
|
|
41
|
+
? JoinArr<R, Acc extends "" ? H : `${Acc}, ${H}`>
|
|
42
|
+
: Acc;
|
|
43
|
+
|
|
44
|
+
// Re-flag every select fragment the applyIf transform INTRODUCED as conditional.
|
|
45
|
+
// "Introduced" = a brand-new id OR an existing id whose fragment the transform
|
|
46
|
+
// overwrote (different text/flag). Only fragments byte-identical to the input
|
|
47
|
+
// tag's same-id fragment are left untouched (they keep their flag). This is what
|
|
48
|
+
// makes the F-G2 edge correct: a conditional producer overwriting an
|
|
49
|
+
// unconditional slot carries the conditional flag (spec "Fragment-id reuse").
|
|
50
|
+
// Flat 11-field rebuild (NOT `Omit<After,"selects"> & {…}`) — see the DEPTH NOTE
|
|
51
|
+
// in sql-tag.ts: an `Omit`-based override nests on every chained builder call and
|
|
52
|
+
// crosses TS's depth guard.
|
|
53
|
+
type FlagNewConditional<Before extends SqlTag, After extends SqlTag> = {
|
|
54
|
+
readonly ctes: After["ctes"];
|
|
55
|
+
readonly selects: ReflagSelects<Before["selects"], After["selects"]>;
|
|
56
|
+
readonly from: After["from"];
|
|
57
|
+
readonly joins: After["joins"];
|
|
58
|
+
readonly wheres: After["wheres"];
|
|
59
|
+
readonly groupBys: After["groupBys"];
|
|
60
|
+
readonly havings: After["havings"];
|
|
61
|
+
readonly orderBys: After["orderBys"];
|
|
62
|
+
readonly limit: After["limit"];
|
|
63
|
+
readonly offset: After["offset"];
|
|
64
|
+
readonly union: After["union"];
|
|
65
|
+
};
|
|
66
|
+
type ReflagSelects<
|
|
67
|
+
Before extends readonly SelFrag[],
|
|
68
|
+
After extends readonly SelFrag[],
|
|
69
|
+
> = {
|
|
70
|
+
[I in keyof After]: After[I] extends SelFrag
|
|
71
|
+
? FindFragById<Before, After[I]["id"]> extends infer B
|
|
72
|
+
? [B] extends [never]
|
|
73
|
+
? MarkCond<After[I]> // brand-new id → conditional
|
|
74
|
+
: FragEqual<B, After[I]> extends true
|
|
75
|
+
? After[I] // untouched → keep its flag
|
|
76
|
+
: MarkCond<After[I]> // overwritten by transform → conditional
|
|
77
|
+
: MarkCond<After[I]>
|
|
78
|
+
: After[I];
|
|
79
|
+
};
|
|
80
|
+
type FindFragById<List extends readonly SelFrag[], Id extends string> =
|
|
81
|
+
List extends readonly [infer H extends SelFrag, ...infer R extends readonly SelFrag[]]
|
|
82
|
+
? H["id"] extends Id ? H : FindFragById<R, Id>
|
|
83
|
+
: never;
|
|
84
|
+
type FragEqual<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false;
|
|
85
|
+
type MarkCond<F extends SelFrag> = { id: F["id"]; text: F["text"]; cond: true };
|
|
86
|
+
|
|
87
|
+
export interface SelectQueryBuilder<Schema extends DatabaseSchema, Sql extends SqlTag> {
|
|
88
|
+
select<const Cols extends string | readonly string[], Id extends string | undefined = undefined>(
|
|
89
|
+
columns: Cols,
|
|
90
|
+
id?: Id,
|
|
91
|
+
): SelectQueryBuilder<Schema, WithSelect<Sql, ColsText<Cols>, ResolveId<Id, "select", Sql["selects"]>, false>>;
|
|
92
|
+
|
|
93
|
+
selectIf<const Cols extends string | readonly string[], Id extends string | undefined = undefined>(
|
|
94
|
+
condition: boolean,
|
|
95
|
+
columns: Cols,
|
|
96
|
+
id?: Id,
|
|
97
|
+
): SelectQueryBuilder<Schema, WithSelect<Sql, ColsText<Cols>, ResolveId<Id, "select", Sql["selects"]>, true>>;
|
|
98
|
+
|
|
99
|
+
from<Src extends string | SelectQueryBuilder<Schema, any>>(
|
|
100
|
+
source: Src,
|
|
101
|
+
): SelectQueryBuilder<Schema, WithFrom<Sql, Src extends string ? Src : string>>;
|
|
102
|
+
|
|
103
|
+
where<Cond extends string | ConditionTreeBuilder<any, any>, Id extends string | undefined = undefined>(
|
|
104
|
+
condition: Cond,
|
|
105
|
+
id?: Id,
|
|
106
|
+
): SelectQueryBuilder<Schema, WithWhere<Sql, CondText<Cond>, ResolveId<Id, "where", Sql["wheres"]>>>;
|
|
107
|
+
|
|
108
|
+
whereIf<Cond extends string | ConditionTreeBuilder<any, any>, Id extends string | undefined = undefined>(
|
|
109
|
+
condition: boolean,
|
|
110
|
+
clause: Cond,
|
|
111
|
+
id?: Id,
|
|
112
|
+
): SelectQueryBuilder<Schema, WithWhere<Sql, CondText<Cond>, ResolveId<Id, "where", Sql["wheres"]>>>;
|
|
113
|
+
|
|
114
|
+
join<J extends string, Id extends string | undefined = undefined>(
|
|
115
|
+
joinSql: J,
|
|
116
|
+
id?: Id,
|
|
117
|
+
): SelectQueryBuilder<Schema, WithJoin<Sql, J, ResolveId<Id, "join", Sql["joins"]>>>;
|
|
118
|
+
|
|
119
|
+
joinIf<J extends string, Id extends string | undefined = undefined>(
|
|
120
|
+
condition: boolean,
|
|
121
|
+
joinSql: J,
|
|
122
|
+
id?: Id,
|
|
123
|
+
): SelectQueryBuilder<Schema, WithJoin<Sql, J, ResolveId<Id, "join", Sql["joins"]>>>;
|
|
124
|
+
|
|
125
|
+
groupBy<const Cols extends string | readonly string[], Id extends string | undefined = undefined>(
|
|
126
|
+
columns: Cols,
|
|
127
|
+
id?: Id,
|
|
128
|
+
): SelectQueryBuilder<Schema, WithGroupBy<Sql, ColsText<Cols>, ResolveId<Id, "group", Sql["groupBys"]>>>;
|
|
129
|
+
|
|
130
|
+
groupByIf<const Cols extends string | readonly string[], Id extends string | undefined = undefined>(
|
|
131
|
+
condition: boolean,
|
|
132
|
+
columns: Cols,
|
|
133
|
+
id?: Id,
|
|
134
|
+
): SelectQueryBuilder<Schema, WithGroupBy<Sql, ColsText<Cols>, ResolveId<Id, "group", Sql["groupBys"]>>>;
|
|
135
|
+
|
|
136
|
+
having<Cond extends string | ConditionTreeBuilder<any, any>, Id extends string | undefined = undefined>(
|
|
137
|
+
condition: Cond,
|
|
138
|
+
id?: Id,
|
|
139
|
+
): SelectQueryBuilder<Schema, WithHaving<Sql, CondText<Cond>, ResolveId<Id, "having", Sql["havings"]>>>;
|
|
140
|
+
|
|
141
|
+
havingIf<Cond extends string | ConditionTreeBuilder<any, any>, Id extends string | undefined = undefined>(
|
|
142
|
+
condition: boolean,
|
|
143
|
+
clause: Cond,
|
|
144
|
+
id?: Id,
|
|
145
|
+
): SelectQueryBuilder<Schema, WithHaving<Sql, CondText<Cond>, ResolveId<Id, "having", Sql["havings"]>>>;
|
|
146
|
+
|
|
147
|
+
orderBy<const Cols extends string | readonly string[], Id extends string | undefined = undefined>(
|
|
148
|
+
columns: Cols,
|
|
149
|
+
id?: Id,
|
|
150
|
+
): SelectQueryBuilder<Schema, WithOrderBy<Sql, ColsText<Cols>, ResolveId<Id, "order", Sql["orderBys"]>>>;
|
|
151
|
+
|
|
152
|
+
orderByIf<const Cols extends string | readonly string[], Id extends string | undefined = undefined>(
|
|
153
|
+
condition: boolean,
|
|
154
|
+
columns: Cols,
|
|
155
|
+
id?: Id,
|
|
156
|
+
): SelectQueryBuilder<Schema, WithOrderBy<Sql, ColsText<Cols>, ResolveId<Id, "order", Sql["orderBys"]>>>;
|
|
157
|
+
|
|
158
|
+
limit<const L extends number>(limit: L): SelectQueryBuilder<Schema, WithLimit<Sql, L>>;
|
|
159
|
+
limitIf<const L extends number>(condition: boolean, limit: L): SelectQueryBuilder<Schema, WithLimit<Sql, L>>;
|
|
160
|
+
offset<const O extends number>(offset: O): SelectQueryBuilder<Schema, WithOffset<Sql, O>>;
|
|
161
|
+
offsetIf<const O extends number>(condition: boolean, offset: O): SelectQueryBuilder<Schema, WithOffset<Sql, O>>;
|
|
162
|
+
|
|
163
|
+
removeSelect<Id extends string>(id: Id): SelectQueryBuilder<Schema, WithoutSelect<Sql, Id>>;
|
|
164
|
+
removeJoin<Id extends string>(id: Id): SelectQueryBuilder<Schema, WithoutJoin<Sql, Id>>;
|
|
165
|
+
removeWhere<Id extends string>(id: Id): SelectQueryBuilder<Schema, WithoutWhere<Sql, Id>>;
|
|
166
|
+
removeGroupBy<Id extends string>(id: Id): SelectQueryBuilder<Schema, WithoutGroupBy<Sql, Id>>;
|
|
167
|
+
removeHaving<Id extends string>(id: Id): SelectQueryBuilder<Schema, WithoutHaving<Sql, Id>>;
|
|
168
|
+
removeOrderBy<Id extends string>(id: Id): SelectQueryBuilder<Schema, WithoutOrderBy<Sql, Id>>;
|
|
169
|
+
|
|
170
|
+
// Runtime introspection over keyed clause state. Plain booleans — no
|
|
171
|
+
// phantom-type transform; intended for imperative dedup helpers (e.g.
|
|
172
|
+
// "upgrade LEFT JOIN to INNER only if the alias is already joined").
|
|
173
|
+
hasSelect(id: string): boolean;
|
|
174
|
+
hasJoin(id: string): boolean;
|
|
175
|
+
hasWhere(id: string): boolean;
|
|
176
|
+
hasGroupBy(id: string): boolean;
|
|
177
|
+
hasHaving(id: string): boolean;
|
|
178
|
+
hasOrderBy(id: string): boolean;
|
|
179
|
+
hasFrom(): boolean;
|
|
180
|
+
hasLimit(): boolean;
|
|
181
|
+
hasOffset(): boolean;
|
|
182
|
+
|
|
183
|
+
withParams<P extends Record<string, QueryParamInput>>(
|
|
184
|
+
params: P,
|
|
185
|
+
): SelectQueryBuilder<Schema, Sql>;
|
|
186
|
+
|
|
187
|
+
apply<Sql2 extends SqlTag>(
|
|
188
|
+
fn: (b: SelectQueryBuilder<Schema, Sql>) => SelectQueryBuilder<Schema, Sql2>,
|
|
189
|
+
): SelectQueryBuilder<Schema, Sql2>;
|
|
190
|
+
|
|
191
|
+
applyIf<Sql2 extends SqlTag>(
|
|
192
|
+
condition: boolean,
|
|
193
|
+
fn: (b: SelectQueryBuilder<Schema, Sql>) => SelectQueryBuilder<Schema, Sql2>,
|
|
194
|
+
): SelectQueryBuilder<Schema, FlagNewConditional<Sql, Sql2>>;
|
|
195
|
+
|
|
196
|
+
getParams(): ReadonlyArray<QueryParamValue>;
|
|
197
|
+
toString(): string;
|
|
198
|
+
toBrandedString(): string & { __type: BuilderResultBrand<Schema, Sql> };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// (No `DefaultId`: idless calls resolve a type-level auto id via `ResolveId`
|
|
202
|
+
// in each method's return type — see below.)
|
|
203
|
+
|
|
204
|
+
class SelectQueryBuilderImpl<Schema extends DatabaseSchema, Sql extends SqlTag> {
|
|
205
|
+
readonly _state: RuntimeSelectState;
|
|
206
|
+
|
|
207
|
+
constructor(state?: RuntimeSelectState) {
|
|
208
|
+
this._state = state ?? EMPTY_RUNTIME_STATE;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private clone(patch: Partial<RuntimeSelectState>): RuntimeSelectState {
|
|
212
|
+
return { ...this._state, ...patch };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private next(state: RuntimeSelectState): any {
|
|
216
|
+
return new SelectQueryBuilderImpl<Schema, any>(state);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
select(columns: string | readonly string[], id?: string): any {
|
|
220
|
+
const rawCols = Array.isArray(columns) ? [...columns] : [columns as string];
|
|
221
|
+
const cols = rawCols.length > 0 ? [...rawCols] : [];
|
|
222
|
+
const key = id ?? `select_${Object.keys(this._state.selectSql).length}`;
|
|
223
|
+
return this.next(this.clone({
|
|
224
|
+
selectSql: { ...this._state.selectSql, [key]: cols },
|
|
225
|
+
}));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
selectIf(condition: boolean, columns: string | readonly string[], id?: string): any {
|
|
229
|
+
return condition ? this.select(columns, id) : this.next(this._state);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
from(source: string | { toString(): string; getParams(): ReadonlyArray<QueryParamValue> }): any {
|
|
233
|
+
let fromSql: string;
|
|
234
|
+
if (typeof source === "string") {
|
|
235
|
+
fromSql = source;
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
if (source.getParams().length > 0) {
|
|
239
|
+
throw new Error(
|
|
240
|
+
"from() does not support a parameterized subquery builder: the inner " +
|
|
241
|
+
"builder carries params that cannot be merged into the outer query. " +
|
|
242
|
+
"Inline the subquery as a string or remove its params.",
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
fromSql = `(${source.toString()})`;
|
|
246
|
+
}
|
|
247
|
+
return this.next(this.clone({ fromSql }));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
where(condition: string | ConditionTreeBuilder<any, any>, id?: string): any {
|
|
251
|
+
// An empty condition tree contributes nothing — treat it as a no-op
|
|
252
|
+
// (same as a false whereIf) so we never emit an invalid `WHERE ()`.
|
|
253
|
+
// String conditions are always applied verbatim.
|
|
254
|
+
if (condition instanceof ConditionTreeBuilder && condition.isEmpty()) {
|
|
255
|
+
return this.next(this._state);
|
|
256
|
+
}
|
|
257
|
+
const key = id ?? `where_${Object.keys(this._state.whereSql).length}`;
|
|
258
|
+
const sql = typeof condition === "string" ? condition : condition.toString();
|
|
259
|
+
return this.next(this.clone({ whereSql: { ...this._state.whereSql, [key]: sql } }));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
whereIf(condition: boolean, clause: string | ConditionTreeBuilder<any, any>, id?: string): any {
|
|
263
|
+
return condition ? this.where(clause, id) : this.next(this._state);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
join(joinSql: string, id?: string): any {
|
|
267
|
+
const key = id ?? `join_${this._state.joins.length}`;
|
|
268
|
+
// Idempotent by id: re-joining an existing id only replaces its SQL in
|
|
269
|
+
// joinSql below, keeping the ordering array (and thus its FROM-chain
|
|
270
|
+
// position) untouched. A brand-new id is appended at the tail.
|
|
271
|
+
const existing = this._state.joins.some(j => j.id === key);
|
|
272
|
+
const nextJoins = existing ? this._state.joins : [...this._state.joins, { id: key }];
|
|
273
|
+
return this.next(this.clone({
|
|
274
|
+
joinSql: { ...this._state.joinSql, [key]: joinSql },
|
|
275
|
+
joins: nextJoins,
|
|
276
|
+
}));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
joinIf(condition: boolean, joinSql: string, id?: string): any {
|
|
280
|
+
return condition ? this.join(joinSql, id) : this.next(this._state);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
groupBy(columns: string | readonly string[], id?: string): any {
|
|
284
|
+
const rawCols = Array.isArray(columns) ? [...columns] : [columns as string];
|
|
285
|
+
const key = id ?? `group_${Object.keys(this._state.groupBySql).length}`;
|
|
286
|
+
return this.next(this.clone({
|
|
287
|
+
groupBySql: { ...this._state.groupBySql, [key]: rawCols.join(", ") },
|
|
288
|
+
}));
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
groupByIf(condition: boolean, columns: string | readonly string[], id?: string): any {
|
|
292
|
+
return condition ? this.groupBy(columns, id) : this.next(this._state);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
having(condition: string | ConditionTreeBuilder<any, any>, id?: string): any {
|
|
296
|
+
// An empty condition tree is a no-op (see where()): never emit
|
|
297
|
+
// `HAVING ()`. String conditions are applied verbatim.
|
|
298
|
+
if (condition instanceof ConditionTreeBuilder && condition.isEmpty()) {
|
|
299
|
+
return this.next(this._state);
|
|
300
|
+
}
|
|
301
|
+
const key = id ?? `having_${Object.keys(this._state.havingSql).length}`;
|
|
302
|
+
const sql = typeof condition === "string" ? condition : condition.toString();
|
|
303
|
+
return this.next(this.clone({ havingSql: { ...this._state.havingSql, [key]: sql } }));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
havingIf(condition: boolean, clause: string | ConditionTreeBuilder<any, any>, id?: string): any {
|
|
307
|
+
return condition ? this.having(clause, id) : this.next(this._state);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
orderBy(columns: string | readonly string[], id?: string): any {
|
|
311
|
+
const rawCols = Array.isArray(columns) ? [...columns] : [columns as string];
|
|
312
|
+
const key = id ?? `order_${Object.keys(this._state.orderBySql).length}`;
|
|
313
|
+
return this.next(this.clone({
|
|
314
|
+
orderBySql: { ...this._state.orderBySql, [key]: rawCols.join(", ") },
|
|
315
|
+
}));
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
orderByIf(condition: boolean, columns: string | readonly string[], id?: string): any {
|
|
319
|
+
return condition ? this.orderBy(columns, id) : this.next(this._state);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
limit(limit: number): any {
|
|
323
|
+
return this.next(this.clone({ limit }));
|
|
324
|
+
}
|
|
325
|
+
limitIf(condition: boolean, limit: number): any {
|
|
326
|
+
return condition ? this.limit(limit) : this.next(this._state);
|
|
327
|
+
}
|
|
328
|
+
offset(offset: number): any {
|
|
329
|
+
return this.next(this.clone({ offset }));
|
|
330
|
+
}
|
|
331
|
+
offsetIf(condition: boolean, offset: number): any {
|
|
332
|
+
return condition ? this.offset(offset) : this.next(this._state);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
removeSelect(id: string): any {
|
|
336
|
+
const nextSelectSql = { ...this._state.selectSql };
|
|
337
|
+
if (!(id in nextSelectSql)) {
|
|
338
|
+
return this.next(this._state);
|
|
339
|
+
}
|
|
340
|
+
delete (nextSelectSql as any)[id];
|
|
341
|
+
return this.next(this.clone({ selectSql: nextSelectSql }));
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
removeJoin(id: string): any {
|
|
345
|
+
const nextJoinSql = { ...this._state.joinSql };
|
|
346
|
+
const hadSql = id in nextJoinSql;
|
|
347
|
+
delete (nextJoinSql as any)[id];
|
|
348
|
+
const nextJoins = this._state.joins.filter(j => j.id !== id);
|
|
349
|
+
if (!hadSql && nextJoins.length === this._state.joins.length) {
|
|
350
|
+
return this.next(this._state);
|
|
351
|
+
}
|
|
352
|
+
return this.next(this.clone({ joinSql: nextJoinSql, joins: nextJoins }));
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
removeWhere(id: string): any {
|
|
356
|
+
const nextWhereSql = { ...this._state.whereSql };
|
|
357
|
+
if (!(id in nextWhereSql)) {
|
|
358
|
+
return this.next(this._state);
|
|
359
|
+
}
|
|
360
|
+
delete (nextWhereSql as any)[id];
|
|
361
|
+
return this.next(this.clone({ whereSql: nextWhereSql }));
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
removeGroupBy(id: string): any {
|
|
365
|
+
const nextGroupBySql = { ...this._state.groupBySql };
|
|
366
|
+
if (!(id in nextGroupBySql)) {
|
|
367
|
+
return this.next(this._state);
|
|
368
|
+
}
|
|
369
|
+
delete (nextGroupBySql as any)[id];
|
|
370
|
+
return this.next(this.clone({ groupBySql: nextGroupBySql }));
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
removeHaving(id: string): any {
|
|
374
|
+
const nextHavingSql = { ...this._state.havingSql };
|
|
375
|
+
if (!(id in nextHavingSql)) {
|
|
376
|
+
return this.next(this._state);
|
|
377
|
+
}
|
|
378
|
+
delete (nextHavingSql as any)[id];
|
|
379
|
+
return this.next(this.clone({ havingSql: nextHavingSql }));
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
removeOrderBy(id: string): any {
|
|
383
|
+
const nextOrderBySql = { ...this._state.orderBySql };
|
|
384
|
+
if (!(id in nextOrderBySql)) {
|
|
385
|
+
return this.next(this._state);
|
|
386
|
+
}
|
|
387
|
+
delete (nextOrderBySql as any)[id];
|
|
388
|
+
return this.next(this.clone({ orderBySql: nextOrderBySql }));
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// has*: read-only checks against the keyed runtime state.
|
|
392
|
+
hasSelect(id: string): boolean {
|
|
393
|
+
return id in this._state.selectSql;
|
|
394
|
+
}
|
|
395
|
+
hasJoin(id: string): boolean {
|
|
396
|
+
return id in this._state.joinSql;
|
|
397
|
+
}
|
|
398
|
+
hasWhere(id: string): boolean {
|
|
399
|
+
return id in this._state.whereSql;
|
|
400
|
+
}
|
|
401
|
+
hasGroupBy(id: string): boolean {
|
|
402
|
+
return id in this._state.groupBySql;
|
|
403
|
+
}
|
|
404
|
+
hasHaving(id: string): boolean {
|
|
405
|
+
return id in this._state.havingSql;
|
|
406
|
+
}
|
|
407
|
+
hasOrderBy(id: string): boolean {
|
|
408
|
+
return id in this._state.orderBySql;
|
|
409
|
+
}
|
|
410
|
+
hasFrom(): boolean {
|
|
411
|
+
return this._state.fromSql !== undefined;
|
|
412
|
+
}
|
|
413
|
+
hasLimit(): boolean {
|
|
414
|
+
return this._state.limit !== undefined;
|
|
415
|
+
}
|
|
416
|
+
hasOffset(): boolean {
|
|
417
|
+
return this._state.offset !== undefined;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
withParams(params: Record<string, QueryParamInput>): any {
|
|
421
|
+
return this.next(this.clone({
|
|
422
|
+
namedParams: { ...this._state.namedParams, ...params },
|
|
423
|
+
}));
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
apply(fn: (b: any) => any): any {
|
|
427
|
+
return fn(this);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
applyIf(condition: boolean, fn: (b: any) => any): any {
|
|
431
|
+
return condition ? fn(this) : this;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
getParams(): ReadonlyArray<QueryParamValue> {
|
|
435
|
+
const namedParams = this._state.namedParams;
|
|
436
|
+
if (namedParams && Object.keys(namedParams).length > 0) {
|
|
437
|
+
const sql = assembleSelectSQLPreSub(this._state);
|
|
438
|
+
return collectParamValues(sql, namedParams);
|
|
439
|
+
}
|
|
440
|
+
return this._state.params;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
toString(): string {
|
|
444
|
+
return assembleSelectSQL(this._state);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
toBrandedString(): any {
|
|
448
|
+
return assembleSelectSQL(this._state);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Build the un-substituted fragment string for getParams ordering (matches OLD:
|
|
453
|
+
// getParams scans fragments joined by " " BEFORE $n substitution).
|
|
454
|
+
function assembleSelectSQLPreSub(state: RuntimeSelectState): string {
|
|
455
|
+
return [
|
|
456
|
+
...Object.values(state.cteSql),
|
|
457
|
+
...Object.values(state.selectSql).flat(),
|
|
458
|
+
state.fromSql ?? "",
|
|
459
|
+
...Object.values(state.joinSql),
|
|
460
|
+
...Object.values(state.whereSql),
|
|
461
|
+
...Object.values(state.groupBySql),
|
|
462
|
+
...Object.values(state.havingSql),
|
|
463
|
+
...Object.values(state.orderBySql),
|
|
464
|
+
state.unionSql ?? "",
|
|
465
|
+
].join(" ");
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
export function createSelectQuery<Schema extends DatabaseSchema>(): SelectQueryBuilder<Schema, EmptySqlTag> {
|
|
469
|
+
return new SelectQueryBuilderImpl<Schema, EmptySqlTag>(EMPTY_RUNTIME_STATE) as unknown as SelectQueryBuilder<Schema, EmptySqlTag>;
|
|
470
|
+
}
|