@bunnykit/orm 0.1.5 → 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 +381 -82
- 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 +6 -0
- package/dist/src/index.js +3 -0
- package/dist/src/model/BelongsToMany.js +9 -6
- package/dist/src/model/Model.d.ts +5 -0
- package/dist/src/model/Model.js +57 -20
- package/dist/src/model/MorphRelations.js +10 -10
- package/dist/src/query/Builder.d.ts +44 -5
- package/dist/src/query/Builder.js +252 -113
- 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
|
@@ -15,10 +15,16 @@ export class Builder {
|
|
|
15
15
|
eagerLoads = [];
|
|
16
16
|
randomOrderFlag = false;
|
|
17
17
|
lockMode;
|
|
18
|
+
unions = [];
|
|
19
|
+
fromRaw;
|
|
20
|
+
updateJoins = [];
|
|
18
21
|
constructor(connection, table) {
|
|
19
22
|
this.connection = connection;
|
|
20
23
|
this.tableName = table;
|
|
21
24
|
}
|
|
25
|
+
get grammar() {
|
|
26
|
+
return this.connection.getGrammar();
|
|
27
|
+
}
|
|
22
28
|
setModel(model) {
|
|
23
29
|
this.model = model;
|
|
24
30
|
return this;
|
|
@@ -142,6 +148,85 @@ export class Builder {
|
|
|
142
148
|
this.wheres.push({ type: "exists", column: sql, boolean, operator: not ? "NOT EXISTS" : "EXISTS" });
|
|
143
149
|
return this;
|
|
144
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
|
+
}
|
|
145
230
|
orderBy(column, direction = "asc") {
|
|
146
231
|
this.orders.push({ column, direction });
|
|
147
232
|
return this;
|
|
@@ -156,14 +241,36 @@ export class Builder {
|
|
|
156
241
|
this.randomOrderFlag = true;
|
|
157
242
|
return this;
|
|
158
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
|
+
}
|
|
159
255
|
groupBy(...columns) {
|
|
160
256
|
this.groups.push(...columns);
|
|
161
257
|
return this;
|
|
162
258
|
}
|
|
163
259
|
having(column, operator, value) {
|
|
164
|
-
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" });
|
|
165
261
|
return this;
|
|
166
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 });
|
|
269
|
+
return this;
|
|
270
|
+
}
|
|
271
|
+
orHavingRaw(sql) {
|
|
272
|
+
return this.havingRaw(sql, "or");
|
|
273
|
+
}
|
|
167
274
|
limit(count) {
|
|
168
275
|
this.limitValue = count;
|
|
169
276
|
return this;
|
|
@@ -176,7 +283,7 @@ export class Builder {
|
|
|
176
283
|
return this.offset((page - 1) * perPage).limit(perPage);
|
|
177
284
|
}
|
|
178
285
|
join(table, first, operator, second, type = "INNER") {
|
|
179
|
-
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)}`;
|
|
180
287
|
this.joins.push(joinSql);
|
|
181
288
|
return this;
|
|
182
289
|
}
|
|
@@ -186,6 +293,18 @@ export class Builder {
|
|
|
186
293
|
rightJoin(table, first, operator, second) {
|
|
187
294
|
return this.join(table, first, operator, second, "RIGHT");
|
|
188
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
|
+
}
|
|
189
308
|
with(...relations) {
|
|
190
309
|
this.eagerLoads.push(...relations);
|
|
191
310
|
return this;
|
|
@@ -252,7 +371,7 @@ export class Builder {
|
|
|
252
371
|
if ((operator === "<" || operator === "=") && count <= 0) {
|
|
253
372
|
return this.whereExists(relation.getRelationExistenceSql(this, callback), "and", true);
|
|
254
373
|
}
|
|
255
|
-
return this.whereRaw(`(${relation.getRelationCountSql(this, callback)}) ${operator} ${this.escape(count)}`);
|
|
374
|
+
return this.whereRaw(`(${relation.getRelationCountSql(this, callback)}) ${operator} ${this.grammar.escape(count)}`);
|
|
256
375
|
}
|
|
257
376
|
orHas(relationName, operator = ">=", count = 1, callback) {
|
|
258
377
|
if (typeof operator === "function") {
|
|
@@ -267,7 +386,7 @@ export class Builder {
|
|
|
267
386
|
if ((operator === "<" || operator === "=") && count <= 0) {
|
|
268
387
|
return this.whereExists(relation.getRelationExistenceSql(this, callback), "or", true);
|
|
269
388
|
}
|
|
270
|
-
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");
|
|
271
390
|
}
|
|
272
391
|
whereHas(relationName, callback, operator = ">=", count = 1) {
|
|
273
392
|
return this.has(relationName, operator, count, callback);
|
|
@@ -305,6 +424,19 @@ export class Builder {
|
|
|
305
424
|
this.columns.push(...columns);
|
|
306
425
|
return this;
|
|
307
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
|
+
}
|
|
308
440
|
clone() {
|
|
309
441
|
const cloned = new Builder(this.connection, this.tableName);
|
|
310
442
|
cloned.columns = [...this.columns];
|
|
@@ -320,63 +452,38 @@ export class Builder {
|
|
|
320
452
|
cloned.eagerLoads = [...this.eagerLoads];
|
|
321
453
|
cloned.randomOrderFlag = this.randomOrderFlag;
|
|
322
454
|
cloned.lockMode = this.lockMode;
|
|
455
|
+
cloned.unions = [...this.unions];
|
|
456
|
+
cloned.fromRaw = this.fromRaw;
|
|
457
|
+
cloned.updateJoins = [...this.updateJoins];
|
|
323
458
|
return cloned;
|
|
324
459
|
}
|
|
325
460
|
wrapColumn(value) {
|
|
326
|
-
return this.wrap(value);
|
|
461
|
+
return this.grammar.wrap(value);
|
|
327
462
|
}
|
|
328
463
|
escapeValue(value) {
|
|
329
|
-
return this.escape(value);
|
|
330
|
-
}
|
|
331
|
-
wrap(value) {
|
|
332
|
-
if (value.includes(" as ")) {
|
|
333
|
-
const [column, alias] = value.split(/\s+as\s+/i);
|
|
334
|
-
return `${this.wrap(column)} AS ${this.wrapValue(alias)}`;
|
|
335
|
-
}
|
|
336
|
-
if (value.includes(".")) {
|
|
337
|
-
return value.split(".").map((v) => this.wrapValue(v)).join(".");
|
|
338
|
-
}
|
|
339
|
-
return this.wrapValue(value);
|
|
340
|
-
}
|
|
341
|
-
wrapValue(value) {
|
|
342
|
-
const driver = this.connection.getDriverName();
|
|
343
|
-
const char = driver === "mysql" ? "`" : '"';
|
|
344
|
-
if (value === "*")
|
|
345
|
-
return value;
|
|
346
|
-
return `${char}${value}${char}`;
|
|
347
|
-
}
|
|
348
|
-
escape(value) {
|
|
349
|
-
if (value === null)
|
|
350
|
-
return "NULL";
|
|
351
|
-
if (typeof value === "boolean")
|
|
352
|
-
return value ? "1" : "0";
|
|
353
|
-
if (typeof value === "number")
|
|
354
|
-
return String(value);
|
|
355
|
-
if (typeof value === "string" && value.toUpperCase().includes("CURRENT_TIMESTAMP"))
|
|
356
|
-
return value;
|
|
357
|
-
return `'${String(value).replace(/'/g, "''")}'`;
|
|
464
|
+
return this.grammar.escape(value);
|
|
358
465
|
}
|
|
359
466
|
compileWhereClause(where, prefix) {
|
|
360
467
|
if (where.type === "basic") {
|
|
361
|
-
return `${prefix} ${this.wrap(where.column)} ${where.operator} ${this.escape(where.value)}`;
|
|
468
|
+
return `${prefix} ${this.grammar.wrap(where.column)} ${where.operator} ${this.grammar.escape(where.value)}`;
|
|
362
469
|
}
|
|
363
470
|
else if (where.type === "in") {
|
|
364
471
|
const op = where.operator === "NOT IN" ? "NOT IN" : "IN";
|
|
365
|
-
return `${prefix} ${this.wrap(where.column)} ${op} (${where.value.map((v) => this.escape(v)).join(", ")})`;
|
|
472
|
+
return `${prefix} ${this.grammar.wrap(where.column)} ${op} (${where.value.map((v) => this.grammar.escape(v)).join(", ")})`;
|
|
366
473
|
}
|
|
367
474
|
else if (where.type === "null") {
|
|
368
475
|
const op = where.operator === "NOT NULL" ? "IS NOT NULL" : "IS NULL";
|
|
369
|
-
return `${prefix} ${this.wrap(where.column)} ${op}`;
|
|
476
|
+
return `${prefix} ${this.grammar.wrap(where.column)} ${op}`;
|
|
370
477
|
}
|
|
371
478
|
else if (where.type === "between") {
|
|
372
479
|
const op = where.operator === "NOT BETWEEN" ? "NOT BETWEEN" : "BETWEEN";
|
|
373
|
-
return `${prefix} ${this.wrap(where.column)} ${op} ${this.escape(where.value[0])} AND ${this.escape(where.value[1])}`;
|
|
480
|
+
return `${prefix} ${this.grammar.wrap(where.column)} ${op} ${this.grammar.escape(where.value[0])} AND ${this.grammar.escape(where.value[1])}`;
|
|
374
481
|
}
|
|
375
482
|
else if (where.type === "raw") {
|
|
376
483
|
return `${prefix} ${where.column}`;
|
|
377
484
|
}
|
|
378
485
|
else if (where.type === "column") {
|
|
379
|
-
return `${prefix} ${this.wrap(where.column)} ${where.operator} ${this.wrap(where.value)}`;
|
|
486
|
+
return `${prefix} ${this.grammar.wrap(where.column)} ${where.operator} ${this.grammar.wrap(where.value)}`;
|
|
380
487
|
}
|
|
381
488
|
else if (where.type === "exists") {
|
|
382
489
|
return `${prefix} ${where.operator} (${where.column})`;
|
|
@@ -403,23 +510,25 @@ export class Builder {
|
|
|
403
510
|
}
|
|
404
511
|
compileOrders() {
|
|
405
512
|
if (this.randomOrderFlag) {
|
|
406
|
-
|
|
407
|
-
const fn = driver === "mysql" ? "RAND()" : "RANDOM()";
|
|
408
|
-
return `ORDER BY ${fn}`;
|
|
513
|
+
return this.grammar.compileRandomOrder();
|
|
409
514
|
}
|
|
410
515
|
if (this.orders.length === 0)
|
|
411
516
|
return "";
|
|
412
|
-
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(", ")}`;
|
|
413
518
|
}
|
|
414
519
|
compileGroups() {
|
|
415
520
|
if (this.groups.length === 0)
|
|
416
521
|
return "";
|
|
417
|
-
return `GROUP BY ${this.groups.map((c) => this.wrap(c)).join(", ")}`;
|
|
522
|
+
return `GROUP BY ${this.groups.map((c) => this.grammar.wrap(c)).join(", ")}`;
|
|
418
523
|
}
|
|
419
524
|
compileHavings() {
|
|
420
525
|
if (this.havings.length === 0)
|
|
421
526
|
return "";
|
|
422
|
-
|
|
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(" ")}`;
|
|
423
532
|
}
|
|
424
533
|
compileLimit() {
|
|
425
534
|
if (this.limitValue === undefined)
|
|
@@ -429,20 +538,18 @@ export class Builder {
|
|
|
429
538
|
compileOffset() {
|
|
430
539
|
if (this.offsetValue === undefined)
|
|
431
540
|
return "";
|
|
432
|
-
|
|
433
|
-
? "LIMIT -1 "
|
|
434
|
-
: "";
|
|
435
|
-
return `${limitSql}OFFSET ${this.offsetValue}`;
|
|
541
|
+
return this.grammar.compileOffset(this.offsetValue, this.limitValue);
|
|
436
542
|
}
|
|
437
543
|
compileColumns() {
|
|
438
|
-
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(", ");
|
|
439
545
|
}
|
|
440
546
|
isRawColumn(column) {
|
|
441
547
|
return column.includes("(") || /\s+as\s+/i.test(column) || /^[0-9]+$/.test(column);
|
|
442
548
|
}
|
|
443
549
|
toSql() {
|
|
444
550
|
const distinct = this.distinctFlag ? "DISTINCT " : "";
|
|
445
|
-
|
|
551
|
+
const from = this.fromRaw || this.grammar.wrap(this.tableName);
|
|
552
|
+
let sql = `SELECT ${distinct}${this.compileColumns()} FROM ${from}`;
|
|
446
553
|
if (this.joins.length > 0)
|
|
447
554
|
sql += " " + this.joins.join(" ");
|
|
448
555
|
sql += " " + this.compileWheres();
|
|
@@ -451,8 +558,10 @@ export class Builder {
|
|
|
451
558
|
sql += " " + this.compileOrders();
|
|
452
559
|
sql += " " + this.compileLimit();
|
|
453
560
|
sql += " " + this.compileOffset();
|
|
454
|
-
|
|
455
|
-
|
|
561
|
+
sql += this.grammar.compileLock(this.lockMode);
|
|
562
|
+
for (const union of this.unions) {
|
|
563
|
+
sql += ` UNION${union.all ? " ALL" : ""} ${union.query}`;
|
|
564
|
+
}
|
|
456
565
|
return sql.replace(/\s+/g, " ").trim();
|
|
457
566
|
}
|
|
458
567
|
async get() {
|
|
@@ -463,6 +572,9 @@ export class Builder {
|
|
|
463
572
|
const instance = new this.model(row);
|
|
464
573
|
instance.$exists = true;
|
|
465
574
|
instance.$original = { ...row };
|
|
575
|
+
if (typeof instance.setConnection === "function") {
|
|
576
|
+
instance.setConnection(this.connection);
|
|
577
|
+
}
|
|
466
578
|
return instance;
|
|
467
579
|
});
|
|
468
580
|
if (this.eagerLoads.length > 0) {
|
|
@@ -500,7 +612,12 @@ export class Builder {
|
|
|
500
612
|
if (!this.model) {
|
|
501
613
|
throw new Error("firstOrCreate requires a model to be set on the builder");
|
|
502
614
|
}
|
|
503
|
-
|
|
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;
|
|
504
621
|
}
|
|
505
622
|
async updateOrCreate(attributes, values = {}) {
|
|
506
623
|
const found = await this.clone().where(attributes).first();
|
|
@@ -515,7 +632,12 @@ export class Builder {
|
|
|
515
632
|
if (!this.model) {
|
|
516
633
|
throw new Error("updateOrCreate requires a model to be set on the builder");
|
|
517
634
|
}
|
|
518
|
-
|
|
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;
|
|
519
641
|
}
|
|
520
642
|
async pluck(column) {
|
|
521
643
|
const results = await this.select(column).get();
|
|
@@ -605,32 +727,56 @@ export class Builder {
|
|
|
605
727
|
return;
|
|
606
728
|
const columns = Object.keys(records[0]);
|
|
607
729
|
const values = records.map((record) => {
|
|
608
|
-
return `(${columns.map((col) => this.escape(record[col])).join(", ")})`;
|
|
730
|
+
return `(${columns.map((col) => this.grammar.escape(record[col])).join(", ")})`;
|
|
609
731
|
});
|
|
610
|
-
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(", ")}`;
|
|
611
733
|
return await this.connection.run(sql);
|
|
612
734
|
}
|
|
613
735
|
async insertGetId(data, idColumn = "id") {
|
|
614
736
|
const result = await this.insert(data);
|
|
615
737
|
return result?.lastInsertRowid ?? result?.insertId ?? null;
|
|
616
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
|
+
}
|
|
617
763
|
async update(data) {
|
|
618
764
|
const sets = Object.entries(data)
|
|
619
|
-
.map(([key, value]) => `${this.wrap(key)} = ${this.escape(value)}`)
|
|
765
|
+
.map(([key, value]) => `${this.grammar.wrap(key)} = ${this.grammar.escape(value)}`)
|
|
620
766
|
.join(", ");
|
|
621
|
-
const sql =
|
|
622
|
-
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);
|
|
623
769
|
}
|
|
624
770
|
async delete() {
|
|
625
|
-
const sql =
|
|
626
|
-
return await this.connection.run(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);
|
|
627
773
|
}
|
|
628
774
|
async increment(column, amount = 1, extra = {}) {
|
|
629
|
-
const sets = [`${this.wrap(column)} = ${this.wrap(column)} + ${amount}`];
|
|
775
|
+
const sets = [`${this.grammar.wrap(column)} = ${this.grammar.wrap(column)} + ${amount}`];
|
|
630
776
|
for (const [key, value] of Object.entries(extra)) {
|
|
631
|
-
sets.push(`${this.wrap(key)} = ${this.escape(value)}`);
|
|
777
|
+
sets.push(`${this.grammar.wrap(key)} = ${this.grammar.escape(value)}`);
|
|
632
778
|
}
|
|
633
|
-
const sql = `UPDATE ${this.wrap(this.tableName)} SET ${sets.join(", ")} ${this.compileWheres()}`;
|
|
779
|
+
const sql = `UPDATE ${this.grammar.wrap(this.tableName)} SET ${sets.join(", ")} ${this.compileWheres()}`;
|
|
634
780
|
return await this.connection.run(sql.trim());
|
|
635
781
|
}
|
|
636
782
|
async decrement(column, amount = 1, extra = {}) {
|
|
@@ -650,6 +796,33 @@ export class Builder {
|
|
|
650
796
|
async doesntExist() {
|
|
651
797
|
return !(await this.exists());
|
|
652
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
|
+
}
|
|
653
826
|
take(count) {
|
|
654
827
|
return this.limit(count);
|
|
655
828
|
}
|
|
@@ -659,7 +832,7 @@ export class Builder {
|
|
|
659
832
|
lockForUpdate() {
|
|
660
833
|
const driver = this.connection.getDriverName();
|
|
661
834
|
if (driver !== "sqlite") {
|
|
662
|
-
this.lockMode =
|
|
835
|
+
this.lockMode = "FOR UPDATE";
|
|
663
836
|
}
|
|
664
837
|
return this;
|
|
665
838
|
}
|
|
@@ -673,58 +846,24 @@ export class Builder {
|
|
|
673
846
|
}
|
|
674
847
|
return this;
|
|
675
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
|
+
}
|
|
676
861
|
addDateWhere(type, column, operator, value, boolean = "and") {
|
|
677
862
|
if (value === undefined) {
|
|
678
863
|
value = operator;
|
|
679
864
|
operator = "=";
|
|
680
865
|
}
|
|
681
|
-
const
|
|
682
|
-
const wrapped = this.wrap(column);
|
|
683
|
-
let sql;
|
|
684
|
-
switch (type) {
|
|
685
|
-
case "date":
|
|
686
|
-
if (driver === "sqlite")
|
|
687
|
-
sql = `date(${wrapped}) ${operator} ${this.escape(value)}`;
|
|
688
|
-
else if (driver === "mysql")
|
|
689
|
-
sql = `DATE(${wrapped}) ${operator} ${this.escape(value)}`;
|
|
690
|
-
else
|
|
691
|
-
sql = `(${wrapped})::date ${operator} ${this.escape(value)}`;
|
|
692
|
-
break;
|
|
693
|
-
case "day":
|
|
694
|
-
if (driver === "sqlite")
|
|
695
|
-
sql = `CAST(strftime('%d', ${wrapped}) AS INTEGER) ${operator} ${this.escape(value)}`;
|
|
696
|
-
else if (driver === "mysql")
|
|
697
|
-
sql = `DAY(${wrapped}) ${operator} ${this.escape(value)}`;
|
|
698
|
-
else
|
|
699
|
-
sql = `EXTRACT(DAY FROM ${wrapped}) ${operator} ${this.escape(value)}`;
|
|
700
|
-
break;
|
|
701
|
-
case "month":
|
|
702
|
-
if (driver === "sqlite")
|
|
703
|
-
sql = `CAST(strftime('%m', ${wrapped}) AS INTEGER) ${operator} ${this.escape(value)}`;
|
|
704
|
-
else if (driver === "mysql")
|
|
705
|
-
sql = `MONTH(${wrapped}) ${operator} ${this.escape(value)}`;
|
|
706
|
-
else
|
|
707
|
-
sql = `EXTRACT(MONTH FROM ${wrapped}) ${operator} ${this.escape(value)}`;
|
|
708
|
-
break;
|
|
709
|
-
case "year":
|
|
710
|
-
if (driver === "sqlite")
|
|
711
|
-
sql = `CAST(strftime('%Y', ${wrapped}) AS INTEGER) ${operator} ${this.escape(value)}`;
|
|
712
|
-
else if (driver === "mysql")
|
|
713
|
-
sql = `YEAR(${wrapped}) ${operator} ${this.escape(value)}`;
|
|
714
|
-
else
|
|
715
|
-
sql = `EXTRACT(YEAR FROM ${wrapped}) ${operator} ${this.escape(value)}`;
|
|
716
|
-
break;
|
|
717
|
-
case "time":
|
|
718
|
-
if (driver === "sqlite")
|
|
719
|
-
sql = `time(${wrapped}) ${operator} ${this.escape(value)}`;
|
|
720
|
-
else if (driver === "mysql")
|
|
721
|
-
sql = `TIME(${wrapped}) ${operator} ${this.escape(value)}`;
|
|
722
|
-
else
|
|
723
|
-
sql = `(${wrapped})::time ${operator} ${this.escape(value)}`;
|
|
724
|
-
break;
|
|
725
|
-
default:
|
|
726
|
-
sql = `${wrapped} ${operator} ${this.escape(value)}`;
|
|
727
|
-
}
|
|
866
|
+
const sql = this.grammar.compileDateWhere(type, this.grammar.wrap(column), operator, value);
|
|
728
867
|
this.wheres.push({ type: "raw", column: sql, boolean, scope: undefined });
|
|
729
868
|
return this;
|
|
730
869
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare abstract class Grammar {
|
|
2
|
+
abstract wrap(value: string): string;
|
|
3
|
+
wrapArray(values: string[]): string[];
|
|
4
|
+
escape(value: any): string;
|
|
5
|
+
abstract compileRandomOrder(): string;
|
|
6
|
+
compileOffset(offset: number, _limit?: number): string;
|
|
7
|
+
compileLock(lockMode?: string): string;
|
|
8
|
+
abstract compileDateWhere(type: string, column: string, operator: string, value: any): string;
|
|
9
|
+
abstract compileInsertOrIgnore(table: string, columns: string[], values: string[]): string;
|
|
10
|
+
abstract compileUpsert(table: string, columns: string[], values: string[], uniqueBy: string[], updateColumns: string[]): string;
|
|
11
|
+
compileUpdate(table: string, sets: string[], wheres: string, joins?: string[]): string;
|
|
12
|
+
compileDelete(table: string, wheres: string, joins?: string[], limit?: number): string;
|
|
13
|
+
abstract compileJsonContains(column: string, value: any): string;
|
|
14
|
+
abstract compileJsonLength(column: string, operator: string, value: any): string;
|
|
15
|
+
compileLike(column: string, value: string, not: boolean): string;
|
|
16
|
+
abstract compileRegexp(column: string, value: string, not: boolean): string;
|
|
17
|
+
abstract compileFullText(columns: string[], value: string): string;
|
|
18
|
+
abstract compileExplain(sql: string): string;
|
|
19
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export class Grammar {
|
|
2
|
+
wrapArray(values) {
|
|
3
|
+
return values.map((v) => this.wrap(v));
|
|
4
|
+
}
|
|
5
|
+
escape(value) {
|
|
6
|
+
if (value === null)
|
|
7
|
+
return "NULL";
|
|
8
|
+
if (typeof value === "boolean")
|
|
9
|
+
return value ? "1" : "0";
|
|
10
|
+
if (typeof value === "number")
|
|
11
|
+
return String(value);
|
|
12
|
+
if (typeof value === "string" && value.toUpperCase().includes("CURRENT_TIMESTAMP"))
|
|
13
|
+
return value;
|
|
14
|
+
return `'${String(value).replace(/'/g, "''")}'`;
|
|
15
|
+
}
|
|
16
|
+
compileOffset(offset, _limit) {
|
|
17
|
+
return `OFFSET ${offset}`;
|
|
18
|
+
}
|
|
19
|
+
compileLock(lockMode) {
|
|
20
|
+
return lockMode ? ` ${lockMode}` : "";
|
|
21
|
+
}
|
|
22
|
+
compileUpdate(table, sets, wheres, joins) {
|
|
23
|
+
let sql = `UPDATE ${table}`;
|
|
24
|
+
if (joins && joins.length > 0) {
|
|
25
|
+
sql += ` ${joins.join(" ")}`;
|
|
26
|
+
}
|
|
27
|
+
sql += ` SET ${sets.join(", ")}`;
|
|
28
|
+
if (wheres)
|
|
29
|
+
sql += ` ${wheres}`;
|
|
30
|
+
return sql.trim();
|
|
31
|
+
}
|
|
32
|
+
compileDelete(table, wheres, joins, limit) {
|
|
33
|
+
let sql = `DELETE FROM ${table}`;
|
|
34
|
+
if (joins && joins.length > 0) {
|
|
35
|
+
sql += ` ${joins.join(" ")}`;
|
|
36
|
+
}
|
|
37
|
+
if (wheres)
|
|
38
|
+
sql += ` ${wheres}`;
|
|
39
|
+
if (limit !== undefined)
|
|
40
|
+
sql += ` LIMIT ${limit}`;
|
|
41
|
+
return sql.trim();
|
|
42
|
+
}
|
|
43
|
+
compileLike(column, value, not) {
|
|
44
|
+
const op = not ? "NOT LIKE" : "LIKE";
|
|
45
|
+
return `${column} ${op} ${this.escape(value)}`;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Grammar } from "./Grammar.js";
|
|
2
|
+
export declare class MySqlGrammar extends Grammar {
|
|
3
|
+
wrap(value: string): string;
|
|
4
|
+
compileRandomOrder(): string;
|
|
5
|
+
compileDateWhere(type: string, column: string, operator: string, value: any): string;
|
|
6
|
+
compileInsertOrIgnore(table: string, columns: string[], values: string[]): string;
|
|
7
|
+
compileUpsert(table: string, columns: string[], values: string[], _uniqueBy: string[], updateColumns: string[]): string;
|
|
8
|
+
compileJsonContains(column: string, value: any): string;
|
|
9
|
+
compileJsonLength(column: string, operator: string, value: any): string;
|
|
10
|
+
compileRegexp(column: string, value: string, not: boolean): string;
|
|
11
|
+
compileFullText(columns: string[], value: string): string;
|
|
12
|
+
compileExplain(sql: string): string;
|
|
13
|
+
}
|