@grest-ts/sql 0.0.6 → 0.0.7
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 -21
- package/README.md +498 -498
- package/dist/src/select/parts/S0Union.d.ts +2 -2
- package/dist/tsconfig.publish.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/MysqlTable.ts +101 -101
- package/src/MysqlTableStructureParser.ts +283 -283
- package/src/SQL.ts +244 -244
- package/src/SqlExpression.ts +172 -172
- package/src/SqlFunctions.ts +471 -471
- package/src/Symbols.ts +10 -10
- package/src/Types.ts +244 -244
- package/src/delete/DeleteBuilder.ts +64 -64
- package/src/delete/DeleteInterfaces.ts +70 -70
- package/src/escape.ts +51 -51
- package/src/index.ts +5 -5
- package/src/insert/InsertBuilder.ts +75 -75
- package/src/insert/InsertInterfaces.ts +16 -16
- package/src/select/SelectBuilder.ts +371 -371
- package/src/select/parts/GatewayWhere.ts +30 -30
- package/src/select/parts/S0Union.ts +43 -43
- package/src/select/parts/S0Uses.ts +21 -21
- package/src/select/parts/S0With.ts +21 -21
- package/src/select/parts/S1Join.ts +38 -38
- package/src/select/parts/S2Columns.ts +68 -68
- package/src/select/parts/S2Where.ts +51 -51
- package/src/select/parts/S3GroupBy.ts +23 -23
- package/src/select/parts/S4Having.ts +24 -24
- package/src/select/parts/S5OrderBy.ts +21 -21
- package/src/select/parts/S6Limit.ts +9 -9
- package/src/select/parts/S7Exec.ts +41 -41
- package/src/update/UpdateBuilder.ts +95 -95
- package/src/update/UpdateInterfaces.ts +87 -87
|
@@ -1,372 +1,372 @@
|
|
|
1
|
-
import {Over, SqlExpression, Transformer} from "../SqlExpression";
|
|
2
|
-
import {WhereMethods} from "./parts/S2Where";
|
|
3
|
-
import {ColumnsMethods} from "./parts/S2Columns";
|
|
4
|
-
import {JoinMethods} from "./parts/S1Join";
|
|
5
|
-
import {WithMethods} from "./parts/S0With";
|
|
6
|
-
import {UsesMethods} from "./parts/S0Uses";
|
|
7
|
-
import {UnionMethods} from "./parts/S0Union";
|
|
8
|
-
import {GroupByMethods} from "./parts/S3GroupBy";
|
|
9
|
-
import {HavingMethods} from "./parts/S4Having";
|
|
10
|
-
import {OrderByMethods} from "./parts/S5OrderBy";
|
|
11
|
-
import {LimitMethods} from "./parts/S6Limit";
|
|
12
|
-
import {ExecMethods} from "./parts/S7Exec";
|
|
13
|
-
import {MysqlTable} from "../MysqlTable";
|
|
14
|
-
import {SQL_ALIAS, SQL_ENTITY, SQL_EXPRESSION} from "../Symbols";
|
|
15
|
-
import {AliasedTable, AnyAliasedTableDef, DbTableDefinition, SelectExecutor} from "../Types";
|
|
16
|
-
import {escape, escapeId} from "../escape";
|
|
17
|
-
import {ONE, orderByStructureToSqlString} from "../SqlFunctions";
|
|
18
|
-
import {GatewaySelectGroupByMethods, GatewaySelectLimitMethods, GatewaySelectOrderByMethods, GatewayWhereMethods} from "./parts/GatewayWhere";
|
|
19
|
-
|
|
20
|
-
const TAB = " ";
|
|
21
|
-
|
|
22
|
-
export class SelectBuilder<Result, Aliases, AliasesFromWith, Tables> implements UnionMethods<Result>,
|
|
23
|
-
WithMethods<AliasesFromWith>,
|
|
24
|
-
UsesMethods<Aliases, Tables>,
|
|
25
|
-
JoinMethods<Aliases, AliasesFromWith, Tables>,
|
|
26
|
-
ColumnsMethods<Result, Tables>,
|
|
27
|
-
WhereMethods<Result, Tables>,
|
|
28
|
-
GroupByMethods<Result, Tables>,
|
|
29
|
-
HavingMethods<Result, Tables>,
|
|
30
|
-
OrderByMethods<Result, Tables>,
|
|
31
|
-
LimitMethods<Result>,
|
|
32
|
-
ExecMethods<Result>,
|
|
33
|
-
GatewayWhereMethods<Result, Tables>,
|
|
34
|
-
GatewaySelectGroupByMethods<Result, Tables>,
|
|
35
|
-
GatewaySelectOrderByMethods<Result, Tables>,
|
|
36
|
-
GatewaySelectOrderByMethods<Result, Tables>,
|
|
37
|
-
GatewaySelectLimitMethods<Result> {
|
|
38
|
-
|
|
39
|
-
public readonly [SQL_ENTITY]: undefined; // never used
|
|
40
|
-
|
|
41
|
-
private _withQueries: Map<string, string> = new Map()
|
|
42
|
-
private _from: string;
|
|
43
|
-
private readonly _columns: string[] = [];
|
|
44
|
-
private _columnStruct: DbTableDefinition<any> = {} as any;
|
|
45
|
-
|
|
46
|
-
private readonly _joins: string[] = []
|
|
47
|
-
private readonly _windows: string[] = []
|
|
48
|
-
private readonly _where: string[] = []
|
|
49
|
-
private readonly _having: string[] = []
|
|
50
|
-
private readonly _groupBy: string[] = []
|
|
51
|
-
private _orderBy: string[] = []
|
|
52
|
-
private _limit: string = undefined
|
|
53
|
-
|
|
54
|
-
private readonly unions: string[] = []
|
|
55
|
-
private _unionColumnOrder: string[] = []
|
|
56
|
-
|
|
57
|
-
private _distinct: boolean = false;
|
|
58
|
-
private _forUpdate: boolean = false;
|
|
59
|
-
|
|
60
|
-
public readonly transformers: { property: string, transformer: Transformer<any, any> }[] = []
|
|
61
|
-
|
|
62
|
-
public union(table: any | SelectBuilder<any, any, any, any>): SelectBuilder<any, any, any, any> {
|
|
63
|
-
return this._union(table, "DISTINCT") as any
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
public unionAll(table: any | SelectBuilder<any, any, any, any>): SelectBuilder<any, any, any, any> {
|
|
67
|
-
return this._union(table, "ALL")
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
private _union(table: SelectBuilder<any, any, any, any>, type: "ALL" | "DISTINCT" = "DISTINCT"): SelectBuilder<any, any, any, any> {
|
|
71
|
-
if (this.unions.length === 0) {
|
|
72
|
-
const builder = new SelectBuilder()
|
|
73
|
-
builder.unions.push("(\n" + this.toString(1) + ") ")
|
|
74
|
-
builder._columnStruct = this._columnStruct;
|
|
75
|
-
for (const k in this._columnStruct) {
|
|
76
|
-
builder._unionColumnOrder.push(k);
|
|
77
|
-
}
|
|
78
|
-
builder._union(table, type);
|
|
79
|
-
return builder;
|
|
80
|
-
} else {
|
|
81
|
-
const struct = table._columnStruct;
|
|
82
|
-
let i = 0;
|
|
83
|
-
for (const k in struct) {
|
|
84
|
-
if (this._unionColumnOrder[i] !== k) {
|
|
85
|
-
const order: string[] = []
|
|
86
|
-
for (const k in struct) {
|
|
87
|
-
order.push(k);
|
|
88
|
-
}
|
|
89
|
-
throw new Error("Union columns from pos " + i + " do not match! \nFirst call columns: '" + this._unionColumnOrder.join(", ") + "'. \nCurrent call columns: '" + order.join(", ") + "'")
|
|
90
|
-
}
|
|
91
|
-
i++;
|
|
92
|
-
}
|
|
93
|
-
if (i !== this._unionColumnOrder.length) {
|
|
94
|
-
throw new Error("Union columns count do not match! " + this._unionColumnOrder.length + " != " + i + " (Why didn't compiler warn you about this?)");
|
|
95
|
-
}
|
|
96
|
-
this.unions.push("UNION " + (type === "DISTINCT" ? "DISTINCT " : "ALL ") + "(\n" + table.toString(1) + ") ")
|
|
97
|
-
return this;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
public with(...tables: (any | AnyAliasedTableDef)[]) {
|
|
102
|
-
console.log(tables)
|
|
103
|
-
for (let i = 0; i < tables.length; i++) {
|
|
104
|
-
const table = tables[i];
|
|
105
|
-
const alias = table[SQL_ALIAS]
|
|
106
|
-
this._withQueries.set(alias, escapeId(alias) + " AS " + table[SQL_EXPRESSION])
|
|
107
|
-
}
|
|
108
|
-
return this
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
public uses(table: any | AnyAliasedTableDef) {
|
|
112
|
-
return this
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
public selectFrom(table: any | AnyAliasedTableDef) {
|
|
116
|
-
if (typeof table === "string") {
|
|
117
|
-
this._from = escapeId(table);
|
|
118
|
-
} else {
|
|
119
|
-
this._from = table[SQL_EXPRESSION] + " as " + escapeId(table[SQL_ALIAS]);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return this
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
public join(table: any | AnyAliasedTableDef, condition: any) {
|
|
126
|
-
return this._join(table, condition, "JOIN")
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
public leftJoin(table: any | AnyAliasedTableDef, condition: any) {
|
|
130
|
-
return this._join(table, condition, "LEFT JOIN")
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
private _join(table: AnyAliasedTableDef, condition: any, type: "JOIN" | "LEFT JOIN" = "JOIN") {
|
|
134
|
-
const sql = this._withQueries.has(table[SQL_ALIAS]) ? escapeId(table[SQL_ALIAS]) : table[SQL_EXPRESSION] + " as " + escapeId(table[SQL_ALIAS])
|
|
135
|
-
this._joins.push(type + " " + sql + " ON (" + condition.expression + ")")
|
|
136
|
-
return this;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
public window(name: string, func: (builder: Over<any>) => Over<any>) {
|
|
140
|
-
const builder2 = new Over()
|
|
141
|
-
func(builder2)
|
|
142
|
-
this._windows.push(name + " AS (" + builder2.toString() + ")")
|
|
143
|
-
return this
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
public distinct() {
|
|
147
|
-
this._distinct = true;
|
|
148
|
-
return this
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
public columns(...cols: (any | string | SqlExpression<string, string, any>)[]) {
|
|
152
|
-
for (let i = 0; i < cols.length; i++) {
|
|
153
|
-
const col = cols[i];
|
|
154
|
-
if (col instanceof SqlExpression) {
|
|
155
|
-
this._columns.push(col.expression + (col.nameAs ? " as " + escapeId(col.nameAs) : ""));
|
|
156
|
-
(this._columnStruct as any)[col.nameAs] = true;
|
|
157
|
-
if (col.transformer) {
|
|
158
|
-
this.transformers.push({property: col.nameAs, transformer: col.transformer});
|
|
159
|
-
}
|
|
160
|
-
} else if (typeof col === "string") {
|
|
161
|
-
this._columns.push(escapeId(col));
|
|
162
|
-
(this._columnStruct as any)[col] = true;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
return this
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
public one() {
|
|
169
|
-
this.columns(ONE())
|
|
170
|
-
return this;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
public allColumnsFrom(table: any | AnyAliasedTableDef) {
|
|
174
|
-
for (const k in table) {
|
|
175
|
-
this.columns(table[k])
|
|
176
|
-
}
|
|
177
|
-
return this
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
public noWhere() {
|
|
181
|
-
return this as any
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
public where(...cols: (any | undefined | null | "" | object | SqlExpression<string, string, any>)[]) {
|
|
185
|
-
for (let i = 0; i < cols.length; i++) {
|
|
186
|
-
const col = cols[i];
|
|
187
|
-
if (col instanceof SqlExpression) {
|
|
188
|
-
this._where.push(col.expression);
|
|
189
|
-
} else if (typeof col === "object") {
|
|
190
|
-
for (const prop in col) {
|
|
191
|
-
this._where.push(escapeId(prop) + " = " + escape(col[prop]));
|
|
192
|
-
}
|
|
193
|
-
} else if (col === undefined || col === null || col === "" || col === false) {
|
|
194
|
-
// skip
|
|
195
|
-
} else {
|
|
196
|
-
throw new Error("Invalid argument to where! Type: '" + (typeof col) + "' String: '" + String(col) + "'")
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
return this as any
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
public groupBy(...items: (any | SqlExpression<string, string, any>)[]) {
|
|
203
|
-
for (let i = 0; i < items.length; i++) {
|
|
204
|
-
const item = items[i];
|
|
205
|
-
if (item instanceof SqlExpression) {
|
|
206
|
-
this._groupBy.push(item.expression);
|
|
207
|
-
} else if (typeof item === "string") {
|
|
208
|
-
this._groupBy.push(escapeId(item));
|
|
209
|
-
} else if (item === undefined || item === null || item === "") {
|
|
210
|
-
// skip
|
|
211
|
-
} else {
|
|
212
|
-
throw new Error("Invalid argument to groupBy! Type: '" + (typeof item) + "' String: '" + String(item) + "'")
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
return this as any
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
public groupByF(func: (res: any | AliasedTable<string, string, Result, unknown, any>) => (any | SqlExpression<string, string, any>)[]) {
|
|
219
|
-
const proxy: any = new Proxy({}, {
|
|
220
|
-
get(target: {}, p: string, receiver: any): any {
|
|
221
|
-
if (p === "" || p === "undefined") {
|
|
222
|
-
return undefined;
|
|
223
|
-
}
|
|
224
|
-
return new SqlExpression(p, p)
|
|
225
|
-
}
|
|
226
|
-
})
|
|
227
|
-
this.groupBy(...(func(proxy) as any));
|
|
228
|
-
return this as any
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
public having(...items: (any | SqlExpression<string, string, any>)[]) {
|
|
232
|
-
for (let i = 0; i < items.length; i++) {
|
|
233
|
-
const item = items[i];
|
|
234
|
-
if (item instanceof SqlExpression) {
|
|
235
|
-
this._having.push(item.expression);
|
|
236
|
-
} else if (typeof item === "string") {
|
|
237
|
-
this._having.push(escapeId(item));
|
|
238
|
-
} else if (item === undefined || item === null || item === "") {
|
|
239
|
-
// skip
|
|
240
|
-
} else {
|
|
241
|
-
throw new Error("Invalid argument to having! Type: '" + (typeof item) + "' String: '" + String(item) + "'")
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
return this as any
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
public havingF(func: (res: any | AliasedTable<string, string, Result, unknown, any>) => (any | SqlExpression<string, string, any>)[]) {
|
|
248
|
-
const proxy: any = new Proxy({}, {
|
|
249
|
-
get(target: {}, p: string, receiver: any): any {
|
|
250
|
-
if (p === "" || p === "undefined") {
|
|
251
|
-
return undefined;
|
|
252
|
-
}
|
|
253
|
-
return new SqlExpression(p, p)
|
|
254
|
-
}
|
|
255
|
-
})
|
|
256
|
-
this.having(...(func(proxy) as any));
|
|
257
|
-
return this
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
public orderBy(...items: (any | SqlExpression<string, string, any>)[]) {
|
|
261
|
-
this._orderBy = orderByStructureToSqlString(items as any)
|
|
262
|
-
return this as any
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
public orderByF(func: (res: any | AliasedTable<string, string, Result, unknown, any>) => (any | SqlExpression<string, string, any>)[]) {
|
|
266
|
-
const proxy: any = new Proxy({}, {
|
|
267
|
-
get(target: {}, p: string, receiver: any): any {
|
|
268
|
-
if (p === "" || p === "undefined") {
|
|
269
|
-
return undefined;
|
|
270
|
-
}
|
|
271
|
-
return new SqlExpression(p, p)
|
|
272
|
-
}
|
|
273
|
-
})
|
|
274
|
-
this.orderBy(...(func(proxy) as any));
|
|
275
|
-
return this
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
public noLimit() {
|
|
279
|
-
return this as any
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
public limit(limit: number | [number, number]) {
|
|
283
|
-
if (Array.isArray(limit)) {
|
|
284
|
-
this._limit = Number(limit[0]) + "," + Number(limit[1]);
|
|
285
|
-
} else if (limit !== undefined && limit !== null && String(limit) !== "") {
|
|
286
|
-
this._limit = String(Number(limit));
|
|
287
|
-
}
|
|
288
|
-
return this as any
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
public limit1() {
|
|
292
|
-
return this.limit(1) as any
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
public forUpdate() {
|
|
296
|
-
this._forUpdate = true;
|
|
297
|
-
return this as any
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
public asScalar(alias: any): any {
|
|
301
|
-
return SqlExpression.create("(\n" + this.toString(2) + TAB + ")", alias);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
public as(alias: any): any {
|
|
305
|
-
return MysqlTable.defineDbTable("(\n" + this.toString(2) + ")" as "(SUBQUERY)", alias, this._columnStruct)
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
public async transformResult<T>(rows: T[]): Promise<T[]> {
|
|
309
|
-
for (let r = 0; r < rows.length; r++) {
|
|
310
|
-
const row: any = rows[r];
|
|
311
|
-
for (let i = 0; i < this.transformers.length; i++) {
|
|
312
|
-
const parser = this.transformers[i]
|
|
313
|
-
const res = parser.transformer(row[parser.property]);
|
|
314
|
-
if (res instanceof Promise) {
|
|
315
|
-
row[parser.property] = await res;
|
|
316
|
-
} else {
|
|
317
|
-
row[parser.property] = res;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
return rows;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
public toString(lvl: number = 0) {
|
|
325
|
-
if (this.transformers.length > 0) {
|
|
326
|
-
throw new Error("Select.toString() call might be accidental! This query uses transforms and it may cause errors in the application! " +
|
|
327
|
-
"This is safety measure to avoid executing queries that have transforms that would not get executed! " +
|
|
328
|
-
"You can call toSqlQuery() method to still get a sql string!")
|
|
329
|
-
}
|
|
330
|
-
return this._toString(lvl);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
public toSqlString() {
|
|
334
|
-
return this._toString();
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
private _toString(lvl: number = 0) {
|
|
338
|
-
const tabs = TAB.repeat(lvl);
|
|
339
|
-
let base: string;
|
|
340
|
-
if (this.unions.length > 0) {
|
|
341
|
-
base = this.unions.join("") + "\n";
|
|
342
|
-
} else {
|
|
343
|
-
base = (this._withQueries.size > 0 ? tabs + "WITH\n" + TAB + Array.from(this._withQueries.values()).join(",\n\n" + TAB) + "\n\n" : "") +
|
|
344
|
-
tabs + "SELECT " + (this._distinct ? "DISTINCT " : "") + "\n" +
|
|
345
|
-
tabs + TAB + this._columns.join(",\n" + tabs + TAB) + "\n" +
|
|
346
|
-
tabs + "FROM " + this._from + "\n" +
|
|
347
|
-
(this._joins.length > 0 ? tabs + this._joins.join("\n") + "\n" : "") +
|
|
348
|
-
(this._where.length > 0 ? tabs + "WHERE " + this._where.join("\n" + TAB + " AND ") + "\n" : "")
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
return base +
|
|
352
|
-
(this._groupBy.length > 0 ? tabs + "GROUP BY " + this._groupBy.join(", ") + "\n" : "") +
|
|
353
|
-
(this._having.length > 0 ? tabs + "HAVING " + this._having.join("\n" + TAB + " AND ") + "\n" : "") +
|
|
354
|
-
(this._windows.length > 0 ? tabs + "WINDOW " + this._windows.join(",\n" + TAB) + "\n" : "") +
|
|
355
|
-
(this._orderBy.length > 0 ? tabs + "ORDER BY " + this._orderBy.join(", ") + "\n" : "") +
|
|
356
|
-
(this._limit ? tabs + "LIMIT " + this._limit + "\n" : "") +
|
|
357
|
-
(this._forUpdate && this.unions.length === 0 ? tabs + "FOR UPDATE\n" : "");
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
public async exec<Args extends any[]>(executor: SelectExecutor<Result, Args>, ...args: Args): Promise<Result[]> {
|
|
361
|
-
const res = await executor(this.toSqlString(), ...args);
|
|
362
|
-
if (res && res.length > 0 && this.transformers.length > 0) {
|
|
363
|
-
await this.transformResult(res)
|
|
364
|
-
}
|
|
365
|
-
return res;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
public async execOne<Args extends any[]>(executor: SelectExecutor<Result, Args>, ...args: Args): Promise<Result> {
|
|
369
|
-
return (await this.exec(executor, ...args))?.[0]
|
|
370
|
-
}
|
|
371
|
-
|
|
1
|
+
import {Over, SqlExpression, Transformer} from "../SqlExpression";
|
|
2
|
+
import {WhereMethods} from "./parts/S2Where";
|
|
3
|
+
import {ColumnsMethods} from "./parts/S2Columns";
|
|
4
|
+
import {JoinMethods} from "./parts/S1Join";
|
|
5
|
+
import {WithMethods} from "./parts/S0With";
|
|
6
|
+
import {UsesMethods} from "./parts/S0Uses";
|
|
7
|
+
import {UnionMethods} from "./parts/S0Union";
|
|
8
|
+
import {GroupByMethods} from "./parts/S3GroupBy";
|
|
9
|
+
import {HavingMethods} from "./parts/S4Having";
|
|
10
|
+
import {OrderByMethods} from "./parts/S5OrderBy";
|
|
11
|
+
import {LimitMethods} from "./parts/S6Limit";
|
|
12
|
+
import {ExecMethods} from "./parts/S7Exec";
|
|
13
|
+
import {MysqlTable} from "../MysqlTable";
|
|
14
|
+
import {SQL_ALIAS, SQL_ENTITY, SQL_EXPRESSION} from "../Symbols";
|
|
15
|
+
import {AliasedTable, AnyAliasedTableDef, DbTableDefinition, SelectExecutor} from "../Types";
|
|
16
|
+
import {escape, escapeId} from "../escape";
|
|
17
|
+
import {ONE, orderByStructureToSqlString} from "../SqlFunctions";
|
|
18
|
+
import {GatewaySelectGroupByMethods, GatewaySelectLimitMethods, GatewaySelectOrderByMethods, GatewayWhereMethods} from "./parts/GatewayWhere";
|
|
19
|
+
|
|
20
|
+
const TAB = " ";
|
|
21
|
+
|
|
22
|
+
export class SelectBuilder<Result, Aliases, AliasesFromWith, Tables> implements UnionMethods<Result>,
|
|
23
|
+
WithMethods<AliasesFromWith>,
|
|
24
|
+
UsesMethods<Aliases, Tables>,
|
|
25
|
+
JoinMethods<Aliases, AliasesFromWith, Tables>,
|
|
26
|
+
ColumnsMethods<Result, Tables>,
|
|
27
|
+
WhereMethods<Result, Tables>,
|
|
28
|
+
GroupByMethods<Result, Tables>,
|
|
29
|
+
HavingMethods<Result, Tables>,
|
|
30
|
+
OrderByMethods<Result, Tables>,
|
|
31
|
+
LimitMethods<Result>,
|
|
32
|
+
ExecMethods<Result>,
|
|
33
|
+
GatewayWhereMethods<Result, Tables>,
|
|
34
|
+
GatewaySelectGroupByMethods<Result, Tables>,
|
|
35
|
+
GatewaySelectOrderByMethods<Result, Tables>,
|
|
36
|
+
GatewaySelectOrderByMethods<Result, Tables>,
|
|
37
|
+
GatewaySelectLimitMethods<Result> {
|
|
38
|
+
|
|
39
|
+
public readonly [SQL_ENTITY]: undefined; // never used
|
|
40
|
+
|
|
41
|
+
private _withQueries: Map<string, string> = new Map()
|
|
42
|
+
private _from: string;
|
|
43
|
+
private readonly _columns: string[] = [];
|
|
44
|
+
private _columnStruct: DbTableDefinition<any> = {} as any;
|
|
45
|
+
|
|
46
|
+
private readonly _joins: string[] = []
|
|
47
|
+
private readonly _windows: string[] = []
|
|
48
|
+
private readonly _where: string[] = []
|
|
49
|
+
private readonly _having: string[] = []
|
|
50
|
+
private readonly _groupBy: string[] = []
|
|
51
|
+
private _orderBy: string[] = []
|
|
52
|
+
private _limit: string = undefined
|
|
53
|
+
|
|
54
|
+
private readonly unions: string[] = []
|
|
55
|
+
private _unionColumnOrder: string[] = []
|
|
56
|
+
|
|
57
|
+
private _distinct: boolean = false;
|
|
58
|
+
private _forUpdate: boolean = false;
|
|
59
|
+
|
|
60
|
+
public readonly transformers: { property: string, transformer: Transformer<any, any> }[] = []
|
|
61
|
+
|
|
62
|
+
public union(table: any | SelectBuilder<any, any, any, any>): SelectBuilder<any, any, any, any> {
|
|
63
|
+
return this._union(table, "DISTINCT") as any
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public unionAll(table: any | SelectBuilder<any, any, any, any>): SelectBuilder<any, any, any, any> {
|
|
67
|
+
return this._union(table, "ALL")
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private _union(table: SelectBuilder<any, any, any, any>, type: "ALL" | "DISTINCT" = "DISTINCT"): SelectBuilder<any, any, any, any> {
|
|
71
|
+
if (this.unions.length === 0) {
|
|
72
|
+
const builder = new SelectBuilder()
|
|
73
|
+
builder.unions.push("(\n" + this.toString(1) + ") ")
|
|
74
|
+
builder._columnStruct = this._columnStruct;
|
|
75
|
+
for (const k in this._columnStruct) {
|
|
76
|
+
builder._unionColumnOrder.push(k);
|
|
77
|
+
}
|
|
78
|
+
builder._union(table, type);
|
|
79
|
+
return builder;
|
|
80
|
+
} else {
|
|
81
|
+
const struct = table._columnStruct;
|
|
82
|
+
let i = 0;
|
|
83
|
+
for (const k in struct) {
|
|
84
|
+
if (this._unionColumnOrder[i] !== k) {
|
|
85
|
+
const order: string[] = []
|
|
86
|
+
for (const k in struct) {
|
|
87
|
+
order.push(k);
|
|
88
|
+
}
|
|
89
|
+
throw new Error("Union columns from pos " + i + " do not match! \nFirst call columns: '" + this._unionColumnOrder.join(", ") + "'. \nCurrent call columns: '" + order.join(", ") + "'")
|
|
90
|
+
}
|
|
91
|
+
i++;
|
|
92
|
+
}
|
|
93
|
+
if (i !== this._unionColumnOrder.length) {
|
|
94
|
+
throw new Error("Union columns count do not match! " + this._unionColumnOrder.length + " != " + i + " (Why didn't compiler warn you about this?)");
|
|
95
|
+
}
|
|
96
|
+
this.unions.push("UNION " + (type === "DISTINCT" ? "DISTINCT " : "ALL ") + "(\n" + table.toString(1) + ") ")
|
|
97
|
+
return this;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public with(...tables: (any | AnyAliasedTableDef)[]) {
|
|
102
|
+
console.log(tables)
|
|
103
|
+
for (let i = 0; i < tables.length; i++) {
|
|
104
|
+
const table = tables[i];
|
|
105
|
+
const alias = table[SQL_ALIAS]
|
|
106
|
+
this._withQueries.set(alias, escapeId(alias) + " AS " + table[SQL_EXPRESSION])
|
|
107
|
+
}
|
|
108
|
+
return this
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
public uses(table: any | AnyAliasedTableDef) {
|
|
112
|
+
return this
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
public selectFrom(table: any | AnyAliasedTableDef) {
|
|
116
|
+
if (typeof table === "string") {
|
|
117
|
+
this._from = escapeId(table);
|
|
118
|
+
} else {
|
|
119
|
+
this._from = table[SQL_EXPRESSION] + " as " + escapeId(table[SQL_ALIAS]);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return this
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
public join(table: any | AnyAliasedTableDef, condition: any) {
|
|
126
|
+
return this._join(table, condition, "JOIN")
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
public leftJoin(table: any | AnyAliasedTableDef, condition: any) {
|
|
130
|
+
return this._join(table, condition, "LEFT JOIN")
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private _join(table: AnyAliasedTableDef, condition: any, type: "JOIN" | "LEFT JOIN" = "JOIN") {
|
|
134
|
+
const sql = this._withQueries.has(table[SQL_ALIAS]) ? escapeId(table[SQL_ALIAS]) : table[SQL_EXPRESSION] + " as " + escapeId(table[SQL_ALIAS])
|
|
135
|
+
this._joins.push(type + " " + sql + " ON (" + condition.expression + ")")
|
|
136
|
+
return this;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
public window(name: string, func: (builder: Over<any>) => Over<any>) {
|
|
140
|
+
const builder2 = new Over()
|
|
141
|
+
func(builder2)
|
|
142
|
+
this._windows.push(name + " AS (" + builder2.toString() + ")")
|
|
143
|
+
return this
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
public distinct() {
|
|
147
|
+
this._distinct = true;
|
|
148
|
+
return this
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
public columns(...cols: (any | string | SqlExpression<string, string, any>)[]) {
|
|
152
|
+
for (let i = 0; i < cols.length; i++) {
|
|
153
|
+
const col = cols[i];
|
|
154
|
+
if (col instanceof SqlExpression) {
|
|
155
|
+
this._columns.push(col.expression + (col.nameAs ? " as " + escapeId(col.nameAs) : ""));
|
|
156
|
+
(this._columnStruct as any)[col.nameAs] = true;
|
|
157
|
+
if (col.transformer) {
|
|
158
|
+
this.transformers.push({property: col.nameAs, transformer: col.transformer});
|
|
159
|
+
}
|
|
160
|
+
} else if (typeof col === "string") {
|
|
161
|
+
this._columns.push(escapeId(col));
|
|
162
|
+
(this._columnStruct as any)[col] = true;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return this
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
public one() {
|
|
169
|
+
this.columns(ONE())
|
|
170
|
+
return this;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
public allColumnsFrom(table: any | AnyAliasedTableDef) {
|
|
174
|
+
for (const k in table) {
|
|
175
|
+
this.columns(table[k])
|
|
176
|
+
}
|
|
177
|
+
return this
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
public noWhere() {
|
|
181
|
+
return this as any
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
public where(...cols: (any | undefined | null | "" | object | SqlExpression<string, string, any>)[]) {
|
|
185
|
+
for (let i = 0; i < cols.length; i++) {
|
|
186
|
+
const col = cols[i];
|
|
187
|
+
if (col instanceof SqlExpression) {
|
|
188
|
+
this._where.push(col.expression);
|
|
189
|
+
} else if (typeof col === "object") {
|
|
190
|
+
for (const prop in col) {
|
|
191
|
+
this._where.push(escapeId(prop) + " = " + escape(col[prop]));
|
|
192
|
+
}
|
|
193
|
+
} else if (col === undefined || col === null || col === "" || col === false) {
|
|
194
|
+
// skip
|
|
195
|
+
} else {
|
|
196
|
+
throw new Error("Invalid argument to where! Type: '" + (typeof col) + "' String: '" + String(col) + "'")
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return this as any
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
public groupBy(...items: (any | SqlExpression<string, string, any>)[]) {
|
|
203
|
+
for (let i = 0; i < items.length; i++) {
|
|
204
|
+
const item = items[i];
|
|
205
|
+
if (item instanceof SqlExpression) {
|
|
206
|
+
this._groupBy.push(item.expression);
|
|
207
|
+
} else if (typeof item === "string") {
|
|
208
|
+
this._groupBy.push(escapeId(item));
|
|
209
|
+
} else if (item === undefined || item === null || item === "") {
|
|
210
|
+
// skip
|
|
211
|
+
} else {
|
|
212
|
+
throw new Error("Invalid argument to groupBy! Type: '" + (typeof item) + "' String: '" + String(item) + "'")
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return this as any
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
public groupByF(func: (res: any | AliasedTable<string, string, Result, unknown, any>) => (any | SqlExpression<string, string, any>)[]) {
|
|
219
|
+
const proxy: any = new Proxy({}, {
|
|
220
|
+
get(target: {}, p: string, receiver: any): any {
|
|
221
|
+
if (p === "" || p === "undefined") {
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
return new SqlExpression(p, p)
|
|
225
|
+
}
|
|
226
|
+
})
|
|
227
|
+
this.groupBy(...(func(proxy) as any));
|
|
228
|
+
return this as any
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
public having(...items: (any | SqlExpression<string, string, any>)[]) {
|
|
232
|
+
for (let i = 0; i < items.length; i++) {
|
|
233
|
+
const item = items[i];
|
|
234
|
+
if (item instanceof SqlExpression) {
|
|
235
|
+
this._having.push(item.expression);
|
|
236
|
+
} else if (typeof item === "string") {
|
|
237
|
+
this._having.push(escapeId(item));
|
|
238
|
+
} else if (item === undefined || item === null || item === "") {
|
|
239
|
+
// skip
|
|
240
|
+
} else {
|
|
241
|
+
throw new Error("Invalid argument to having! Type: '" + (typeof item) + "' String: '" + String(item) + "'")
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return this as any
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
public havingF(func: (res: any | AliasedTable<string, string, Result, unknown, any>) => (any | SqlExpression<string, string, any>)[]) {
|
|
248
|
+
const proxy: any = new Proxy({}, {
|
|
249
|
+
get(target: {}, p: string, receiver: any): any {
|
|
250
|
+
if (p === "" || p === "undefined") {
|
|
251
|
+
return undefined;
|
|
252
|
+
}
|
|
253
|
+
return new SqlExpression(p, p)
|
|
254
|
+
}
|
|
255
|
+
})
|
|
256
|
+
this.having(...(func(proxy) as any));
|
|
257
|
+
return this
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
public orderBy(...items: (any | SqlExpression<string, string, any>)[]) {
|
|
261
|
+
this._orderBy = orderByStructureToSqlString(items as any)
|
|
262
|
+
return this as any
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
public orderByF(func: (res: any | AliasedTable<string, string, Result, unknown, any>) => (any | SqlExpression<string, string, any>)[]) {
|
|
266
|
+
const proxy: any = new Proxy({}, {
|
|
267
|
+
get(target: {}, p: string, receiver: any): any {
|
|
268
|
+
if (p === "" || p === "undefined") {
|
|
269
|
+
return undefined;
|
|
270
|
+
}
|
|
271
|
+
return new SqlExpression(p, p)
|
|
272
|
+
}
|
|
273
|
+
})
|
|
274
|
+
this.orderBy(...(func(proxy) as any));
|
|
275
|
+
return this
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
public noLimit() {
|
|
279
|
+
return this as any
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
public limit(limit: number | [number, number]) {
|
|
283
|
+
if (Array.isArray(limit)) {
|
|
284
|
+
this._limit = Number(limit[0]) + "," + Number(limit[1]);
|
|
285
|
+
} else if (limit !== undefined && limit !== null && String(limit) !== "") {
|
|
286
|
+
this._limit = String(Number(limit));
|
|
287
|
+
}
|
|
288
|
+
return this as any
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
public limit1() {
|
|
292
|
+
return this.limit(1) as any
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
public forUpdate() {
|
|
296
|
+
this._forUpdate = true;
|
|
297
|
+
return this as any
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
public asScalar(alias: any): any {
|
|
301
|
+
return SqlExpression.create("(\n" + this.toString(2) + TAB + ")", alias);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
public as(alias: any): any {
|
|
305
|
+
return MysqlTable.defineDbTable("(\n" + this.toString(2) + ")" as "(SUBQUERY)", alias, this._columnStruct)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
public async transformResult<T>(rows: T[]): Promise<T[]> {
|
|
309
|
+
for (let r = 0; r < rows.length; r++) {
|
|
310
|
+
const row: any = rows[r];
|
|
311
|
+
for (let i = 0; i < this.transformers.length; i++) {
|
|
312
|
+
const parser = this.transformers[i]
|
|
313
|
+
const res = parser.transformer(row[parser.property]);
|
|
314
|
+
if (res instanceof Promise) {
|
|
315
|
+
row[parser.property] = await res;
|
|
316
|
+
} else {
|
|
317
|
+
row[parser.property] = res;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return rows;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
public toString(lvl: number = 0) {
|
|
325
|
+
if (this.transformers.length > 0) {
|
|
326
|
+
throw new Error("Select.toString() call might be accidental! This query uses transforms and it may cause errors in the application! " +
|
|
327
|
+
"This is safety measure to avoid executing queries that have transforms that would not get executed! " +
|
|
328
|
+
"You can call toSqlQuery() method to still get a sql string!")
|
|
329
|
+
}
|
|
330
|
+
return this._toString(lvl);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
public toSqlString() {
|
|
334
|
+
return this._toString();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
private _toString(lvl: number = 0) {
|
|
338
|
+
const tabs = TAB.repeat(lvl);
|
|
339
|
+
let base: string;
|
|
340
|
+
if (this.unions.length > 0) {
|
|
341
|
+
base = this.unions.join("") + "\n";
|
|
342
|
+
} else {
|
|
343
|
+
base = (this._withQueries.size > 0 ? tabs + "WITH\n" + TAB + Array.from(this._withQueries.values()).join(",\n\n" + TAB) + "\n\n" : "") +
|
|
344
|
+
tabs + "SELECT " + (this._distinct ? "DISTINCT " : "") + "\n" +
|
|
345
|
+
tabs + TAB + this._columns.join(",\n" + tabs + TAB) + "\n" +
|
|
346
|
+
tabs + "FROM " + this._from + "\n" +
|
|
347
|
+
(this._joins.length > 0 ? tabs + this._joins.join("\n") + "\n" : "") +
|
|
348
|
+
(this._where.length > 0 ? tabs + "WHERE " + this._where.join("\n" + TAB + " AND ") + "\n" : "")
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return base +
|
|
352
|
+
(this._groupBy.length > 0 ? tabs + "GROUP BY " + this._groupBy.join(", ") + "\n" : "") +
|
|
353
|
+
(this._having.length > 0 ? tabs + "HAVING " + this._having.join("\n" + TAB + " AND ") + "\n" : "") +
|
|
354
|
+
(this._windows.length > 0 ? tabs + "WINDOW " + this._windows.join(",\n" + TAB) + "\n" : "") +
|
|
355
|
+
(this._orderBy.length > 0 ? tabs + "ORDER BY " + this._orderBy.join(", ") + "\n" : "") +
|
|
356
|
+
(this._limit ? tabs + "LIMIT " + this._limit + "\n" : "") +
|
|
357
|
+
(this._forUpdate && this.unions.length === 0 ? tabs + "FOR UPDATE\n" : "");
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
public async exec<Args extends any[]>(executor: SelectExecutor<Result, Args>, ...args: Args): Promise<Result[]> {
|
|
361
|
+
const res = await executor(this.toSqlString(), ...args);
|
|
362
|
+
if (res && res.length > 0 && this.transformers.length > 0) {
|
|
363
|
+
await this.transformResult(res)
|
|
364
|
+
}
|
|
365
|
+
return res;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
public async execOne<Args extends any[]>(executor: SelectExecutor<Result, Args>, ...args: Args): Promise<Result> {
|
|
369
|
+
return (await this.exec(executor, ...args))?.[0]
|
|
370
|
+
}
|
|
371
|
+
|
|
372
372
|
}
|