@bunnykit/orm 0.1.5 → 0.1.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.
@@ -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
- const driver = this.connection.getDriverName();
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
- return `HAVING ${this.havings.join(" AND ")}`;
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
- const limitSql = this.limitValue === undefined && this.connection.getDriverName() === "sqlite"
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
- let sql = `SELECT ${distinct}${this.compileColumns()} FROM ${this.wrap(this.tableName)}`;
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
- if (this.lockMode)
455
- sql += " " + this.lockMode;
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
- return this.model.create({ ...attributes, ...values });
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
- return this.model.create({ ...attributes, ...values });
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 = `UPDATE ${this.wrap(this.tableName)} SET ${sets} ${this.compileWheres()}`;
622
- return await this.connection.run(sql.trim());
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 = `DELETE FROM ${this.wrap(this.tableName)} ${this.compileWheres()}`;
626
- return await this.connection.run(sql.trim());
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 = driver === "mysql" ? "FOR UPDATE" : "FOR UPDATE";
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 driver = this.connection.getDriverName();
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
+ }