@bunnykit/orm 0.1.4 → 0.1.6
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/README.md +420 -81
- package/dist/bin/bunny.js +133 -19
- package/dist/src/config/BunnyConfig.d.ts +28 -0
- package/dist/src/config/BunnyConfig.js +14 -0
- package/dist/src/connection/Connection.d.ts +20 -1
- package/dist/src/connection/Connection.js +80 -2
- package/dist/src/connection/ConnectionManager.d.ts +40 -0
- package/dist/src/connection/ConnectionManager.js +104 -0
- package/dist/src/connection/TenantContext.d.ts +15 -0
- package/dist/src/connection/TenantContext.js +22 -0
- package/dist/src/index.d.ts +7 -0
- package/dist/src/index.js +4 -0
- package/dist/src/model/BelongsToMany.js +9 -6
- package/dist/src/model/Model.d.ts +41 -0
- package/dist/src/model/Model.js +217 -18
- package/dist/src/model/ModelNotFoundError.d.ts +5 -0
- package/dist/src/model/ModelNotFoundError.js +13 -0
- package/dist/src/model/MorphRelations.js +10 -10
- package/dist/src/query/Builder.d.ts +85 -7
- package/dist/src/query/Builder.js +489 -68
- package/dist/src/query/grammars/Grammar.d.ts +19 -0
- package/dist/src/query/grammars/Grammar.js +47 -0
- package/dist/src/query/grammars/MySqlGrammar.d.ts +13 -0
- package/dist/src/query/grammars/MySqlGrammar.js +59 -0
- package/dist/src/query/grammars/PostgresGrammar.d.ts +13 -0
- package/dist/src/query/grammars/PostgresGrammar.js +62 -0
- package/dist/src/query/grammars/SQLiteGrammar.d.ts +14 -0
- package/dist/src/query/grammars/SQLiteGrammar.js +63 -0
- package/dist/src/schema/Schema.js +44 -26
- package/dist/src/typegen/TypeGenerator.js +4 -2
- package/dist/src/types/index.d.ts +10 -0
- package/package.json +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ModelNotFoundError } from "../model/ModelNotFoundError.js";
|
|
1
2
|
export class Builder {
|
|
2
3
|
connection;
|
|
3
4
|
tableName;
|
|
@@ -12,10 +13,18 @@ export class Builder {
|
|
|
12
13
|
distinctFlag = false;
|
|
13
14
|
model;
|
|
14
15
|
eagerLoads = [];
|
|
16
|
+
randomOrderFlag = false;
|
|
17
|
+
lockMode;
|
|
18
|
+
unions = [];
|
|
19
|
+
fromRaw;
|
|
20
|
+
updateJoins = [];
|
|
15
21
|
constructor(connection, table) {
|
|
16
22
|
this.connection = connection;
|
|
17
23
|
this.tableName = table;
|
|
18
24
|
}
|
|
25
|
+
get grammar() {
|
|
26
|
+
return this.connection.getGrammar();
|
|
27
|
+
}
|
|
19
28
|
setModel(model) {
|
|
20
29
|
this.model = model;
|
|
21
30
|
return this;
|
|
@@ -33,6 +42,9 @@ export class Builder {
|
|
|
33
42
|
return this;
|
|
34
43
|
}
|
|
35
44
|
where(column, operator, value, boolean = "and", scope) {
|
|
45
|
+
if (typeof column === "function") {
|
|
46
|
+
return this.whereNested(column, boolean);
|
|
47
|
+
}
|
|
36
48
|
if (typeof column === "object" && column !== null) {
|
|
37
49
|
for (const [key, val] of Object.entries(column)) {
|
|
38
50
|
this.where(key, "=", val, boolean, scope);
|
|
@@ -46,9 +58,30 @@ export class Builder {
|
|
|
46
58
|
this.wheres.push({ type: "basic", column, operator, value, boolean, scope });
|
|
47
59
|
return this;
|
|
48
60
|
}
|
|
61
|
+
whereNested(callback, boolean = "and") {
|
|
62
|
+
const nested = new Builder(this.connection, this.tableName);
|
|
63
|
+
callback(nested);
|
|
64
|
+
if (nested.wheres.length > 0) {
|
|
65
|
+
const sql = this.compileNestedWheres(nested);
|
|
66
|
+
this.wheres.push({ type: "raw", column: `(${sql})`, boolean, scope: undefined });
|
|
67
|
+
}
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
49
70
|
orWhere(column, operator, value) {
|
|
50
71
|
return this.where(column, operator, value, "or");
|
|
51
72
|
}
|
|
73
|
+
whereNot(column, value, boolean = "and") {
|
|
74
|
+
if (typeof column === "object" && column !== null) {
|
|
75
|
+
for (const [key, val] of Object.entries(column)) {
|
|
76
|
+
this.whereNot(key, val, boolean);
|
|
77
|
+
}
|
|
78
|
+
return this;
|
|
79
|
+
}
|
|
80
|
+
return this.where(column, "!=", value, boolean);
|
|
81
|
+
}
|
|
82
|
+
orWhereNot(column, value) {
|
|
83
|
+
return this.whereNot(column, value, "or");
|
|
84
|
+
}
|
|
52
85
|
whereIn(column, values, boolean = "and", scope) {
|
|
53
86
|
this.wheres.push({ type: "in", column, value: values, boolean, scope });
|
|
54
87
|
return this;
|
|
@@ -73,6 +106,36 @@ export class Builder {
|
|
|
73
106
|
this.wheres.push({ type: "between", column, value: values, boolean, operator: "NOT BETWEEN", scope });
|
|
74
107
|
return this;
|
|
75
108
|
}
|
|
109
|
+
whereDate(column, operator, value, boolean = "and") {
|
|
110
|
+
return this.addDateWhere("date", column, operator, value, boolean);
|
|
111
|
+
}
|
|
112
|
+
orWhereDate(column, operator, value) {
|
|
113
|
+
return this.whereDate(column, operator, value, "or");
|
|
114
|
+
}
|
|
115
|
+
whereDay(column, operator, value, boolean = "and") {
|
|
116
|
+
return this.addDateWhere("day", column, operator, value, boolean);
|
|
117
|
+
}
|
|
118
|
+
orWhereDay(column, operator, value) {
|
|
119
|
+
return this.whereDay(column, operator, value, "or");
|
|
120
|
+
}
|
|
121
|
+
whereMonth(column, operator, value, boolean = "and") {
|
|
122
|
+
return this.addDateWhere("month", column, operator, value, boolean);
|
|
123
|
+
}
|
|
124
|
+
orWhereMonth(column, operator, value) {
|
|
125
|
+
return this.whereMonth(column, operator, value, "or");
|
|
126
|
+
}
|
|
127
|
+
whereYear(column, operator, value, boolean = "and") {
|
|
128
|
+
return this.addDateWhere("year", column, operator, value, boolean);
|
|
129
|
+
}
|
|
130
|
+
orWhereYear(column, operator, value) {
|
|
131
|
+
return this.whereYear(column, operator, value, "or");
|
|
132
|
+
}
|
|
133
|
+
whereTime(column, operator, value, boolean = "and") {
|
|
134
|
+
return this.addDateWhere("time", column, operator, value, boolean);
|
|
135
|
+
}
|
|
136
|
+
orWhereTime(column, operator, value) {
|
|
137
|
+
return this.whereTime(column, operator, value, "or");
|
|
138
|
+
}
|
|
76
139
|
whereRaw(sql, boolean = "and", scope) {
|
|
77
140
|
this.wheres.push({ type: "raw", column: sql, boolean, scope });
|
|
78
141
|
return this;
|
|
@@ -85,18 +148,129 @@ export class Builder {
|
|
|
85
148
|
this.wheres.push({ type: "exists", column: sql, boolean, operator: not ? "NOT EXISTS" : "EXISTS" });
|
|
86
149
|
return this;
|
|
87
150
|
}
|
|
151
|
+
orWhereNull(column, scope) {
|
|
152
|
+
return this.whereNull(column, "or", scope);
|
|
153
|
+
}
|
|
154
|
+
orWhereNotNull(column, scope) {
|
|
155
|
+
return this.whereNotNull(column, "or", scope);
|
|
156
|
+
}
|
|
157
|
+
orWhereBetween(column, values, scope) {
|
|
158
|
+
return this.whereBetween(column, values, "or", scope);
|
|
159
|
+
}
|
|
160
|
+
orWhereNotBetween(column, values, scope) {
|
|
161
|
+
return this.whereNotBetween(column, values, "or", scope);
|
|
162
|
+
}
|
|
163
|
+
orWhereIn(column, values, scope) {
|
|
164
|
+
return this.whereIn(column, values, "or", scope);
|
|
165
|
+
}
|
|
166
|
+
orWhereNotIn(column, values, scope) {
|
|
167
|
+
return this.whereNotIn(column, values, "or", scope);
|
|
168
|
+
}
|
|
169
|
+
orWhereExists(sql) {
|
|
170
|
+
return this.whereExists(sql, "or");
|
|
171
|
+
}
|
|
172
|
+
orWhereNotExists(sql) {
|
|
173
|
+
return this.whereExists(sql, "or", true);
|
|
174
|
+
}
|
|
175
|
+
orWhereColumn(first, operator, second) {
|
|
176
|
+
return this.whereColumn(first, operator, second, "or");
|
|
177
|
+
}
|
|
178
|
+
orWhereRaw(sql, scope) {
|
|
179
|
+
return this.whereRaw(sql, "or", scope);
|
|
180
|
+
}
|
|
181
|
+
whereJsonContains(column, value, boolean = "and", not = false) {
|
|
182
|
+
let sql = this.grammar.compileJsonContains(this.grammar.wrap(column), value);
|
|
183
|
+
if (not)
|
|
184
|
+
sql = `NOT (${sql})`;
|
|
185
|
+
this.wheres.push({ type: "raw", column: sql, boolean, scope: undefined });
|
|
186
|
+
return this;
|
|
187
|
+
}
|
|
188
|
+
whereJsonLength(column, operator = "=", value, boolean = "and", not = false) {
|
|
189
|
+
if (value === undefined) {
|
|
190
|
+
value = operator;
|
|
191
|
+
operator = "=";
|
|
192
|
+
}
|
|
193
|
+
let sql = this.grammar.compileJsonLength(this.grammar.wrap(column), String(operator), value);
|
|
194
|
+
if (not)
|
|
195
|
+
sql = `NOT (${sql})`;
|
|
196
|
+
this.wheres.push({ type: "raw", column: sql, boolean, scope: undefined });
|
|
197
|
+
return this;
|
|
198
|
+
}
|
|
199
|
+
whereLike(column, value, boolean = "and", not = false) {
|
|
200
|
+
const sql = this.grammar.compileLike(this.grammar.wrap(column), value, not);
|
|
201
|
+
this.wheres.push({ type: "raw", column: sql, boolean, scope: undefined });
|
|
202
|
+
return this;
|
|
203
|
+
}
|
|
204
|
+
whereNotLike(column, value) {
|
|
205
|
+
return this.whereLike(column, value, "and", true);
|
|
206
|
+
}
|
|
207
|
+
whereRegexp(column, value, boolean = "and", not = false) {
|
|
208
|
+
const sql = this.grammar.compileRegexp(this.grammar.wrap(column), value, not);
|
|
209
|
+
this.wheres.push({ type: "raw", column: sql, boolean, scope: undefined });
|
|
210
|
+
return this;
|
|
211
|
+
}
|
|
212
|
+
whereFullText(columns, value, boolean = "and", not = false) {
|
|
213
|
+
const cols = Array.isArray(columns) ? columns : [columns];
|
|
214
|
+
let sql = this.grammar.compileFullText(cols.map((c) => this.grammar.wrap(c)), value);
|
|
215
|
+
if (not)
|
|
216
|
+
sql = `NOT (${sql})`;
|
|
217
|
+
this.wheres.push({ type: "raw", column: sql, boolean, scope: undefined });
|
|
218
|
+
return this;
|
|
219
|
+
}
|
|
220
|
+
whereAll(columns, operator, value, boolean = "and") {
|
|
221
|
+
const sql = columns.map((c) => `${this.grammar.wrap(c)} ${operator} ${this.grammar.escape(value)}`).join(" AND ");
|
|
222
|
+
this.wheres.push({ type: "raw", column: `(${sql})`, boolean, scope: undefined });
|
|
223
|
+
return this;
|
|
224
|
+
}
|
|
225
|
+
whereAny(columns, operator, value, boolean = "and") {
|
|
226
|
+
const sql = columns.map((c) => `${this.grammar.wrap(c)} ${operator} ${this.grammar.escape(value)}`).join(" OR ");
|
|
227
|
+
this.wheres.push({ type: "raw", column: `(${sql})`, boolean, scope: undefined });
|
|
228
|
+
return this;
|
|
229
|
+
}
|
|
88
230
|
orderBy(column, direction = "asc") {
|
|
89
231
|
this.orders.push({ column, direction });
|
|
90
232
|
return this;
|
|
91
233
|
}
|
|
234
|
+
latest(column = "created_at") {
|
|
235
|
+
return this.orderBy(column, "desc");
|
|
236
|
+
}
|
|
237
|
+
oldest(column = "created_at") {
|
|
238
|
+
return this.orderBy(column, "asc");
|
|
239
|
+
}
|
|
240
|
+
inRandomOrder() {
|
|
241
|
+
this.randomOrderFlag = true;
|
|
242
|
+
return this;
|
|
243
|
+
}
|
|
244
|
+
orderByDesc(column) {
|
|
245
|
+
return this.orderBy(column, "desc");
|
|
246
|
+
}
|
|
247
|
+
reorder(column, direction = "asc") {
|
|
248
|
+
this.orders = [];
|
|
249
|
+
this.randomOrderFlag = false;
|
|
250
|
+
if (column) {
|
|
251
|
+
this.orderBy(column, direction);
|
|
252
|
+
}
|
|
253
|
+
return this;
|
|
254
|
+
}
|
|
92
255
|
groupBy(...columns) {
|
|
93
256
|
this.groups.push(...columns);
|
|
94
257
|
return this;
|
|
95
258
|
}
|
|
96
259
|
having(column, operator, value) {
|
|
97
|
-
this.havings.push(`${this.wrap(column)} ${operator} ${this.escape(value)}
|
|
260
|
+
this.havings.push({ sql: `${this.grammar.wrap(column)} ${operator} ${this.grammar.escape(value)}`, boolean: "and" });
|
|
261
|
+
return this;
|
|
262
|
+
}
|
|
263
|
+
orHaving(column, operator, value) {
|
|
264
|
+
this.havings.push({ sql: `${this.grammar.wrap(column)} ${operator} ${this.grammar.escape(value)}`, boolean: "or" });
|
|
265
|
+
return this;
|
|
266
|
+
}
|
|
267
|
+
havingRaw(sql, boolean = "and") {
|
|
268
|
+
this.havings.push({ sql, boolean });
|
|
98
269
|
return this;
|
|
99
270
|
}
|
|
271
|
+
orHavingRaw(sql) {
|
|
272
|
+
return this.havingRaw(sql, "or");
|
|
273
|
+
}
|
|
100
274
|
limit(count) {
|
|
101
275
|
this.limitValue = count;
|
|
102
276
|
return this;
|
|
@@ -109,7 +283,7 @@ export class Builder {
|
|
|
109
283
|
return this.offset((page - 1) * perPage).limit(perPage);
|
|
110
284
|
}
|
|
111
285
|
join(table, first, operator, second, type = "INNER") {
|
|
112
|
-
const joinSql = `${type} JOIN ${this.wrap(table)} ON ${this.wrap(first)} ${operator} ${this.wrap(second)}`;
|
|
286
|
+
const joinSql = `${type} JOIN ${this.grammar.wrap(table)} ON ${this.grammar.wrap(first)} ${operator} ${this.grammar.wrap(second)}`;
|
|
113
287
|
this.joins.push(joinSql);
|
|
114
288
|
return this;
|
|
115
289
|
}
|
|
@@ -119,6 +293,18 @@ export class Builder {
|
|
|
119
293
|
rightJoin(table, first, operator, second) {
|
|
120
294
|
return this.join(table, first, operator, second, "RIGHT");
|
|
121
295
|
}
|
|
296
|
+
crossJoin(table) {
|
|
297
|
+
this.joins.push(`CROSS JOIN ${this.grammar.wrap(table)}`);
|
|
298
|
+
return this;
|
|
299
|
+
}
|
|
300
|
+
union(query, all = false) {
|
|
301
|
+
const sql = typeof query === "string" ? query : query.toSql();
|
|
302
|
+
this.unions.push({ query: sql, all });
|
|
303
|
+
return this;
|
|
304
|
+
}
|
|
305
|
+
unionAll(query) {
|
|
306
|
+
return this.union(query, true);
|
|
307
|
+
}
|
|
122
308
|
with(...relations) {
|
|
123
309
|
this.eagerLoads.push(...relations);
|
|
124
310
|
return this;
|
|
@@ -154,6 +340,24 @@ export class Builder {
|
|
|
154
340
|
const result = scope.call(this.model, this, ...args);
|
|
155
341
|
return (result || this);
|
|
156
342
|
}
|
|
343
|
+
when(condition, callback, defaultCallback) {
|
|
344
|
+
if (condition) {
|
|
345
|
+
const result = callback(this);
|
|
346
|
+
return (result || this);
|
|
347
|
+
}
|
|
348
|
+
else if (defaultCallback) {
|
|
349
|
+
const result = defaultCallback(this);
|
|
350
|
+
return (result || this);
|
|
351
|
+
}
|
|
352
|
+
return this;
|
|
353
|
+
}
|
|
354
|
+
unless(condition, callback, defaultCallback) {
|
|
355
|
+
return this.when(!condition, callback, defaultCallback);
|
|
356
|
+
}
|
|
357
|
+
tap(callback) {
|
|
358
|
+
const result = callback(this);
|
|
359
|
+
return (result || this);
|
|
360
|
+
}
|
|
157
361
|
has(relationName, operator = ">=", count = 1, callback) {
|
|
158
362
|
if (typeof operator === "function") {
|
|
159
363
|
callback = operator;
|
|
@@ -167,7 +371,7 @@ export class Builder {
|
|
|
167
371
|
if ((operator === "<" || operator === "=") && count <= 0) {
|
|
168
372
|
return this.whereExists(relation.getRelationExistenceSql(this, callback), "and", true);
|
|
169
373
|
}
|
|
170
|
-
return this.whereRaw(`(${relation.getRelationCountSql(this, callback)}) ${operator} ${this.escape(count)}`);
|
|
374
|
+
return this.whereRaw(`(${relation.getRelationCountSql(this, callback)}) ${operator} ${this.grammar.escape(count)}`);
|
|
171
375
|
}
|
|
172
376
|
orHas(relationName, operator = ">=", count = 1, callback) {
|
|
173
377
|
if (typeof operator === "function") {
|
|
@@ -182,7 +386,7 @@ export class Builder {
|
|
|
182
386
|
if ((operator === "<" || operator === "=") && count <= 0) {
|
|
183
387
|
return this.whereExists(relation.getRelationExistenceSql(this, callback), "or", true);
|
|
184
388
|
}
|
|
185
|
-
return this.whereRaw(`(${relation.getRelationCountSql(this, callback)}) ${operator} ${this.escape(count)}`, "or");
|
|
389
|
+
return this.whereRaw(`(${relation.getRelationCountSql(this, callback)}) ${operator} ${this.grammar.escape(count)}`, "or");
|
|
186
390
|
}
|
|
187
391
|
whereHas(relationName, callback, operator = ">=", count = 1) {
|
|
188
392
|
return this.has(relationName, operator, count, callback);
|
|
@@ -220,6 +424,19 @@ export class Builder {
|
|
|
220
424
|
this.columns.push(...columns);
|
|
221
425
|
return this;
|
|
222
426
|
}
|
|
427
|
+
selectRaw(sql) {
|
|
428
|
+
this.columns.push(sql);
|
|
429
|
+
return this;
|
|
430
|
+
}
|
|
431
|
+
fromSub(query, as) {
|
|
432
|
+
const sql = typeof query === "string" ? query : query.toSql();
|
|
433
|
+
this.fromRaw = `(${sql}) AS ${this.grammar.wrap(as)}`;
|
|
434
|
+
return this;
|
|
435
|
+
}
|
|
436
|
+
updateFrom(table, first, operator, second) {
|
|
437
|
+
this.updateJoins.push(`INNER JOIN ${this.grammar.wrap(table)} ON ${this.grammar.wrap(first)} ${operator} ${this.grammar.wrap(second)}`);
|
|
438
|
+
return this;
|
|
439
|
+
}
|
|
223
440
|
clone() {
|
|
224
441
|
const cloned = new Builder(this.connection, this.tableName);
|
|
225
442
|
cloned.columns = [...this.columns];
|
|
@@ -233,89 +450,85 @@ export class Builder {
|
|
|
233
450
|
cloned.distinctFlag = this.distinctFlag;
|
|
234
451
|
cloned.model = this.model;
|
|
235
452
|
cloned.eagerLoads = [...this.eagerLoads];
|
|
453
|
+
cloned.randomOrderFlag = this.randomOrderFlag;
|
|
454
|
+
cloned.lockMode = this.lockMode;
|
|
455
|
+
cloned.unions = [...this.unions];
|
|
456
|
+
cloned.fromRaw = this.fromRaw;
|
|
457
|
+
cloned.updateJoins = [...this.updateJoins];
|
|
236
458
|
return cloned;
|
|
237
459
|
}
|
|
238
460
|
wrapColumn(value) {
|
|
239
|
-
return this.wrap(value);
|
|
461
|
+
return this.grammar.wrap(value);
|
|
240
462
|
}
|
|
241
463
|
escapeValue(value) {
|
|
242
|
-
return this.escape(value);
|
|
464
|
+
return this.grammar.escape(value);
|
|
243
465
|
}
|
|
244
|
-
|
|
245
|
-
if (
|
|
246
|
-
|
|
247
|
-
return `${this.wrap(column)} AS ${this.wrapValue(alias)}`;
|
|
466
|
+
compileWhereClause(where, prefix) {
|
|
467
|
+
if (where.type === "basic") {
|
|
468
|
+
return `${prefix} ${this.grammar.wrap(where.column)} ${where.operator} ${this.grammar.escape(where.value)}`;
|
|
248
469
|
}
|
|
249
|
-
if (
|
|
250
|
-
|
|
470
|
+
else if (where.type === "in") {
|
|
471
|
+
const op = where.operator === "NOT IN" ? "NOT IN" : "IN";
|
|
472
|
+
return `${prefix} ${this.grammar.wrap(where.column)} ${op} (${where.value.map((v) => this.grammar.escape(v)).join(", ")})`;
|
|
251
473
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
return value
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
if (
|
|
267
|
-
return
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
return `'${String(value).replace(/'/g, "''")}'`;
|
|
474
|
+
else if (where.type === "null") {
|
|
475
|
+
const op = where.operator === "NOT NULL" ? "IS NOT NULL" : "IS NULL";
|
|
476
|
+
return `${prefix} ${this.grammar.wrap(where.column)} ${op}`;
|
|
477
|
+
}
|
|
478
|
+
else if (where.type === "between") {
|
|
479
|
+
const op = where.operator === "NOT BETWEEN" ? "NOT BETWEEN" : "BETWEEN";
|
|
480
|
+
return `${prefix} ${this.grammar.wrap(where.column)} ${op} ${this.grammar.escape(where.value[0])} AND ${this.grammar.escape(where.value[1])}`;
|
|
481
|
+
}
|
|
482
|
+
else if (where.type === "raw") {
|
|
483
|
+
return `${prefix} ${where.column}`;
|
|
484
|
+
}
|
|
485
|
+
else if (where.type === "column") {
|
|
486
|
+
return `${prefix} ${this.grammar.wrap(where.column)} ${where.operator} ${this.grammar.wrap(where.value)}`;
|
|
487
|
+
}
|
|
488
|
+
else if (where.type === "exists") {
|
|
489
|
+
return `${prefix} ${where.operator} (${where.column})`;
|
|
490
|
+
}
|
|
491
|
+
return "";
|
|
271
492
|
}
|
|
272
493
|
compileWheres() {
|
|
273
494
|
if (this.wheres.length === 0)
|
|
274
495
|
return "";
|
|
275
496
|
const clauses = this.wheres.map((where, index) => {
|
|
276
497
|
const prefix = index === 0 ? "WHERE" : where.boolean.toUpperCase();
|
|
277
|
-
|
|
278
|
-
return `${prefix} ${this.wrap(where.column)} ${where.operator} ${this.escape(where.value)}`;
|
|
279
|
-
}
|
|
280
|
-
else if (where.type === "in") {
|
|
281
|
-
const op = where.operator === "NOT IN" ? "NOT IN" : "IN";
|
|
282
|
-
return `${prefix} ${this.wrap(where.column)} ${op} (${where.value.map((v) => this.escape(v)).join(", ")})`;
|
|
283
|
-
}
|
|
284
|
-
else if (where.type === "null") {
|
|
285
|
-
const op = where.operator === "NOT NULL" ? "IS NOT NULL" : "IS NULL";
|
|
286
|
-
return `${prefix} ${this.wrap(where.column)} ${op}`;
|
|
287
|
-
}
|
|
288
|
-
else if (where.type === "between") {
|
|
289
|
-
const op = where.operator === "NOT BETWEEN" ? "NOT BETWEEN" : "BETWEEN";
|
|
290
|
-
return `${prefix} ${this.wrap(where.column)} ${op} ${this.escape(where.value[0])} AND ${this.escape(where.value[1])}`;
|
|
291
|
-
}
|
|
292
|
-
else if (where.type === "raw") {
|
|
293
|
-
return `${prefix} ${where.column}`;
|
|
294
|
-
}
|
|
295
|
-
else if (where.type === "column") {
|
|
296
|
-
return `${prefix} ${this.wrap(where.column)} ${where.operator} ${this.wrap(where.value)}`;
|
|
297
|
-
}
|
|
298
|
-
else if (where.type === "exists") {
|
|
299
|
-
return `${prefix} ${where.operator} (${where.column})`;
|
|
300
|
-
}
|
|
301
|
-
return "";
|
|
498
|
+
return this.compileWhereClause(where, prefix);
|
|
302
499
|
});
|
|
303
500
|
return clauses.join(" ");
|
|
304
501
|
}
|
|
502
|
+
compileNestedWheres(builder) {
|
|
503
|
+
if (builder.wheres.length === 0)
|
|
504
|
+
return "";
|
|
505
|
+
const clauses = builder.wheres.map((where, index) => {
|
|
506
|
+
const prefix = index === 0 ? "" : where.boolean.toUpperCase();
|
|
507
|
+
return this.compileWhereClause(where, prefix);
|
|
508
|
+
});
|
|
509
|
+
return clauses.join(" ").trim();
|
|
510
|
+
}
|
|
305
511
|
compileOrders() {
|
|
512
|
+
if (this.randomOrderFlag) {
|
|
513
|
+
return this.grammar.compileRandomOrder();
|
|
514
|
+
}
|
|
306
515
|
if (this.orders.length === 0)
|
|
307
516
|
return "";
|
|
308
|
-
return `ORDER BY ${this.orders.map((o) => `${this.wrap(o.column)} ${o.direction.toUpperCase()}`).join(", ")}`;
|
|
517
|
+
return `ORDER BY ${this.orders.map((o) => `${this.grammar.wrap(o.column)} ${o.direction.toUpperCase()}`).join(", ")}`;
|
|
309
518
|
}
|
|
310
519
|
compileGroups() {
|
|
311
520
|
if (this.groups.length === 0)
|
|
312
521
|
return "";
|
|
313
|
-
return `GROUP BY ${this.groups.map((c) => this.wrap(c)).join(", ")}`;
|
|
522
|
+
return `GROUP BY ${this.groups.map((c) => this.grammar.wrap(c)).join(", ")}`;
|
|
314
523
|
}
|
|
315
524
|
compileHavings() {
|
|
316
525
|
if (this.havings.length === 0)
|
|
317
526
|
return "";
|
|
318
|
-
|
|
527
|
+
const clauses = this.havings.map((h, index) => {
|
|
528
|
+
const prefix = index === 0 ? "" : h.boolean.toUpperCase() + " ";
|
|
529
|
+
return prefix + h.sql;
|
|
530
|
+
});
|
|
531
|
+
return `HAVING ${clauses.join(" ")}`;
|
|
319
532
|
}
|
|
320
533
|
compileLimit() {
|
|
321
534
|
if (this.limitValue === undefined)
|
|
@@ -325,17 +538,18 @@ export class Builder {
|
|
|
325
538
|
compileOffset() {
|
|
326
539
|
if (this.offsetValue === undefined)
|
|
327
540
|
return "";
|
|
328
|
-
return
|
|
541
|
+
return this.grammar.compileOffset(this.offsetValue, this.limitValue);
|
|
329
542
|
}
|
|
330
543
|
compileColumns() {
|
|
331
|
-
return this.columns.map((c) => (this.isRawColumn(c) ? c : this.wrap(c))).join(", ");
|
|
544
|
+
return this.columns.map((c) => (this.isRawColumn(c) ? c : this.grammar.wrap(c))).join(", ");
|
|
332
545
|
}
|
|
333
546
|
isRawColumn(column) {
|
|
334
547
|
return column.includes("(") || /\s+as\s+/i.test(column) || /^[0-9]+$/.test(column);
|
|
335
548
|
}
|
|
336
549
|
toSql() {
|
|
337
550
|
const distinct = this.distinctFlag ? "DISTINCT " : "";
|
|
338
|
-
|
|
551
|
+
const from = this.fromRaw || this.grammar.wrap(this.tableName);
|
|
552
|
+
let sql = `SELECT ${distinct}${this.compileColumns()} FROM ${from}`;
|
|
339
553
|
if (this.joins.length > 0)
|
|
340
554
|
sql += " " + this.joins.join(" ");
|
|
341
555
|
sql += " " + this.compileWheres();
|
|
@@ -344,6 +558,10 @@ export class Builder {
|
|
|
344
558
|
sql += " " + this.compileOrders();
|
|
345
559
|
sql += " " + this.compileLimit();
|
|
346
560
|
sql += " " + this.compileOffset();
|
|
561
|
+
sql += this.grammar.compileLock(this.lockMode);
|
|
562
|
+
for (const union of this.unions) {
|
|
563
|
+
sql += ` UNION${union.all ? " ALL" : ""} ${union.query}`;
|
|
564
|
+
}
|
|
347
565
|
return sql.replace(/\s+/g, " ").trim();
|
|
348
566
|
}
|
|
349
567
|
async get() {
|
|
@@ -354,6 +572,9 @@ export class Builder {
|
|
|
354
572
|
const instance = new this.model(row);
|
|
355
573
|
instance.$exists = true;
|
|
356
574
|
instance.$original = { ...row };
|
|
575
|
+
if (typeof instance.setConnection === "function") {
|
|
576
|
+
instance.setConnection(this.connection);
|
|
577
|
+
}
|
|
357
578
|
return instance;
|
|
358
579
|
});
|
|
359
580
|
if (this.eagerLoads.length > 0) {
|
|
@@ -370,6 +591,54 @@ export class Builder {
|
|
|
370
591
|
async find(id, column = "id") {
|
|
371
592
|
return this.where(column, id).first();
|
|
372
593
|
}
|
|
594
|
+
async findOrFail(id, column = "id") {
|
|
595
|
+
const result = await this.find(id, column);
|
|
596
|
+
if (!result) {
|
|
597
|
+
throw new ModelNotFoundError(this.model?.name || "Model", id);
|
|
598
|
+
}
|
|
599
|
+
return result;
|
|
600
|
+
}
|
|
601
|
+
async firstOrFail() {
|
|
602
|
+
const result = await this.first();
|
|
603
|
+
if (!result) {
|
|
604
|
+
throw new ModelNotFoundError(this.model?.name || "Model");
|
|
605
|
+
}
|
|
606
|
+
return result;
|
|
607
|
+
}
|
|
608
|
+
async firstOrCreate(attributes = {}, values = {}) {
|
|
609
|
+
const found = await this.clone().where(attributes).first();
|
|
610
|
+
if (found)
|
|
611
|
+
return found;
|
|
612
|
+
if (!this.model) {
|
|
613
|
+
throw new Error("firstOrCreate requires a model to be set on the builder");
|
|
614
|
+
}
|
|
615
|
+
const instance = new this.model({ ...attributes, ...values });
|
|
616
|
+
if (typeof instance.setConnection === "function") {
|
|
617
|
+
instance.setConnection(this.connection);
|
|
618
|
+
}
|
|
619
|
+
await instance.save();
|
|
620
|
+
return instance;
|
|
621
|
+
}
|
|
622
|
+
async updateOrCreate(attributes, values = {}) {
|
|
623
|
+
const found = await this.clone().where(attributes).first();
|
|
624
|
+
if (found) {
|
|
625
|
+
const model = found;
|
|
626
|
+
if (typeof model.fill === "function") {
|
|
627
|
+
model.fill(values);
|
|
628
|
+
await model.save();
|
|
629
|
+
}
|
|
630
|
+
return found;
|
|
631
|
+
}
|
|
632
|
+
if (!this.model) {
|
|
633
|
+
throw new Error("updateOrCreate requires a model to be set on the builder");
|
|
634
|
+
}
|
|
635
|
+
const instance = new this.model({ ...attributes, ...values });
|
|
636
|
+
if (typeof instance.setConnection === "function") {
|
|
637
|
+
instance.setConnection(this.connection);
|
|
638
|
+
}
|
|
639
|
+
await instance.save();
|
|
640
|
+
return instance;
|
|
641
|
+
}
|
|
373
642
|
async pluck(column) {
|
|
374
643
|
const results = await this.select(column).get();
|
|
375
644
|
return results.map((row) => row[column]);
|
|
@@ -409,32 +678,110 @@ export class Builder {
|
|
|
409
678
|
to: total === 0 ? 0 : Math.min(page * perPage, total),
|
|
410
679
|
};
|
|
411
680
|
}
|
|
681
|
+
async chunk(count, callback) {
|
|
682
|
+
let page = 1;
|
|
683
|
+
while (true) {
|
|
684
|
+
const items = await this.clone().forPage(page, count).get();
|
|
685
|
+
if (items.length === 0)
|
|
686
|
+
break;
|
|
687
|
+
await callback(items);
|
|
688
|
+
if (items.length < count)
|
|
689
|
+
break;
|
|
690
|
+
page++;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
async each(count, callback) {
|
|
694
|
+
await this.chunk(count, async (items) => {
|
|
695
|
+
for (const item of items) {
|
|
696
|
+
await callback(item);
|
|
697
|
+
}
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
async *cursor() {
|
|
701
|
+
let offset = 0;
|
|
702
|
+
while (true) {
|
|
703
|
+
const items = await this.clone().offset(offset).limit(1).get();
|
|
704
|
+
if (items.length === 0)
|
|
705
|
+
break;
|
|
706
|
+
yield items[0];
|
|
707
|
+
offset++;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
async *lazy(count = 1000) {
|
|
711
|
+
let page = 1;
|
|
712
|
+
while (true) {
|
|
713
|
+
const items = await this.clone().forPage(page, count).get();
|
|
714
|
+
if (items.length === 0)
|
|
715
|
+
break;
|
|
716
|
+
for (const item of items) {
|
|
717
|
+
yield item;
|
|
718
|
+
}
|
|
719
|
+
if (items.length < count)
|
|
720
|
+
break;
|
|
721
|
+
page++;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
412
724
|
async insert(data) {
|
|
413
725
|
const records = Array.isArray(data) ? data : [data];
|
|
414
726
|
if (records.length === 0)
|
|
415
727
|
return;
|
|
416
728
|
const columns = Object.keys(records[0]);
|
|
417
729
|
const values = records.map((record) => {
|
|
418
|
-
return `(${columns.map((col) => this.escape(record[col])).join(", ")})`;
|
|
730
|
+
return `(${columns.map((col) => this.grammar.escape(record[col])).join(", ")})`;
|
|
419
731
|
});
|
|
420
|
-
const sql = `INSERT INTO ${this.wrap(this.tableName)} (${columns.map((c) => this.wrap(c)).join(", ")}) VALUES ${values.join(", ")}`;
|
|
732
|
+
const sql = `INSERT INTO ${this.grammar.wrap(this.tableName)} (${columns.map((c) => this.grammar.wrap(c)).join(", ")}) VALUES ${values.join(", ")}`;
|
|
421
733
|
return await this.connection.run(sql);
|
|
422
734
|
}
|
|
423
735
|
async insertGetId(data, idColumn = "id") {
|
|
424
736
|
const result = await this.insert(data);
|
|
425
737
|
return result?.lastInsertRowid ?? result?.insertId ?? null;
|
|
426
738
|
}
|
|
739
|
+
async insertOrIgnore(data) {
|
|
740
|
+
const records = Array.isArray(data) ? data : [data];
|
|
741
|
+
if (records.length === 0)
|
|
742
|
+
return;
|
|
743
|
+
const columns = Object.keys(records[0]);
|
|
744
|
+
const values = records.map((record) => {
|
|
745
|
+
return `(${columns.map((col) => this.grammar.escape(record[col])).join(", ")})`;
|
|
746
|
+
});
|
|
747
|
+
const sql = this.grammar.compileInsertOrIgnore(this.grammar.wrap(this.tableName), columns, values);
|
|
748
|
+
return await this.connection.run(sql);
|
|
749
|
+
}
|
|
750
|
+
async upsert(data, uniqueBy, updateColumns) {
|
|
751
|
+
const records = Array.isArray(data) ? data : [data];
|
|
752
|
+
if (records.length === 0)
|
|
753
|
+
return;
|
|
754
|
+
const columns = Object.keys(records[0]);
|
|
755
|
+
const values = records.map((record) => {
|
|
756
|
+
return `(${columns.map((col) => this.grammar.escape(record[col])).join(", ")})`;
|
|
757
|
+
});
|
|
758
|
+
const uniqueCols = Array.isArray(uniqueBy) ? uniqueBy : [uniqueBy];
|
|
759
|
+
const updateCols = updateColumns ?? columns.filter((c) => !uniqueCols.includes(c));
|
|
760
|
+
const sql = this.grammar.compileUpsert(this.grammar.wrap(this.tableName), columns, values, uniqueCols, updateCols);
|
|
761
|
+
return await this.connection.run(sql);
|
|
762
|
+
}
|
|
427
763
|
async update(data) {
|
|
428
764
|
const sets = Object.entries(data)
|
|
429
|
-
.map(([key, value]) => `${this.wrap(key)} = ${this.escape(value)}`)
|
|
765
|
+
.map(([key, value]) => `${this.grammar.wrap(key)} = ${this.grammar.escape(value)}`)
|
|
430
766
|
.join(", ");
|
|
431
|
-
const sql =
|
|
432
|
-
return await this.connection.run(sql
|
|
767
|
+
const sql = this.grammar.compileUpdate(this.grammar.wrap(this.tableName), sets.split(", "), this.compileWheres(), this.updateJoins);
|
|
768
|
+
return await this.connection.run(sql);
|
|
433
769
|
}
|
|
434
770
|
async delete() {
|
|
435
|
-
const sql =
|
|
771
|
+
const sql = this.grammar.compileDelete(this.grammar.wrap(this.tableName), this.compileWheres(), this.updateJoins, this.limitValue);
|
|
772
|
+
return await this.connection.run(sql);
|
|
773
|
+
}
|
|
774
|
+
async increment(column, amount = 1, extra = {}) {
|
|
775
|
+
const sets = [`${this.grammar.wrap(column)} = ${this.grammar.wrap(column)} + ${amount}`];
|
|
776
|
+
for (const [key, value] of Object.entries(extra)) {
|
|
777
|
+
sets.push(`${this.grammar.wrap(key)} = ${this.grammar.escape(value)}`);
|
|
778
|
+
}
|
|
779
|
+
const sql = `UPDATE ${this.grammar.wrap(this.tableName)} SET ${sets.join(", ")} ${this.compileWheres()}`;
|
|
436
780
|
return await this.connection.run(sql.trim());
|
|
437
781
|
}
|
|
782
|
+
async decrement(column, amount = 1, extra = {}) {
|
|
783
|
+
return this.increment(column, -amount, extra);
|
|
784
|
+
}
|
|
438
785
|
async restore() {
|
|
439
786
|
const model = this.model;
|
|
440
787
|
if (!model?.softDeletes) {
|
|
@@ -446,6 +793,80 @@ export class Builder {
|
|
|
446
793
|
const result = await this.select("1 as exists_check").limit(1).get();
|
|
447
794
|
return result.length > 0;
|
|
448
795
|
}
|
|
796
|
+
async doesntExist() {
|
|
797
|
+
return !(await this.exists());
|
|
798
|
+
}
|
|
799
|
+
async sole() {
|
|
800
|
+
const results = await this.limit(2).get();
|
|
801
|
+
if (results.length === 0) {
|
|
802
|
+
throw new ModelNotFoundError(this.model?.name || "Model");
|
|
803
|
+
}
|
|
804
|
+
if (results.length > 1) {
|
|
805
|
+
throw new Error("Multiple records found when only one was expected.");
|
|
806
|
+
}
|
|
807
|
+
return results[0];
|
|
808
|
+
}
|
|
809
|
+
async value(column) {
|
|
810
|
+
const result = await this.first();
|
|
811
|
+
return result ? result[column] : null;
|
|
812
|
+
}
|
|
813
|
+
dump() {
|
|
814
|
+
console.log(this.toSql());
|
|
815
|
+
return this;
|
|
816
|
+
}
|
|
817
|
+
dd() {
|
|
818
|
+
console.log(this.toSql());
|
|
819
|
+
throw new Error("dd() called — execution halted.");
|
|
820
|
+
}
|
|
821
|
+
async explain() {
|
|
822
|
+
const sql = this.grammar.compileExplain(this.toSql());
|
|
823
|
+
const results = await this.connection.query(sql);
|
|
824
|
+
return Array.from(results);
|
|
825
|
+
}
|
|
826
|
+
take(count) {
|
|
827
|
+
return this.limit(count);
|
|
828
|
+
}
|
|
829
|
+
skip(count) {
|
|
830
|
+
return this.offset(count);
|
|
831
|
+
}
|
|
832
|
+
lockForUpdate() {
|
|
833
|
+
const driver = this.connection.getDriverName();
|
|
834
|
+
if (driver !== "sqlite") {
|
|
835
|
+
this.lockMode = "FOR UPDATE";
|
|
836
|
+
}
|
|
837
|
+
return this;
|
|
838
|
+
}
|
|
839
|
+
sharedLock() {
|
|
840
|
+
const driver = this.connection.getDriverName();
|
|
841
|
+
if (driver === "mysql") {
|
|
842
|
+
this.lockMode = "LOCK IN SHARE MODE";
|
|
843
|
+
}
|
|
844
|
+
else if (driver === "postgres") {
|
|
845
|
+
this.lockMode = "FOR SHARE";
|
|
846
|
+
}
|
|
847
|
+
return this;
|
|
848
|
+
}
|
|
849
|
+
skipLocked() {
|
|
850
|
+
if (this.lockMode) {
|
|
851
|
+
this.lockMode += " SKIP LOCKED";
|
|
852
|
+
}
|
|
853
|
+
return this;
|
|
854
|
+
}
|
|
855
|
+
noWait() {
|
|
856
|
+
if (this.lockMode) {
|
|
857
|
+
this.lockMode += " NOWAIT";
|
|
858
|
+
}
|
|
859
|
+
return this;
|
|
860
|
+
}
|
|
861
|
+
addDateWhere(type, column, operator, value, boolean = "and") {
|
|
862
|
+
if (value === undefined) {
|
|
863
|
+
value = operator;
|
|
864
|
+
operator = "=";
|
|
865
|
+
}
|
|
866
|
+
const sql = this.grammar.compileDateWhere(type, this.grammar.wrap(column), operator, value);
|
|
867
|
+
this.wheres.push({ type: "raw", column: sql, boolean, scope: undefined });
|
|
868
|
+
return this;
|
|
869
|
+
}
|
|
449
870
|
getModelRelation(relationName) {
|
|
450
871
|
if (!this.model) {
|
|
451
872
|
throw new Error(`Cannot query relation "${relationName}" without a model`);
|