@bunnykit/orm 0.1.19 → 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.d.ts +3 -0
- package/dist/src/model/Model.js +73 -4
- 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();
|
|
@@ -105,6 +105,7 @@ export declare class Model<T extends Record<string, any> = Record<string, any>>
|
|
|
105
105
|
static attributes: Record<string, any>;
|
|
106
106
|
static softDeletes: boolean;
|
|
107
107
|
static deletedAtColumn: string;
|
|
108
|
+
static preventLazyLoading: boolean;
|
|
108
109
|
$attributes: T;
|
|
109
110
|
$original: Partial<T>;
|
|
110
111
|
$exists: boolean;
|
|
@@ -112,6 +113,8 @@ export declare class Model<T extends Record<string, any> = Record<string, any>>
|
|
|
112
113
|
$casts: Record<string, CastDefinition>;
|
|
113
114
|
$connection?: Connection;
|
|
114
115
|
constructor(attributes?: Partial<T>);
|
|
116
|
+
private defineAttributeProperty;
|
|
117
|
+
private syncAttributeProperties;
|
|
115
118
|
static getTable(): string;
|
|
116
119
|
static getConnection(): Connection;
|
|
117
120
|
static setConnection(connection: Connection): void;
|
package/dist/src/model/Model.js
CHANGED
|
@@ -38,6 +38,15 @@ export class Relation {
|
|
|
38
38
|
this.builder = related.on(parent.getConnection());
|
|
39
39
|
this.foreignKey = foreignKey || this.defaultForeignKey();
|
|
40
40
|
this.localKey = localKey || related.primaryKey;
|
|
41
|
+
// Wrap getResults with lazy-loading guard
|
|
42
|
+
const originalGetResults = this.getResults.bind(this);
|
|
43
|
+
this.getResults = async () => {
|
|
44
|
+
if (this.parent.constructor.preventLazyLoading) {
|
|
45
|
+
throw new Error(`Lazy loading is prevented on ${this.parent.constructor.name}. ` +
|
|
46
|
+
`Eager load the relation using with().`);
|
|
47
|
+
}
|
|
48
|
+
return await originalGetResults();
|
|
49
|
+
};
|
|
41
50
|
}
|
|
42
51
|
defaultForeignKey() {
|
|
43
52
|
return `${snakeCase(this.parent.constructor.name)}_id`;
|
|
@@ -291,6 +300,7 @@ export class Model {
|
|
|
291
300
|
static attributes = {};
|
|
292
301
|
static softDeletes = false;
|
|
293
302
|
static deletedAtColumn = "deleted_at";
|
|
303
|
+
static preventLazyLoading = false;
|
|
294
304
|
$attributes = {};
|
|
295
305
|
$original = {};
|
|
296
306
|
$exists = false;
|
|
@@ -305,6 +315,9 @@ export class Model {
|
|
|
305
315
|
if (attributes) {
|
|
306
316
|
this.fill(attributes);
|
|
307
317
|
}
|
|
318
|
+
this.syncAttributeProperties();
|
|
319
|
+
// Minimal Proxy fallback for dynamic property access on undefined keys.
|
|
320
|
+
// Pre-defined attribute getters/setters bypass the Proxy entirely.
|
|
308
321
|
return new Proxy(this, {
|
|
309
322
|
get(target, prop, receiver) {
|
|
310
323
|
if (typeof prop === "string" && !(prop in target) && prop in target.$attributes) {
|
|
@@ -320,24 +333,55 @@ export class Model {
|
|
|
320
333
|
return Reflect.set(target, prop, value, receiver);
|
|
321
334
|
},
|
|
322
335
|
has(target, prop) {
|
|
323
|
-
|
|
336
|
+
if (typeof prop === "string" && prop in target.$attributes)
|
|
337
|
+
return true;
|
|
338
|
+
return Reflect.has(target, prop);
|
|
324
339
|
},
|
|
325
340
|
getOwnPropertyDescriptor(target, prop) {
|
|
326
|
-
if (typeof prop === "string" &&
|
|
341
|
+
if (typeof prop === "string" && prop in target.$attributes) {
|
|
327
342
|
return {
|
|
328
343
|
enumerable: true,
|
|
329
344
|
configurable: true,
|
|
330
|
-
writable: true,
|
|
331
345
|
value: target.getAttribute(prop),
|
|
332
346
|
};
|
|
333
347
|
}
|
|
334
348
|
return Reflect.getOwnPropertyDescriptor(target, prop);
|
|
335
349
|
},
|
|
336
350
|
ownKeys(target) {
|
|
337
|
-
|
|
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);
|
|
338
357
|
},
|
|
339
358
|
});
|
|
340
359
|
}
|
|
360
|
+
defineAttributeProperty(key) {
|
|
361
|
+
if (key in this)
|
|
362
|
+
return;
|
|
363
|
+
Object.defineProperty(this, key, {
|
|
364
|
+
get: () => this.getAttribute(key),
|
|
365
|
+
set: (value) => this.setAttribute(key, value),
|
|
366
|
+
enumerable: true,
|
|
367
|
+
configurable: true,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
syncAttributeProperties() {
|
|
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) {
|
|
382
|
+
this.defineAttributeProperty(key);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
341
385
|
static getTable() {
|
|
342
386
|
return this.table || snakeCase(this.name) + "s";
|
|
343
387
|
}
|
|
@@ -662,6 +706,7 @@ export class Model {
|
|
|
662
706
|
}
|
|
663
707
|
setAttribute(key, value) {
|
|
664
708
|
this.$attributes[key] = this.serializeCastAttribute(key, value);
|
|
709
|
+
this.defineAttributeProperty(key);
|
|
665
710
|
}
|
|
666
711
|
castAttribute(key, value) {
|
|
667
712
|
const cast = this.getCastDefinition(key);
|
|
@@ -817,6 +862,7 @@ export class Model {
|
|
|
817
862
|
await ObserverRegistry.dispatch("created", this);
|
|
818
863
|
await ObserverRegistry.dispatch("saved", this);
|
|
819
864
|
}
|
|
865
|
+
this.syncAttributeProperties();
|
|
820
866
|
const identityMap = IdentityMap.current();
|
|
821
867
|
if (identityMap) {
|
|
822
868
|
const pk = this.getAttribute(constructor.primaryKey);
|
|
@@ -835,6 +881,7 @@ export class Model {
|
|
|
835
881
|
if (!this.$exists) {
|
|
836
882
|
this.$attributes["created_at"] = now;
|
|
837
883
|
}
|
|
884
|
+
this.syncAttributeProperties();
|
|
838
885
|
}
|
|
839
886
|
async touch() {
|
|
840
887
|
if (!this.$exists)
|
|
@@ -850,6 +897,7 @@ export class Model {
|
|
|
850
897
|
.update({ updated_at: now });
|
|
851
898
|
this.$attributes["updated_at"] = now;
|
|
852
899
|
this.$original = { ...this.$attributes };
|
|
900
|
+
this.syncAttributeProperties();
|
|
853
901
|
return true;
|
|
854
902
|
}
|
|
855
903
|
async increment(column, amount = 1, extra = {}) {
|
|
@@ -869,6 +917,7 @@ export class Model {
|
|
|
869
917
|
this.$attributes[key] = value;
|
|
870
918
|
}
|
|
871
919
|
this.$original = { ...this.$attributes };
|
|
920
|
+
this.syncAttributeProperties();
|
|
872
921
|
return this;
|
|
873
922
|
}
|
|
874
923
|
async decrement(column, amount = 1, extra = {}) {
|
|
@@ -893,6 +942,7 @@ export class Model {
|
|
|
893
942
|
.update({ [constructor.deletedAtColumn]: deletedAt });
|
|
894
943
|
this.$attributes[constructor.deletedAtColumn] = deletedAt;
|
|
895
944
|
this.$original = { ...this.$attributes };
|
|
945
|
+
this.syncAttributeProperties();
|
|
896
946
|
}
|
|
897
947
|
else {
|
|
898
948
|
const connection = this.getConnection();
|
|
@@ -901,6 +951,10 @@ export class Model {
|
|
|
901
951
|
.delete();
|
|
902
952
|
this.$exists = false;
|
|
903
953
|
}
|
|
954
|
+
const identityMap = IdentityMap.current();
|
|
955
|
+
if (identityMap) {
|
|
956
|
+
IdentityMap.delete(constructor.getTable(), pk);
|
|
957
|
+
}
|
|
904
958
|
await ObserverRegistry.dispatch("deleted", this);
|
|
905
959
|
return true;
|
|
906
960
|
}
|
|
@@ -918,6 +972,7 @@ export class Model {
|
|
|
918
972
|
this.$attributes[constructor.deletedAtColumn] = null;
|
|
919
973
|
this.$original = { ...this.$attributes };
|
|
920
974
|
this.$exists = true;
|
|
975
|
+
this.syncAttributeProperties();
|
|
921
976
|
return true;
|
|
922
977
|
}
|
|
923
978
|
async forceDelete() {
|
|
@@ -930,6 +985,10 @@ export class Model {
|
|
|
930
985
|
.where(constructor.primaryKey, pk)
|
|
931
986
|
.delete();
|
|
932
987
|
this.$exists = false;
|
|
988
|
+
const identityMap = IdentityMap.current();
|
|
989
|
+
if (identityMap) {
|
|
990
|
+
IdentityMap.delete(constructor.getTable(), pk);
|
|
991
|
+
}
|
|
933
992
|
return true;
|
|
934
993
|
}
|
|
935
994
|
async refresh() {
|
|
@@ -937,10 +996,20 @@ export class Model {
|
|
|
937
996
|
const pk = this.getAttribute(constructor.primaryKey);
|
|
938
997
|
if (!pk)
|
|
939
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
|
+
}
|
|
940
1004
|
const result = await constructor.find(pk);
|
|
941
1005
|
if (result) {
|
|
942
1006
|
this.$attributes = result.$attributes;
|
|
943
1007
|
this.$original = { ...result.$attributes };
|
|
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
|
+
}
|
|
944
1013
|
}
|
|
945
1014
|
return this;
|
|
946
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