@bunnykit/orm 0.1.20 → 0.1.21
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/dist/src/connection/ConnectionManager.js +17 -1
- package/dist/src/model/BelongsToMany.js +9 -0
- package/dist/src/model/IdentityMap.d.ts +1 -0
- package/dist/src/model/IdentityMap.js +6 -0
- package/dist/src/model/Model.js +51 -1
- package/dist/src/model/MorphRelations.js +36 -0
- package/dist/src/query/Builder.js +76 -24
- package/dist/src/query/grammars/Grammar.d.ts +6 -6
- package/dist/src/query/grammars/Grammar.js +2 -2
- package/dist/src/query/grammars/MySqlGrammar.d.ts +5 -5
- package/dist/src/query/grammars/MySqlGrammar.js +16 -15
- package/dist/src/query/grammars/PostgresGrammar.d.ts +5 -5
- package/dist/src/query/grammars/PostgresGrammar.js +16 -15
- package/dist/src/query/grammars/SQLiteGrammar.d.ts +5 -5
- package/dist/src/query/grammars/SQLiteGrammar.js +16 -15
- package/dist/src/types/index.d.ts +4 -1
- package/package.json +1 -1
|
@@ -28,12 +28,12 @@ export class ConnectionManager {
|
|
|
28
28
|
}
|
|
29
29
|
const now = Date.now();
|
|
30
30
|
const idleTimeout = poolConfig.idleTimeout || 30000;
|
|
31
|
+
// Reuse a healthy idle connection
|
|
31
32
|
while (pool.length > 0) {
|
|
32
33
|
const idx = pool.findIndex((c) => !c.inUse && (now - c.lastUsed) < idleTimeout);
|
|
33
34
|
if (idx === -1)
|
|
34
35
|
break;
|
|
35
36
|
const pooled = pool[idx];
|
|
36
|
-
pool.splice(idx, 1);
|
|
37
37
|
try {
|
|
38
38
|
await pooled.connection.query("SELECT 1");
|
|
39
39
|
pooled.inUse = true;
|
|
@@ -41,6 +41,14 @@ export class ConnectionManager {
|
|
|
41
41
|
}
|
|
42
42
|
catch {
|
|
43
43
|
await pooled.connection.close().catch(() => null);
|
|
44
|
+
pool.splice(idx, 1);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Clean up expired idle connections
|
|
48
|
+
for (let i = pool.length - 1; i >= 0; i--) {
|
|
49
|
+
if (!pool[i].inUse && (now - pool[i].lastUsed) >= idleTimeout) {
|
|
50
|
+
await pool[i].connection.close().catch(() => null);
|
|
51
|
+
pool.splice(i, 1);
|
|
44
52
|
}
|
|
45
53
|
}
|
|
46
54
|
if (pool.length < (poolConfig.maxConnections || 10)) {
|
|
@@ -203,6 +211,14 @@ export class ConnectionManager {
|
|
|
203
211
|
this.connections.clear();
|
|
204
212
|
this.pools.clear();
|
|
205
213
|
this.tenantCache.clear();
|
|
214
|
+
// Reject all pending waiters
|
|
215
|
+
for (const [name, poolWaiters] of this.waiters) {
|
|
216
|
+
for (const waiter of poolWaiters) {
|
|
217
|
+
clearTimeout(waiter.timer);
|
|
218
|
+
waiter.reject(new Error(`Connection pool "${name}" is closing`));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
this.waiters.clear();
|
|
206
222
|
for (const connection of connections) {
|
|
207
223
|
await connection.close();
|
|
208
224
|
}
|
|
@@ -23,6 +23,15 @@ export class BelongsToMany {
|
|
|
23
23
|
this.relatedPivotKey = relatedPivotKey || `${snakeCase(related.name)}_id`;
|
|
24
24
|
this.builder = related.on(parent.getConnection());
|
|
25
25
|
this.addConstraints();
|
|
26
|
+
// Wrap getResults with lazy-loading guard
|
|
27
|
+
const originalGetResults = this.getResults.bind(this);
|
|
28
|
+
this.getResults = async () => {
|
|
29
|
+
if (this.parent.constructor.preventLazyLoading) {
|
|
30
|
+
throw new Error(`Lazy loading is prevented on ${this.parent.constructor.name}. ` +
|
|
31
|
+
`Eager load the relation using with().`);
|
|
32
|
+
}
|
|
33
|
+
return await originalGetResults();
|
|
34
|
+
};
|
|
26
35
|
}
|
|
27
36
|
addConstraints() {
|
|
28
37
|
const relatedTable = this.related.getTable();
|
package/dist/src/model/Model.js
CHANGED
|
@@ -332,6 +332,29 @@ export class Model {
|
|
|
332
332
|
}
|
|
333
333
|
return Reflect.set(target, prop, value, receiver);
|
|
334
334
|
},
|
|
335
|
+
has(target, prop) {
|
|
336
|
+
if (typeof prop === "string" && prop in target.$attributes)
|
|
337
|
+
return true;
|
|
338
|
+
return Reflect.has(target, prop);
|
|
339
|
+
},
|
|
340
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
341
|
+
if (typeof prop === "string" && prop in target.$attributes) {
|
|
342
|
+
return {
|
|
343
|
+
enumerable: true,
|
|
344
|
+
configurable: true,
|
|
345
|
+
value: target.getAttribute(prop),
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
return Reflect.getOwnPropertyDescriptor(target, prop);
|
|
349
|
+
},
|
|
350
|
+
ownKeys(target) {
|
|
351
|
+
const keys = new Set(Reflect.ownKeys(target));
|
|
352
|
+
for (const key of Object.keys(target.$attributes)) {
|
|
353
|
+
if (!key.startsWith("$"))
|
|
354
|
+
keys.add(key);
|
|
355
|
+
}
|
|
356
|
+
return Array.from(keys);
|
|
357
|
+
},
|
|
335
358
|
});
|
|
336
359
|
}
|
|
337
360
|
defineAttributeProperty(key) {
|
|
@@ -345,7 +368,17 @@ export class Model {
|
|
|
345
368
|
});
|
|
346
369
|
}
|
|
347
370
|
syncAttributeProperties() {
|
|
348
|
-
|
|
371
|
+
const currentKeys = new Set(Object.keys(this.$attributes));
|
|
372
|
+
// Remove stale attribute properties
|
|
373
|
+
for (const key of Reflect.ownKeys(this)) {
|
|
374
|
+
if (key.startsWith("$") || typeof key !== "string")
|
|
375
|
+
continue;
|
|
376
|
+
const desc = Object.getOwnPropertyDescriptor(this, key);
|
|
377
|
+
if (desc && desc.get && desc.configurable && !currentKeys.has(key)) {
|
|
378
|
+
delete this[key];
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
for (const key of currentKeys) {
|
|
349
382
|
this.defineAttributeProperty(key);
|
|
350
383
|
}
|
|
351
384
|
}
|
|
@@ -918,6 +951,10 @@ export class Model {
|
|
|
918
951
|
.delete();
|
|
919
952
|
this.$exists = false;
|
|
920
953
|
}
|
|
954
|
+
const identityMap = IdentityMap.current();
|
|
955
|
+
if (identityMap) {
|
|
956
|
+
IdentityMap.delete(constructor.getTable(), pk);
|
|
957
|
+
}
|
|
921
958
|
await ObserverRegistry.dispatch("deleted", this);
|
|
922
959
|
return true;
|
|
923
960
|
}
|
|
@@ -948,6 +985,10 @@ export class Model {
|
|
|
948
985
|
.where(constructor.primaryKey, pk)
|
|
949
986
|
.delete();
|
|
950
987
|
this.$exists = false;
|
|
988
|
+
const identityMap = IdentityMap.current();
|
|
989
|
+
if (identityMap) {
|
|
990
|
+
IdentityMap.delete(constructor.getTable(), pk);
|
|
991
|
+
}
|
|
951
992
|
return true;
|
|
952
993
|
}
|
|
953
994
|
async refresh() {
|
|
@@ -955,11 +996,20 @@ export class Model {
|
|
|
955
996
|
const pk = this.getAttribute(constructor.primaryKey);
|
|
956
997
|
if (!pk)
|
|
957
998
|
return this;
|
|
999
|
+
// Bypass identity map to fetch fresh data
|
|
1000
|
+
const identityMap = IdentityMap.current();
|
|
1001
|
+
if (identityMap) {
|
|
1002
|
+
IdentityMap.delete(constructor.getTable(), pk);
|
|
1003
|
+
}
|
|
958
1004
|
const result = await constructor.find(pk);
|
|
959
1005
|
if (result) {
|
|
960
1006
|
this.$attributes = result.$attributes;
|
|
961
1007
|
this.$original = { ...result.$attributes };
|
|
962
1008
|
this.syncAttributeProperties();
|
|
1009
|
+
// Ensure this instance is the canonical one in the identity map
|
|
1010
|
+
if (identityMap) {
|
|
1011
|
+
IdentityMap.set(constructor.getTable(), pk, this);
|
|
1012
|
+
}
|
|
963
1013
|
}
|
|
964
1014
|
return this;
|
|
965
1015
|
}
|
|
@@ -13,6 +13,15 @@ export class MorphTo {
|
|
|
13
13
|
this.typeColumn = `${name}_type`;
|
|
14
14
|
this.idColumn = `${name}_id`;
|
|
15
15
|
this.typeMap = typeMap;
|
|
16
|
+
// Wrap getResults with lazy-loading guard
|
|
17
|
+
const originalGetResults = this.getResults.bind(this);
|
|
18
|
+
this.getResults = async () => {
|
|
19
|
+
if (this.parent.constructor.preventLazyLoading) {
|
|
20
|
+
throw new Error(`Lazy loading is prevented on ${this.parent.constructor.name}. ` +
|
|
21
|
+
`Eager load the relation using with().`);
|
|
22
|
+
}
|
|
23
|
+
return await originalGetResults();
|
|
24
|
+
};
|
|
16
25
|
}
|
|
17
26
|
async getResults() {
|
|
18
27
|
const type = this.parent.getAttribute(this.typeColumn);
|
|
@@ -99,6 +108,15 @@ export class MorphOne {
|
|
|
99
108
|
this.builder = related.on(parent.getConnection());
|
|
100
109
|
this.builder.where(this.typeColumn, this.getMorphType());
|
|
101
110
|
this.builder.where(this.idColumn, this.parent.getAttribute(this.localKey));
|
|
111
|
+
// Wrap getResults with lazy-loading guard
|
|
112
|
+
const originalGetResults = this.getResults.bind(this);
|
|
113
|
+
this.getResults = async () => {
|
|
114
|
+
if (this.parent.constructor.preventLazyLoading) {
|
|
115
|
+
throw new Error(`Lazy loading is prevented on ${this.parent.constructor.name}. ` +
|
|
116
|
+
`Eager load the relation using with().`);
|
|
117
|
+
}
|
|
118
|
+
return await originalGetResults();
|
|
119
|
+
};
|
|
102
120
|
}
|
|
103
121
|
getMorphType() {
|
|
104
122
|
return this.parent.constructor.morphName || this.parent.constructor.name;
|
|
@@ -168,6 +186,15 @@ export class MorphMany {
|
|
|
168
186
|
this.builder = related.on(parent.getConnection());
|
|
169
187
|
this.builder.where(this.typeColumn, this.getMorphType());
|
|
170
188
|
this.builder.where(this.idColumn, this.parent.getAttribute(this.localKey));
|
|
189
|
+
// Wrap getResults with lazy-loading guard
|
|
190
|
+
const originalGetResults = this.getResults.bind(this);
|
|
191
|
+
this.getResults = async () => {
|
|
192
|
+
if (this.parent.constructor.preventLazyLoading) {
|
|
193
|
+
throw new Error(`Lazy loading is prevented on ${this.parent.constructor.name}. ` +
|
|
194
|
+
`Eager load the relation using with().`);
|
|
195
|
+
}
|
|
196
|
+
return await originalGetResults();
|
|
197
|
+
};
|
|
171
198
|
}
|
|
172
199
|
getMorphType() {
|
|
173
200
|
return this.parent.constructor.morphName || this.parent.constructor.name;
|
|
@@ -244,6 +271,15 @@ export class MorphToMany {
|
|
|
244
271
|
this.relatedPivotKey = relatedPivotKey || `${snakeCase(related.name)}_id`;
|
|
245
272
|
this.builder = related.on(parent.getConnection());
|
|
246
273
|
this.addConstraints();
|
|
274
|
+
// Wrap getResults with lazy-loading guard
|
|
275
|
+
const originalGetResults = this.getResults.bind(this);
|
|
276
|
+
this.getResults = async () => {
|
|
277
|
+
if (this.parent.constructor.preventLazyLoading) {
|
|
278
|
+
throw new Error(`Lazy loading is prevented on ${this.parent.constructor.name}. ` +
|
|
279
|
+
`Eager load the relation using with().`);
|
|
280
|
+
}
|
|
281
|
+
return await originalGetResults();
|
|
282
|
+
};
|
|
247
283
|
}
|
|
248
284
|
addConstraints() {
|
|
249
285
|
const relatedTable = this.related.getTable();
|
|
@@ -182,10 +182,7 @@ export class Builder {
|
|
|
182
182
|
return this.whereRaw(sql, "or", scope);
|
|
183
183
|
}
|
|
184
184
|
whereJsonContains(column, value, boolean = "and", not = false) {
|
|
185
|
-
|
|
186
|
-
if (not)
|
|
187
|
-
sql = `NOT (${sql})`;
|
|
188
|
-
this.wheres.push({ type: "raw", column: sql, boolean, scope: undefined });
|
|
185
|
+
this.wheres.push({ type: "json_contains", column, value, boolean, scope: undefined, not });
|
|
189
186
|
return this;
|
|
190
187
|
}
|
|
191
188
|
whereJsonLength(column, operator = "=", value, boolean = "and", not = false) {
|
|
@@ -193,41 +190,31 @@ export class Builder {
|
|
|
193
190
|
value = operator;
|
|
194
191
|
operator = "=";
|
|
195
192
|
}
|
|
196
|
-
|
|
197
|
-
if (not)
|
|
198
|
-
sql = `NOT (${sql})`;
|
|
199
|
-
this.wheres.push({ type: "raw", column: sql, boolean, scope: undefined });
|
|
193
|
+
this.wheres.push({ type: "json_length", column, operator: String(operator), value, boolean, scope: undefined, not });
|
|
200
194
|
return this;
|
|
201
195
|
}
|
|
202
196
|
whereLike(column, value, boolean = "and", not = false) {
|
|
203
|
-
|
|
204
|
-
this.wheres.push({ type: "raw", column: sql, boolean, scope: undefined });
|
|
197
|
+
this.wheres.push({ type: "like", column, value, boolean, scope: undefined, not });
|
|
205
198
|
return this;
|
|
206
199
|
}
|
|
207
200
|
whereNotLike(column, value) {
|
|
208
201
|
return this.whereLike(column, value, "and", true);
|
|
209
202
|
}
|
|
210
203
|
whereRegexp(column, value, boolean = "and", not = false) {
|
|
211
|
-
|
|
212
|
-
this.wheres.push({ type: "raw", column: sql, boolean, scope: undefined });
|
|
204
|
+
this.wheres.push({ type: "regexp", column, value, boolean, scope: undefined, not });
|
|
213
205
|
return this;
|
|
214
206
|
}
|
|
215
207
|
whereFullText(columns, value, boolean = "and", not = false) {
|
|
216
208
|
const cols = Array.isArray(columns) ? columns : [columns];
|
|
217
|
-
|
|
218
|
-
if (not)
|
|
219
|
-
sql = `NOT (${sql})`;
|
|
220
|
-
this.wheres.push({ type: "raw", column: sql, boolean, scope: undefined });
|
|
209
|
+
this.wheres.push({ type: "fulltext", column: "", columns: cols, value, boolean, scope: undefined, not });
|
|
221
210
|
return this;
|
|
222
211
|
}
|
|
223
212
|
whereAll(columns, operator, value, boolean = "and") {
|
|
224
|
-
|
|
225
|
-
this.wheres.push({ type: "raw", column: `(${sql})`, boolean, scope: undefined });
|
|
213
|
+
this.wheres.push({ type: "all", column: "", columns: columns, operator, value, boolean, scope: undefined });
|
|
226
214
|
return this;
|
|
227
215
|
}
|
|
228
216
|
whereAny(columns, operator, value, boolean = "and") {
|
|
229
|
-
|
|
230
|
-
this.wheres.push({ type: "raw", column: `(${sql})`, boolean, scope: undefined });
|
|
217
|
+
this.wheres.push({ type: "any", column: "", columns: columns, operator, value, boolean, scope: undefined });
|
|
231
218
|
return this;
|
|
232
219
|
}
|
|
233
220
|
orderBy(column, direction = "asc") {
|
|
@@ -459,6 +446,7 @@ export class Builder {
|
|
|
459
446
|
cloned.fromRaw = this.fromRaw;
|
|
460
447
|
cloned.updateJoins = [...this.updateJoins];
|
|
461
448
|
cloned.bindings = [...this.bindings];
|
|
449
|
+
cloned.parameterize = this.parameterize;
|
|
462
450
|
return cloned;
|
|
463
451
|
}
|
|
464
452
|
wrapColumn(value) {
|
|
@@ -496,6 +484,53 @@ export class Builder {
|
|
|
496
484
|
else if (where.type === "raw") {
|
|
497
485
|
return `${prefix} ${where.column}`;
|
|
498
486
|
}
|
|
487
|
+
else if (where.type === "like") {
|
|
488
|
+
const sql = this.grammar.compileLike(this.grammar.wrap(where.column), where.value, !!where.not, this.parameterize ? (v) => this.addBinding(v) : undefined);
|
|
489
|
+
return `${prefix} ${sql}`;
|
|
490
|
+
}
|
|
491
|
+
else if (where.type === "regexp") {
|
|
492
|
+
const sql = this.grammar.compileRegexp(this.grammar.wrap(where.column), where.value, !!where.not, this.parameterize ? (v) => this.addBinding(v) : undefined);
|
|
493
|
+
return `${prefix} ${sql}`;
|
|
494
|
+
}
|
|
495
|
+
else if (where.type === "fulltext") {
|
|
496
|
+
const cols = (where.columns || []).map((c) => this.grammar.wrap(c));
|
|
497
|
+
let sql = this.grammar.compileFullText(cols, where.value, this.parameterize ? (v) => this.addBinding(v) : undefined);
|
|
498
|
+
if (where.not)
|
|
499
|
+
sql = `NOT (${sql})`;
|
|
500
|
+
return `${prefix} ${sql}`;
|
|
501
|
+
}
|
|
502
|
+
else if (where.type === "json_contains") {
|
|
503
|
+
let sql = this.grammar.compileJsonContains(this.grammar.wrap(where.column), where.value, this.parameterize ? (v) => this.addBinding(v) : undefined);
|
|
504
|
+
if (where.not)
|
|
505
|
+
sql = `NOT (${sql})`;
|
|
506
|
+
return `${prefix} ${sql}`;
|
|
507
|
+
}
|
|
508
|
+
else if (where.type === "json_length") {
|
|
509
|
+
let sql = this.grammar.compileJsonLength(this.grammar.wrap(where.column), where.operator || "=", where.value, this.parameterize ? (v) => this.addBinding(v) : undefined);
|
|
510
|
+
if (where.not)
|
|
511
|
+
sql = `NOT (${sql})`;
|
|
512
|
+
return `${prefix} ${sql}`;
|
|
513
|
+
}
|
|
514
|
+
else if (where.type === "date") {
|
|
515
|
+
const sql = this.grammar.compileDateWhere(where.dateType || "date", this.grammar.wrap(where.column), where.operator || "=", where.value, this.parameterize ? (v) => this.addBinding(v) : undefined);
|
|
516
|
+
return `${prefix} ${sql}`;
|
|
517
|
+
}
|
|
518
|
+
else if (where.type === "all") {
|
|
519
|
+
const cols = (where.columns || []).map((c) => this.grammar.wrap(c));
|
|
520
|
+
const inner = cols.map((c) => {
|
|
521
|
+
const val = this.parameterize ? this.addBinding(where.value) : this.grammar.escape(where.value);
|
|
522
|
+
return `${c} ${where.operator} ${val}`;
|
|
523
|
+
}).join(" AND ");
|
|
524
|
+
return `${prefix} (${inner})`;
|
|
525
|
+
}
|
|
526
|
+
else if (where.type === "any") {
|
|
527
|
+
const cols = (where.columns || []).map((c) => this.grammar.wrap(c));
|
|
528
|
+
const inner = cols.map((c) => {
|
|
529
|
+
const val = this.parameterize ? this.addBinding(where.value) : this.grammar.escape(where.value);
|
|
530
|
+
return `${c} ${where.operator} ${val}`;
|
|
531
|
+
}).join(" OR ");
|
|
532
|
+
return `${prefix} (${inner})`;
|
|
533
|
+
}
|
|
499
534
|
else if (where.type === "column") {
|
|
500
535
|
return `${prefix} ${this.grammar.wrap(where.column)} ${where.operator} ${this.grammar.wrap(where.value)}`;
|
|
501
536
|
}
|
|
@@ -740,16 +775,34 @@ export class Builder {
|
|
|
740
775
|
async *cursor(chunkSize = 1000) {
|
|
741
776
|
const model = this.model;
|
|
742
777
|
const primaryKey = model ? model.primaryKey || "id" : "id";
|
|
778
|
+
// Cursor pagination is incompatible with random ordering
|
|
779
|
+
if (this.randomOrderFlag) {
|
|
780
|
+
throw new Error("cursor() does not support inRandomOrder(). Use lazy() instead.");
|
|
781
|
+
}
|
|
743
782
|
const orderColumn = this.orders[0]?.column || primaryKey;
|
|
744
783
|
const orderDirection = this.orders[0]?.direction || "asc";
|
|
784
|
+
// Use unqualified column name for property access on model instances
|
|
785
|
+
const accessColumn = orderColumn.includes(".") ? orderColumn.split(".")[1] : orderColumn;
|
|
745
786
|
let lastValue = undefined;
|
|
746
787
|
while (true) {
|
|
747
788
|
const builder = this.clone();
|
|
748
|
-
|
|
789
|
+
// Preserve multi-column ORDER BY, appending PK tie-breaker if not present
|
|
790
|
+
builder.orders = this.orders.length > 0 ? [...this.orders] : [{ column: orderColumn, direction: orderDirection }];
|
|
791
|
+
const hasPkOrder = builder.orders.some((o) => o.column === primaryKey);
|
|
792
|
+
if (!hasPkOrder) {
|
|
793
|
+
builder.orders.push({ column: primaryKey, direction: orderDirection });
|
|
794
|
+
}
|
|
749
795
|
builder.offsetValue = undefined;
|
|
750
796
|
builder.limitValue = chunkSize;
|
|
751
797
|
if (lastValue !== undefined) {
|
|
752
798
|
const op = orderDirection === "asc" ? ">" : "<";
|
|
799
|
+
// Parenthesize existing wheres when appending cursor condition to preserve OR precedence
|
|
800
|
+
if (builder.wheres.length > 0) {
|
|
801
|
+
const hasOr = builder.wheres.some((w) => w.boolean === "or");
|
|
802
|
+
if (hasOr) {
|
|
803
|
+
builder.wheres = [{ type: "raw", column: `(${builder.compileWheres().replace(/^WHERE /, "")})`, boolean: "and", scope: undefined }];
|
|
804
|
+
}
|
|
805
|
+
}
|
|
753
806
|
builder.wheres.push({
|
|
754
807
|
type: "basic",
|
|
755
808
|
column: orderColumn,
|
|
@@ -769,7 +822,7 @@ export class Builder {
|
|
|
769
822
|
break;
|
|
770
823
|
const lastItem = items[items.length - 1];
|
|
771
824
|
lastValue = lastItem && typeof lastItem === "object"
|
|
772
|
-
? lastItem[
|
|
825
|
+
? lastItem[accessColumn]
|
|
773
826
|
: undefined;
|
|
774
827
|
}
|
|
775
828
|
}
|
|
@@ -958,8 +1011,7 @@ export class Builder {
|
|
|
958
1011
|
value = operator;
|
|
959
1012
|
operator = "=";
|
|
960
1013
|
}
|
|
961
|
-
|
|
962
|
-
this.wheres.push({ type: "raw", column: sql, boolean, scope: undefined });
|
|
1014
|
+
this.wheres.push({ type: "date", column: column, operator, value, boolean, scope: undefined, dateType: type });
|
|
963
1015
|
return this;
|
|
964
1016
|
}
|
|
965
1017
|
getModelRelation(relationName) {
|
|
@@ -6,15 +6,15 @@ export declare abstract class Grammar {
|
|
|
6
6
|
abstract compileRandomOrder(): string;
|
|
7
7
|
compileOffset(offset: number, _limit?: number): string;
|
|
8
8
|
compileLock(lockMode?: string): string;
|
|
9
|
-
abstract compileDateWhere(type: string, column: string, operator: string, value: any): string;
|
|
9
|
+
abstract compileDateWhere(type: string, column: string, operator: string, value: any, binding?: (value: any) => string): string;
|
|
10
10
|
abstract compileInsertOrIgnore(table: string, columns: string[], values: string[]): string;
|
|
11
11
|
abstract compileUpsert(table: string, columns: string[], values: string[], uniqueBy: string[], updateColumns: string[]): string;
|
|
12
12
|
compileUpdate(table: string, sets: string[], wheres: string, joins?: string[]): string;
|
|
13
13
|
compileDelete(table: string, wheres: string, joins?: string[], limit?: number): string;
|
|
14
|
-
abstract compileJsonContains(column: string, value: any): string;
|
|
15
|
-
abstract compileJsonLength(column: string, operator: string, value: any): string;
|
|
16
|
-
compileLike(column: string, value: string, not: boolean): string;
|
|
17
|
-
abstract compileRegexp(column: string, value: string, not: boolean): string;
|
|
18
|
-
abstract compileFullText(columns: string[], value: string): string;
|
|
14
|
+
abstract compileJsonContains(column: string, value: any, binding?: (value: any) => string): string;
|
|
15
|
+
abstract compileJsonLength(column: string, operator: string, value: any, binding?: (value: any) => string): string;
|
|
16
|
+
compileLike(column: string, value: string, not: boolean, binding?: (value: any) => string): string;
|
|
17
|
+
abstract compileRegexp(column: string, value: string, not: boolean, binding?: (value: any) => string): string;
|
|
18
|
+
abstract compileFullText(columns: string[], value: string, binding?: (value: any) => string): string;
|
|
19
19
|
abstract compileExplain(sql: string): string;
|
|
20
20
|
}
|
|
@@ -40,8 +40,8 @@ export class Grammar {
|
|
|
40
40
|
sql += ` LIMIT ${limit}`;
|
|
41
41
|
return sql.trim();
|
|
42
42
|
}
|
|
43
|
-
compileLike(column, value, not) {
|
|
43
|
+
compileLike(column, value, not, binding) {
|
|
44
44
|
const op = not ? "NOT LIKE" : "LIKE";
|
|
45
|
-
return `${column} ${op} ${this.escape(value)}`;
|
|
45
|
+
return `${column} ${op} ${binding ? binding(value) : this.escape(value)}`;
|
|
46
46
|
}
|
|
47
47
|
}
|
|
@@ -3,12 +3,12 @@ export declare class MySqlGrammar extends Grammar {
|
|
|
3
3
|
wrap(value: string): string;
|
|
4
4
|
placeholder(_index: number): string;
|
|
5
5
|
compileRandomOrder(): string;
|
|
6
|
-
compileDateWhere(type: string, column: string, operator: string, value: any): string;
|
|
6
|
+
compileDateWhere(type: string, column: string, operator: string, value: any, binding?: (value: any) => string): string;
|
|
7
7
|
compileInsertOrIgnore(table: string, columns: string[], values: string[]): string;
|
|
8
8
|
compileUpsert(table: string, columns: string[], values: string[], _uniqueBy: string[], updateColumns: string[]): string;
|
|
9
|
-
compileJsonContains(column: string, value: any): string;
|
|
10
|
-
compileJsonLength(column: string, operator: string, value: any): string;
|
|
11
|
-
compileRegexp(column: string, value: string, not: boolean): string;
|
|
12
|
-
compileFullText(columns: string[], value: string): string;
|
|
9
|
+
compileJsonContains(column: string, value: any, binding?: (value: any) => string): string;
|
|
10
|
+
compileJsonLength(column: string, operator: string, value: any, binding?: (value: any) => string): string;
|
|
11
|
+
compileRegexp(column: string, value: string, not: boolean, binding?: (value: any) => string): string;
|
|
12
|
+
compileFullText(columns: string[], value: string, binding?: (value: any) => string): string;
|
|
13
13
|
compileExplain(sql: string): string;
|
|
14
14
|
}
|
|
@@ -18,20 +18,21 @@ export class MySqlGrammar extends Grammar {
|
|
|
18
18
|
compileRandomOrder() {
|
|
19
19
|
return "ORDER BY RAND()";
|
|
20
20
|
}
|
|
21
|
-
compileDateWhere(type, column, operator, value) {
|
|
21
|
+
compileDateWhere(type, column, operator, value, binding) {
|
|
22
|
+
const val = binding ? binding(value) : this.escape(value);
|
|
22
23
|
switch (type) {
|
|
23
24
|
case "date":
|
|
24
|
-
return `DATE(${column}) ${operator} ${
|
|
25
|
+
return `DATE(${column}) ${operator} ${val}`;
|
|
25
26
|
case "day":
|
|
26
|
-
return `DAY(${column}) ${operator} ${
|
|
27
|
+
return `DAY(${column}) ${operator} ${val}`;
|
|
27
28
|
case "month":
|
|
28
|
-
return `MONTH(${column}) ${operator} ${
|
|
29
|
+
return `MONTH(${column}) ${operator} ${val}`;
|
|
29
30
|
case "year":
|
|
30
|
-
return `YEAR(${column}) ${operator} ${
|
|
31
|
+
return `YEAR(${column}) ${operator} ${val}`;
|
|
31
32
|
case "time":
|
|
32
|
-
return `TIME(${column}) ${operator} ${
|
|
33
|
+
return `TIME(${column}) ${operator} ${val}`;
|
|
33
34
|
default:
|
|
34
|
-
return `${column} ${operator} ${
|
|
35
|
+
return `${column} ${operator} ${val}`;
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
compileInsertOrIgnore(table, columns, values) {
|
|
@@ -43,18 +44,18 @@ export class MySqlGrammar extends Grammar {
|
|
|
43
44
|
.join(", ");
|
|
44
45
|
return `INSERT INTO ${table} (${columns.map((c) => this.wrap(c)).join(", ")}) VALUES ${values.join(", ")} ON DUPLICATE KEY UPDATE ${updateCols}`;
|
|
45
46
|
}
|
|
46
|
-
compileJsonContains(column, value) {
|
|
47
|
-
return `JSON_CONTAINS(${column}, ${this.escape(JSON.stringify(value))})`;
|
|
47
|
+
compileJsonContains(column, value, binding) {
|
|
48
|
+
return `JSON_CONTAINS(${column}, ${binding ? binding(JSON.stringify(value)) : this.escape(JSON.stringify(value))})`;
|
|
48
49
|
}
|
|
49
|
-
compileJsonLength(column, operator, value) {
|
|
50
|
-
return `JSON_LENGTH(${column}) ${operator} ${this.escape(value)}`;
|
|
50
|
+
compileJsonLength(column, operator, value, binding) {
|
|
51
|
+
return `JSON_LENGTH(${column}) ${operator} ${binding ? binding(value) : this.escape(value)}`;
|
|
51
52
|
}
|
|
52
|
-
compileRegexp(column, value, not) {
|
|
53
|
+
compileRegexp(column, value, not, binding) {
|
|
53
54
|
const op = not ? "NOT REGEXP" : "REGEXP";
|
|
54
|
-
return `${column} ${op} ${this.escape(value)}`;
|
|
55
|
+
return `${column} ${op} ${binding ? binding(value) : this.escape(value)}`;
|
|
55
56
|
}
|
|
56
|
-
compileFullText(columns, value) {
|
|
57
|
-
return `MATCH (${columns.join(", ")}) AGAINST (${this.escape(value)})`;
|
|
57
|
+
compileFullText(columns, value, binding) {
|
|
58
|
+
return `MATCH (${columns.join(", ")}) AGAINST (${binding ? binding(value) : this.escape(value)})`;
|
|
58
59
|
}
|
|
59
60
|
compileExplain(sql) {
|
|
60
61
|
return `EXPLAIN ${sql}`;
|
|
@@ -3,12 +3,12 @@ export declare class PostgresGrammar extends Grammar {
|
|
|
3
3
|
wrap(value: string): string;
|
|
4
4
|
placeholder(index: number): string;
|
|
5
5
|
compileRandomOrder(): string;
|
|
6
|
-
compileDateWhere(type: string, column: string, operator: string, value: any): string;
|
|
6
|
+
compileDateWhere(type: string, column: string, operator: string, value: any, binding?: (value: any) => string): string;
|
|
7
7
|
compileInsertOrIgnore(table: string, columns: string[], values: string[]): string;
|
|
8
8
|
compileUpsert(table: string, columns: string[], values: string[], uniqueBy: string[], updateColumns: string[]): string;
|
|
9
|
-
compileJsonContains(column: string, value: any): string;
|
|
10
|
-
compileJsonLength(column: string, operator: string, value: any): string;
|
|
11
|
-
compileRegexp(column: string, value: string, not: boolean): string;
|
|
12
|
-
compileFullText(columns: string[], value: string): string;
|
|
9
|
+
compileJsonContains(column: string, value: any, binding?: (value: any) => string): string;
|
|
10
|
+
compileJsonLength(column: string, operator: string, value: any, binding?: (value: any) => string): string;
|
|
11
|
+
compileRegexp(column: string, value: string, not: boolean, binding?: (value: any) => string): string;
|
|
12
|
+
compileFullText(columns: string[], value: string, binding?: (value: any) => string): string;
|
|
13
13
|
compileExplain(sql: string): string;
|
|
14
14
|
}
|
|
@@ -18,20 +18,21 @@ export class PostgresGrammar extends Grammar {
|
|
|
18
18
|
compileRandomOrder() {
|
|
19
19
|
return "ORDER BY RANDOM()";
|
|
20
20
|
}
|
|
21
|
-
compileDateWhere(type, column, operator, value) {
|
|
21
|
+
compileDateWhere(type, column, operator, value, binding) {
|
|
22
|
+
const val = binding ? binding(value) : this.escape(value);
|
|
22
23
|
switch (type) {
|
|
23
24
|
case "date":
|
|
24
|
-
return `(${column})::date ${operator} ${
|
|
25
|
+
return `(${column})::date ${operator} ${val}`;
|
|
25
26
|
case "day":
|
|
26
|
-
return `EXTRACT(DAY FROM ${column}) ${operator} ${
|
|
27
|
+
return `EXTRACT(DAY FROM ${column}) ${operator} ${val}`;
|
|
27
28
|
case "month":
|
|
28
|
-
return `EXTRACT(MONTH FROM ${column}) ${operator} ${
|
|
29
|
+
return `EXTRACT(MONTH FROM ${column}) ${operator} ${val}`;
|
|
29
30
|
case "year":
|
|
30
|
-
return `EXTRACT(YEAR FROM ${column}) ${operator} ${
|
|
31
|
+
return `EXTRACT(YEAR FROM ${column}) ${operator} ${val}`;
|
|
31
32
|
case "time":
|
|
32
|
-
return `(${column})::time ${operator} ${
|
|
33
|
+
return `(${column})::time ${operator} ${val}`;
|
|
33
34
|
default:
|
|
34
|
-
return `${column} ${operator} ${
|
|
35
|
+
return `${column} ${operator} ${val}`;
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
compileInsertOrIgnore(table, columns, values) {
|
|
@@ -43,21 +44,21 @@ export class PostgresGrammar extends Grammar {
|
|
|
43
44
|
.join(", ");
|
|
44
45
|
return `INSERT INTO ${table} (${columns.map((c) => this.wrap(c)).join(", ")}) VALUES ${values.join(", ")} ON CONFLICT (${uniqueBy.map((c) => this.wrap(c)).join(", ")}) DO UPDATE SET ${updateCols}`;
|
|
45
46
|
}
|
|
46
|
-
compileJsonContains(column, value) {
|
|
47
|
-
return `${column} @> ${this.escape(JSON.stringify([value]))}`;
|
|
47
|
+
compileJsonContains(column, value, binding) {
|
|
48
|
+
return `${column} @> ${binding ? binding(JSON.stringify([value])) : this.escape(JSON.stringify([value]))}`;
|
|
48
49
|
}
|
|
49
|
-
compileJsonLength(column, operator, value) {
|
|
50
|
-
return `jsonb_array_length(${column}) ${operator} ${this.escape(value)}`;
|
|
50
|
+
compileJsonLength(column, operator, value, binding) {
|
|
51
|
+
return `jsonb_array_length(${column}) ${operator} ${binding ? binding(value) : this.escape(value)}`;
|
|
51
52
|
}
|
|
52
|
-
compileRegexp(column, value, not) {
|
|
53
|
+
compileRegexp(column, value, not, binding) {
|
|
53
54
|
const op = not ? "!~" : "~";
|
|
54
|
-
return `${column} ${op} ${this.escape(value)}`;
|
|
55
|
+
return `${column} ${op} ${binding ? binding(value) : this.escape(value)}`;
|
|
55
56
|
}
|
|
56
|
-
compileFullText(columns, value) {
|
|
57
|
+
compileFullText(columns, value, binding) {
|
|
57
58
|
const cols = columns.length > 1
|
|
58
59
|
? `concat_ws(' ', ${columns.join(", ")})`
|
|
59
60
|
: columns[0];
|
|
60
|
-
return `to_tsvector('english', ${cols}) @@ plainto_tsquery('english', ${this.escape(value)})`;
|
|
61
|
+
return `to_tsvector('english', ${cols}) @@ plainto_tsquery('english', ${binding ? binding(value) : this.escape(value)})`;
|
|
61
62
|
}
|
|
62
63
|
compileExplain(sql) {
|
|
63
64
|
return `EXPLAIN (FORMAT JSON) ${sql}`;
|
|
@@ -4,12 +4,12 @@ export declare class SQLiteGrammar extends Grammar {
|
|
|
4
4
|
placeholder(_index: number): string;
|
|
5
5
|
compileRandomOrder(): string;
|
|
6
6
|
compileOffset(offset: number, limit?: number): string;
|
|
7
|
-
compileDateWhere(type: string, column: string, operator: string, value: any): string;
|
|
7
|
+
compileDateWhere(type: string, column: string, operator: string, value: any, binding?: (value: any) => string): string;
|
|
8
8
|
compileInsertOrIgnore(table: string, columns: string[], values: string[]): string;
|
|
9
9
|
compileUpsert(table: string, columns: string[], values: string[], uniqueBy: string[], updateColumns: string[]): string;
|
|
10
|
-
compileJsonContains(column: string, value: any): string;
|
|
11
|
-
compileJsonLength(column: string, operator: string, value: any): string;
|
|
12
|
-
compileRegexp(column: string, value: string, not: boolean): string;
|
|
13
|
-
compileFullText(columns: string[], value: string): string;
|
|
10
|
+
compileJsonContains(column: string, value: any, binding?: (value: any) => string): string;
|
|
11
|
+
compileJsonLength(column: string, operator: string, value: any, binding?: (value: any) => string): string;
|
|
12
|
+
compileRegexp(column: string, value: string, not: boolean, binding?: (value: any) => string): string;
|
|
13
|
+
compileFullText(columns: string[], value: string, binding?: (value: any) => string): string;
|
|
14
14
|
compileExplain(sql: string): string;
|
|
15
15
|
}
|
|
@@ -22,20 +22,21 @@ export class SQLiteGrammar extends Grammar {
|
|
|
22
22
|
const limitSql = limit === undefined ? "LIMIT -1 " : "";
|
|
23
23
|
return `${limitSql}OFFSET ${offset}`;
|
|
24
24
|
}
|
|
25
|
-
compileDateWhere(type, column, operator, value) {
|
|
25
|
+
compileDateWhere(type, column, operator, value, binding) {
|
|
26
|
+
const val = binding ? binding(value) : this.escape(value);
|
|
26
27
|
switch (type) {
|
|
27
28
|
case "date":
|
|
28
|
-
return `date(${column}) ${operator} ${
|
|
29
|
+
return `date(${column}) ${operator} ${val}`;
|
|
29
30
|
case "day":
|
|
30
|
-
return `CAST(strftime('%d', ${column}) AS INTEGER) ${operator} ${
|
|
31
|
+
return `CAST(strftime('%d', ${column}) AS INTEGER) ${operator} ${val}`;
|
|
31
32
|
case "month":
|
|
32
|
-
return `CAST(strftime('%m', ${column}) AS INTEGER) ${operator} ${
|
|
33
|
+
return `CAST(strftime('%m', ${column}) AS INTEGER) ${operator} ${val}`;
|
|
33
34
|
case "year":
|
|
34
|
-
return `CAST(strftime('%Y', ${column}) AS INTEGER) ${operator} ${
|
|
35
|
+
return `CAST(strftime('%Y', ${column}) AS INTEGER) ${operator} ${val}`;
|
|
35
36
|
case "time":
|
|
36
|
-
return `time(${column}) ${operator} ${
|
|
37
|
+
return `time(${column}) ${operator} ${val}`;
|
|
37
38
|
default:
|
|
38
|
-
return `${column} ${operator} ${
|
|
39
|
+
return `${column} ${operator} ${val}`;
|
|
39
40
|
}
|
|
40
41
|
}
|
|
41
42
|
compileInsertOrIgnore(table, columns, values) {
|
|
@@ -47,18 +48,18 @@ export class SQLiteGrammar extends Grammar {
|
|
|
47
48
|
.join(", ");
|
|
48
49
|
return `INSERT INTO ${table} (${columns.map((c) => this.wrap(c)).join(", ")}) VALUES ${values.join(", ")} ON CONFLICT(${uniqueBy.map((c) => this.wrap(c)).join(", ")}) DO UPDATE SET ${updateCols}`;
|
|
49
50
|
}
|
|
50
|
-
compileJsonContains(column, value) {
|
|
51
|
-
return `${column} IN (SELECT value FROM json_each(${column})) AND ${this.escape(value)} IN (SELECT value FROM json_each(${column}))`;
|
|
51
|
+
compileJsonContains(column, value, binding) {
|
|
52
|
+
return `${column} IN (SELECT value FROM json_each(${column})) AND ${binding ? binding(value) : this.escape(value)} IN (SELECT value FROM json_each(${column}))`;
|
|
52
53
|
}
|
|
53
|
-
compileJsonLength(column, operator, value) {
|
|
54
|
-
return `(SELECT COUNT(*) FROM json_each(${column})) ${operator} ${this.escape(value)}`;
|
|
54
|
+
compileJsonLength(column, operator, value, binding) {
|
|
55
|
+
return `(SELECT COUNT(*) FROM json_each(${column})) ${operator} ${binding ? binding(value) : this.escape(value)}`;
|
|
55
56
|
}
|
|
56
|
-
compileRegexp(column, value, not) {
|
|
57
|
+
compileRegexp(column, value, not, binding) {
|
|
57
58
|
const op = not ? "NOT REGEXP" : "REGEXP";
|
|
58
|
-
return `${column} ${op} ${this.escape(value)}`;
|
|
59
|
+
return `${column} ${op} ${binding ? binding(value) : this.escape(value)}`;
|
|
59
60
|
}
|
|
60
|
-
compileFullText(columns, value) {
|
|
61
|
-
return columns.map((c) => `${this.wrap(c)} LIKE ${this.escape(`%${value}%`)}`).join(" OR ");
|
|
61
|
+
compileFullText(columns, value, binding) {
|
|
62
|
+
return columns.map((c) => `${this.wrap(c)} LIKE ${binding ? binding(`%${value}%`) : this.escape(`%${value}%`)}`).join(" OR ");
|
|
62
63
|
}
|
|
63
64
|
compileExplain(sql) {
|
|
64
65
|
return `EXPLAIN QUERY PLAN ${sql}`;
|
|
@@ -29,12 +29,15 @@ export interface ForeignKeyDefinition {
|
|
|
29
29
|
onUpdate?: string;
|
|
30
30
|
}
|
|
31
31
|
export interface WhereClause {
|
|
32
|
-
type: "basic" | "in" | "null" | "raw" | "between" | "column" | "exists";
|
|
32
|
+
type: "basic" | "in" | "null" | "raw" | "between" | "column" | "exists" | "like" | "regexp" | "fulltext" | "json_contains" | "json_length" | "date" | "all" | "any";
|
|
33
33
|
column: string;
|
|
34
|
+
columns?: string[];
|
|
34
35
|
operator?: string;
|
|
35
36
|
value?: any;
|
|
36
37
|
boolean: "and" | "or";
|
|
37
38
|
scope?: string;
|
|
39
|
+
not?: boolean;
|
|
40
|
+
dateType?: string;
|
|
38
41
|
}
|
|
39
42
|
export interface OrderClause {
|
|
40
43
|
column: string;
|
package/package.json
CHANGED