@bunnykit/orm 0.1.22 → 0.1.24
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/Connection.d.ts +1 -0
- package/dist/src/connection/Connection.js +26 -5
- package/dist/src/migration/Migrator.js +10 -10
- package/dist/src/model/Model.d.ts +3 -3
- package/dist/src/model/Model.js +80 -80
- package/dist/src/query/Builder.d.ts +3 -0
- package/dist/src/query/Builder.js +86 -15
- package/dist/src/query/grammars/MySqlGrammar.js +1 -1
- package/dist/src/query/grammars/PostgresGrammar.js +1 -1
- package/dist/src/query/grammars/SQLiteGrammar.js +1 -1
- package/package.json +1 -1
|
@@ -10,6 +10,7 @@ export class Connection {
|
|
|
10
10
|
schema;
|
|
11
11
|
ownsDriver;
|
|
12
12
|
transactionDepth = 0;
|
|
13
|
+
savepointId = 0;
|
|
13
14
|
constructor(config, options = {}) {
|
|
14
15
|
this.config = config;
|
|
15
16
|
this.schema = options.schema || ("schema" in config ? config.schema : undefined);
|
|
@@ -83,16 +84,36 @@ export class Connection {
|
|
|
83
84
|
return await this.driver.unsafe(sqlString, bindings);
|
|
84
85
|
}
|
|
85
86
|
async beginTransaction() {
|
|
86
|
-
|
|
87
|
+
if (this.transactionDepth === 0) {
|
|
88
|
+
await this.driver.unsafe("BEGIN");
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
await this.driver.unsafe(`SAVEPOINT bunny_trans_${++this.savepointId}`);
|
|
92
|
+
}
|
|
87
93
|
this.transactionDepth++;
|
|
88
94
|
}
|
|
89
95
|
async commit() {
|
|
90
|
-
|
|
91
|
-
|
|
96
|
+
if (this.transactionDepth <= 0)
|
|
97
|
+
return;
|
|
98
|
+
if (this.transactionDepth === 1) {
|
|
99
|
+
await this.driver.unsafe("COMMIT");
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
await this.driver.unsafe(`RELEASE SAVEPOINT bunny_trans_${this.savepointId--}`);
|
|
103
|
+
}
|
|
104
|
+
this.transactionDepth--;
|
|
92
105
|
}
|
|
93
106
|
async rollback() {
|
|
94
|
-
|
|
95
|
-
|
|
107
|
+
if (this.transactionDepth <= 0)
|
|
108
|
+
return;
|
|
109
|
+
if (this.transactionDepth === 1) {
|
|
110
|
+
await this.driver.unsafe("ROLLBACK");
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
await this.driver.unsafe(`ROLLBACK TO SAVEPOINT bunny_trans_${this.savepointId}`);
|
|
114
|
+
await this.driver.unsafe(`RELEASE SAVEPOINT bunny_trans_${this.savepointId--}`);
|
|
115
|
+
}
|
|
116
|
+
this.transactionDepth--;
|
|
96
117
|
}
|
|
97
118
|
isInTransaction() {
|
|
98
119
|
return this.transactionDepth > 0;
|
|
@@ -195,30 +195,30 @@ export class Migrator {
|
|
|
195
195
|
const statements = [];
|
|
196
196
|
for (const row of tables) {
|
|
197
197
|
const table = row[key];
|
|
198
|
-
const createRows = await this.connection.query(`SHOW CREATE TABLE ${table}`);
|
|
198
|
+
const createRows = await this.connection.query(`SHOW CREATE TABLE ${this.connection.getGrammar().wrap(table)}`);
|
|
199
199
|
statements.push(`${createRows[0]["Create Table"]};`);
|
|
200
200
|
}
|
|
201
201
|
return statements.join("\n\n") + "\n";
|
|
202
202
|
}
|
|
203
203
|
const schema = this.connection.getSchema() || "public";
|
|
204
|
-
const tables = await this.connection.query(
|
|
204
|
+
const tables = await this.connection.query("SELECT table_name FROM information_schema.tables WHERE table_schema = $1 AND table_type = 'BASE TABLE' ORDER BY table_name", [schema]);
|
|
205
205
|
const statements = [];
|
|
206
206
|
for (const tableRow of tables) {
|
|
207
207
|
const table = tableRow.table_name;
|
|
208
208
|
const columns = await this.connection.query(`SELECT column_name, data_type, is_nullable, column_default, character_maximum_length, numeric_precision, numeric_scale
|
|
209
209
|
FROM information_schema.columns
|
|
210
|
-
WHERE table_schema =
|
|
211
|
-
ORDER BY ordinal_position
|
|
210
|
+
WHERE table_schema = $1 AND table_name = $2
|
|
211
|
+
ORDER BY ordinal_position`, [schema, table]);
|
|
212
212
|
const primaryKeys = await this.connection.query(`SELECT kcu.column_name
|
|
213
213
|
FROM information_schema.table_constraints tc
|
|
214
214
|
JOIN information_schema.key_column_usage kcu
|
|
215
215
|
ON tc.constraint_name = kcu.constraint_name
|
|
216
216
|
AND tc.table_schema = kcu.table_schema
|
|
217
217
|
AND tc.table_name = kcu.table_name
|
|
218
|
-
WHERE tc.table_schema =
|
|
219
|
-
AND tc.table_name =
|
|
218
|
+
WHERE tc.table_schema = $1
|
|
219
|
+
AND tc.table_name = $2
|
|
220
220
|
AND tc.constraint_type = 'PRIMARY KEY'
|
|
221
|
-
ORDER BY kcu.ordinal_position
|
|
221
|
+
ORDER BY kcu.ordinal_position`, [schema, table]);
|
|
222
222
|
const pkColumns = primaryKeys.map((row) => row.column_name);
|
|
223
223
|
const columnSql = columns.map((column) => {
|
|
224
224
|
let type = String(column.data_type).toUpperCase();
|
|
@@ -228,7 +228,7 @@ export class Migrator {
|
|
|
228
228
|
else if ((type === "NUMERIC" || type === "DECIMAL") && column.numeric_precision) {
|
|
229
229
|
type = `${type}(${column.numeric_precision}${column.numeric_scale ? `, ${column.numeric_scale}` : ""})`;
|
|
230
230
|
}
|
|
231
|
-
let sql = `
|
|
231
|
+
let sql = ` ${this.connection.getGrammar().wrap(column.column_name)} ${type}`;
|
|
232
232
|
if (column.is_nullable === "NO")
|
|
233
233
|
sql += " NOT NULL";
|
|
234
234
|
if (column.column_default !== null && column.column_default !== undefined)
|
|
@@ -236,9 +236,9 @@ export class Migrator {
|
|
|
236
236
|
return sql;
|
|
237
237
|
});
|
|
238
238
|
if (pkColumns.length > 0) {
|
|
239
|
-
columnSql.push(` PRIMARY KEY (${pkColumns.map((column) =>
|
|
239
|
+
columnSql.push(` PRIMARY KEY (${pkColumns.map((column) => this.connection.getGrammar().wrap(column)).join(", ")})`);
|
|
240
240
|
}
|
|
241
|
-
statements.push(`CREATE TABLE
|
|
241
|
+
statements.push(`CREATE TABLE ${this.connection.getGrammar().wrap(`${schema}.${table}`)} (\n${columnSql.join(",\n")}\n);`);
|
|
242
242
|
}
|
|
243
243
|
return statements.join("\n\n") + "\n";
|
|
244
244
|
}
|
|
@@ -5,7 +5,7 @@ import { BelongsToMany } from "./BelongsToMany.js";
|
|
|
5
5
|
export type ModelConstructor<T extends Model = Model> = (new (...args: any[]) => T) & Omit<typeof Model, "prototype">;
|
|
6
6
|
export type GlobalScope = (builder: Builder<any>, model: ModelConstructor) => void;
|
|
7
7
|
export type LiteralUnion<T extends string> = T | (string & {});
|
|
8
|
-
type BaseModelInstanceKey = "$attributes" | "$original" | "$exists" | "$relations" | "$casts" | "$connection" | "fill" | "setConnection" | "getConnection" | "isFillable" | "getAttribute" | "setAttribute" | "castAttribute" | "serializeCastAttribute" | "mergeCasts" | "getDirty" | "isDirty" | "save" | "updateTimestamps" | "touch" | "increment" | "decrement" | "load" | "delete" | "restore" | "forceDelete" | "refresh" | "toJSON" | "toString" | "freshTimestamp" | "setRelation" | "getRelation" | "hasMany" | "belongsTo" | "hasOne" | "hasManyThrough" | "hasOneThrough" | "belongsToMany" | "morphTo" | "morphOne" | "morphMany" | "morphToMany" | "morphedByMany";
|
|
8
|
+
type BaseModelInstanceKey = "$attributes" | "$original" | "$exists" | "$relations" | "$casts" | "$castCache" | "$connection" | "fill" | "setConnection" | "getConnection" | "isFillable" | "getAttribute" | "setAttribute" | "castAttribute" | "serializeCastAttribute" | "mergeCasts" | "getDirty" | "isDirty" | "save" | "updateTimestamps" | "touch" | "increment" | "decrement" | "load" | "delete" | "restore" | "forceDelete" | "refresh" | "toJSON" | "toString" | "freshTimestamp" | "setRelation" | "getRelation" | "hasMany" | "belongsTo" | "hasOne" | "hasManyThrough" | "hasOneThrough" | "belongsToMany" | "morphTo" | "morphOne" | "morphMany" | "morphToMany" | "morphedByMany";
|
|
9
9
|
export type ModelInstanceAttributeKeys<T> = Extract<Exclude<keyof T, BaseModelInstanceKey>, string>;
|
|
10
10
|
export type ModelAttributes<T> = T extends {
|
|
11
11
|
$attributes: Record<string, any>;
|
|
@@ -111,10 +111,9 @@ export declare class Model<T extends Record<string, any> = Record<string, any>>
|
|
|
111
111
|
$exists: boolean;
|
|
112
112
|
$relations: Record<string, any>;
|
|
113
113
|
$casts: Record<string, CastDefinition>;
|
|
114
|
+
$castCache: Record<string, any>;
|
|
114
115
|
$connection?: Connection;
|
|
115
116
|
constructor(attributes?: Partial<T>);
|
|
116
|
-
private defineAttributeProperty;
|
|
117
|
-
private syncAttributeProperties;
|
|
118
117
|
static getTable(): string;
|
|
119
118
|
static getConnection(): Connection;
|
|
120
119
|
static setConnection(connection: Connection): void;
|
|
@@ -127,6 +126,7 @@ export declare class Model<T extends Record<string, any> = Record<string, any>>
|
|
|
127
126
|
static applyGlobalScopes(builder: Builder<any>): void;
|
|
128
127
|
static getQualifiedDeletedAtColumn(): string;
|
|
129
128
|
static shouldAutoGeneratePrimaryKey(): Promise<boolean>;
|
|
129
|
+
static hydrate<M extends ModelConstructor>(this: M, row: Record<string, any>, connection?: Connection): InstanceType<M>;
|
|
130
130
|
static create<M extends ModelConstructor>(this: M, attributes: ModelAttributeInput<InstanceType<M>>): Promise<InstanceType<M>>;
|
|
131
131
|
static find<M extends ModelConstructor>(this: M, id: any): Promise<InstanceType<M> | null>;
|
|
132
132
|
static findOrFail<M extends ModelConstructor>(this: M, id: any): Promise<InstanceType<M>>;
|
package/dist/src/model/Model.js
CHANGED
|
@@ -8,6 +8,44 @@ import { ModelNotFoundError } from "./ModelNotFoundError.js";
|
|
|
8
8
|
import { ConnectionManager } from "../connection/ConnectionManager.js";
|
|
9
9
|
import { TenantContext } from "../connection/TenantContext.js";
|
|
10
10
|
import { IdentityMap } from "./IdentityMap.js";
|
|
11
|
+
const modelProxyHandler = {
|
|
12
|
+
get(target, prop, receiver) {
|
|
13
|
+
if (typeof prop === "string" && !(prop in target) && prop in target.$attributes) {
|
|
14
|
+
return target.getAttribute(prop);
|
|
15
|
+
}
|
|
16
|
+
return Reflect.get(target, prop, receiver);
|
|
17
|
+
},
|
|
18
|
+
set(target, prop, value, receiver) {
|
|
19
|
+
if (typeof prop === "string" && !prop.startsWith("$") && !(prop in target)) {
|
|
20
|
+
target.setAttribute(prop, value);
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
return Reflect.set(target, prop, value, receiver);
|
|
24
|
+
},
|
|
25
|
+
has(target, prop) {
|
|
26
|
+
if (typeof prop === "string" && prop in target.$attributes)
|
|
27
|
+
return true;
|
|
28
|
+
return Reflect.has(target, prop);
|
|
29
|
+
},
|
|
30
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
31
|
+
if (typeof prop === "string" && prop in target.$attributes) {
|
|
32
|
+
return {
|
|
33
|
+
enumerable: true,
|
|
34
|
+
configurable: true,
|
|
35
|
+
value: target.getAttribute(prop),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return Reflect.getOwnPropertyDescriptor(target, prop);
|
|
39
|
+
},
|
|
40
|
+
ownKeys(target) {
|
|
41
|
+
const keys = new Set(Reflect.ownKeys(target));
|
|
42
|
+
for (const key of Object.keys(target.$attributes)) {
|
|
43
|
+
if (!key.startsWith("$"))
|
|
44
|
+
keys.add(key);
|
|
45
|
+
}
|
|
46
|
+
return Array.from(keys);
|
|
47
|
+
},
|
|
48
|
+
};
|
|
11
49
|
const globalScopes = new WeakMap();
|
|
12
50
|
function getGlobalScopes(model) {
|
|
13
51
|
const scopes = new Map();
|
|
@@ -306,6 +344,7 @@ export class Model {
|
|
|
306
344
|
$exists = false;
|
|
307
345
|
$relations = {};
|
|
308
346
|
$casts = {};
|
|
347
|
+
$castCache = {};
|
|
309
348
|
$connection;
|
|
310
349
|
constructor(attributes) {
|
|
311
350
|
const defaults = this.constructor.attributes;
|
|
@@ -315,72 +354,7 @@ export class Model {
|
|
|
315
354
|
if (attributes) {
|
|
316
355
|
this.fill(attributes);
|
|
317
356
|
}
|
|
318
|
-
this
|
|
319
|
-
// Minimal Proxy fallback for dynamic property access on undefined keys.
|
|
320
|
-
// Pre-defined attribute getters/setters bypass the Proxy entirely.
|
|
321
|
-
return new Proxy(this, {
|
|
322
|
-
get(target, prop, receiver) {
|
|
323
|
-
if (typeof prop === "string" && !(prop in target) && prop in target.$attributes) {
|
|
324
|
-
return target.getAttribute(prop);
|
|
325
|
-
}
|
|
326
|
-
return Reflect.get(target, prop, receiver);
|
|
327
|
-
},
|
|
328
|
-
set(target, prop, value, receiver) {
|
|
329
|
-
if (typeof prop === "string" && !prop.startsWith("$") && !(prop in target)) {
|
|
330
|
-
target.setAttribute(prop, value);
|
|
331
|
-
return true;
|
|
332
|
-
}
|
|
333
|
-
return Reflect.set(target, prop, value, receiver);
|
|
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
|
-
},
|
|
358
|
-
});
|
|
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
|
-
}
|
|
357
|
+
return new Proxy(this, modelProxyHandler);
|
|
384
358
|
}
|
|
385
359
|
static getTable() {
|
|
386
360
|
return this.table || snakeCase(this.name) + "s";
|
|
@@ -459,6 +433,17 @@ export class Model {
|
|
|
459
433
|
const numericTypes = new Set(["integer", "int", "bigint", "smallint", "tinyint", "real", "float", "double", "decimal", "numeric"]);
|
|
460
434
|
return !numericTypes.has(type);
|
|
461
435
|
}
|
|
436
|
+
static hydrate(row, connection) {
|
|
437
|
+
const instance = new this();
|
|
438
|
+
instance.$attributes = { ...instance.$attributes, ...row };
|
|
439
|
+
instance.$original = { ...row };
|
|
440
|
+
instance.$castCache = {};
|
|
441
|
+
instance.$exists = true;
|
|
442
|
+
if (connection) {
|
|
443
|
+
instance.setConnection(connection);
|
|
444
|
+
}
|
|
445
|
+
return instance;
|
|
446
|
+
}
|
|
462
447
|
static async create(attributes) {
|
|
463
448
|
const instance = new this();
|
|
464
449
|
instance.fill(attributes);
|
|
@@ -701,12 +686,19 @@ export class Model {
|
|
|
701
686
|
return true;
|
|
702
687
|
}
|
|
703
688
|
getAttribute(key) {
|
|
689
|
+
if (Object.prototype.hasOwnProperty.call(this.$castCache, key)) {
|
|
690
|
+
return this.$castCache[key];
|
|
691
|
+
}
|
|
704
692
|
const value = this.$attributes[key];
|
|
705
|
-
|
|
693
|
+
const casted = this.castAttribute(key, value);
|
|
694
|
+
if (this.getCastDefinition(key) && value !== null && value !== undefined) {
|
|
695
|
+
this.$castCache[key] = casted;
|
|
696
|
+
}
|
|
697
|
+
return casted;
|
|
706
698
|
}
|
|
707
699
|
setAttribute(key, value) {
|
|
708
700
|
this.$attributes[key] = this.serializeCastAttribute(key, value);
|
|
709
|
-
this
|
|
701
|
+
delete this.$castCache[key];
|
|
710
702
|
}
|
|
711
703
|
castAttribute(key, value) {
|
|
712
704
|
const cast = this.getCastDefinition(key);
|
|
@@ -785,6 +777,7 @@ export class Model {
|
|
|
785
777
|
}
|
|
786
778
|
mergeCasts(casts) {
|
|
787
779
|
this.$casts = { ...this.$casts, ...casts };
|
|
780
|
+
this.$castCache = {};
|
|
788
781
|
return this;
|
|
789
782
|
}
|
|
790
783
|
getCastDefinition(key) {
|
|
@@ -815,21 +808,23 @@ export class Model {
|
|
|
815
808
|
async save() {
|
|
816
809
|
const constructor = this.constructor;
|
|
817
810
|
if (this.$exists) {
|
|
818
|
-
await ObserverRegistry.dispatch("updating", this);
|
|
819
811
|
await ObserverRegistry.dispatch("saving", this);
|
|
820
|
-
|
|
812
|
+
let dirty = this.getDirty();
|
|
813
|
+
if (Object.keys(dirty).length > 0 && constructor.timestamps) {
|
|
821
814
|
this.$attributes["updated_at"] = this.freshTimestamp();
|
|
815
|
+
delete this.$castCache.updated_at;
|
|
816
|
+
dirty = this.getDirty();
|
|
822
817
|
}
|
|
823
|
-
const dirty = this.getDirty();
|
|
824
818
|
if (Object.keys(dirty).length > 0) {
|
|
819
|
+
await ObserverRegistry.dispatch("updating", this);
|
|
825
820
|
const pk = this.getAttribute(constructor.primaryKey);
|
|
826
821
|
const connection = this.getConnection();
|
|
827
822
|
await new Builder(connection, connection.qualifyTable(constructor.getTable()))
|
|
828
823
|
.where(constructor.primaryKey, pk)
|
|
829
824
|
.update(dirty);
|
|
825
|
+
await ObserverRegistry.dispatch("updated", this);
|
|
830
826
|
}
|
|
831
827
|
this.$original = { ...this.$attributes };
|
|
832
|
-
await ObserverRegistry.dispatch("updated", this);
|
|
833
828
|
await ObserverRegistry.dispatch("saved", this);
|
|
834
829
|
}
|
|
835
830
|
else {
|
|
@@ -839,6 +834,8 @@ export class Model {
|
|
|
839
834
|
const now = this.freshTimestamp();
|
|
840
835
|
this.$attributes["created_at"] = now;
|
|
841
836
|
this.$attributes["updated_at"] = now;
|
|
837
|
+
delete this.$castCache.created_at;
|
|
838
|
+
delete this.$castCache.updated_at;
|
|
842
839
|
}
|
|
843
840
|
const primaryKey = constructor.primaryKey;
|
|
844
841
|
const primaryKeyValue = this.getAttribute(primaryKey);
|
|
@@ -846,6 +843,7 @@ export class Model {
|
|
|
846
843
|
if ((primaryKeyValue === null || primaryKeyValue === undefined || primaryKeyValue === "") && shouldGeneratePrimaryKey) {
|
|
847
844
|
const generated = crypto.randomUUID();
|
|
848
845
|
this.$attributes[primaryKey] = generated;
|
|
846
|
+
delete this.$castCache[primaryKey];
|
|
849
847
|
}
|
|
850
848
|
const connection = this.getConnection();
|
|
851
849
|
if (shouldGeneratePrimaryKey || primaryKeyValue !== null && primaryKeyValue !== undefined && primaryKeyValue !== "") {
|
|
@@ -855,6 +853,7 @@ export class Model {
|
|
|
855
853
|
const result = await new Builder(connection, connection.qualifyTable(constructor.getTable())).insertGetId(this.$attributes);
|
|
856
854
|
if (result) {
|
|
857
855
|
this.$attributes[constructor.primaryKey] = result;
|
|
856
|
+
delete this.$castCache[constructor.primaryKey];
|
|
858
857
|
}
|
|
859
858
|
}
|
|
860
859
|
this.$exists = true;
|
|
@@ -862,7 +861,6 @@ export class Model {
|
|
|
862
861
|
await ObserverRegistry.dispatch("created", this);
|
|
863
862
|
await ObserverRegistry.dispatch("saved", this);
|
|
864
863
|
}
|
|
865
|
-
this.syncAttributeProperties();
|
|
866
864
|
const identityMap = IdentityMap.current();
|
|
867
865
|
if (identityMap) {
|
|
868
866
|
const pk = this.getAttribute(constructor.primaryKey);
|
|
@@ -878,10 +876,11 @@ export class Model {
|
|
|
878
876
|
return;
|
|
879
877
|
const now = this.freshTimestamp();
|
|
880
878
|
this.$attributes["updated_at"] = now;
|
|
879
|
+
delete this.$castCache.updated_at;
|
|
881
880
|
if (!this.$exists) {
|
|
882
881
|
this.$attributes["created_at"] = now;
|
|
882
|
+
delete this.$castCache.created_at;
|
|
883
883
|
}
|
|
884
|
-
this.syncAttributeProperties();
|
|
885
884
|
}
|
|
886
885
|
async touch() {
|
|
887
886
|
if (!this.$exists)
|
|
@@ -896,8 +895,8 @@ export class Model {
|
|
|
896
895
|
.where(constructor.primaryKey, pk)
|
|
897
896
|
.update({ updated_at: now });
|
|
898
897
|
this.$attributes["updated_at"] = now;
|
|
898
|
+
delete this.$castCache.updated_at;
|
|
899
899
|
this.$original = { ...this.$attributes };
|
|
900
|
-
this.syncAttributeProperties();
|
|
901
900
|
return true;
|
|
902
901
|
}
|
|
903
902
|
async increment(column, amount = 1, extra = {}) {
|
|
@@ -913,11 +912,12 @@ export class Model {
|
|
|
913
912
|
}
|
|
914
913
|
await builder.increment(column, amount, extra);
|
|
915
914
|
this.$attributes[column] = (this.$attributes[column] || 0) + amount;
|
|
915
|
+
delete this.$castCache[column];
|
|
916
916
|
for (const [key, value] of Object.entries(extra)) {
|
|
917
917
|
this.$attributes[key] = value;
|
|
918
|
+
delete this.$castCache[key];
|
|
918
919
|
}
|
|
919
920
|
this.$original = { ...this.$attributes };
|
|
920
|
-
this.syncAttributeProperties();
|
|
921
921
|
return this;
|
|
922
922
|
}
|
|
923
923
|
async decrement(column, amount = 1, extra = {}) {
|
|
@@ -930,10 +930,10 @@ export class Model {
|
|
|
930
930
|
}
|
|
931
931
|
async delete() {
|
|
932
932
|
const constructor = this.constructor;
|
|
933
|
-
await ObserverRegistry.dispatch("deleting", this);
|
|
934
933
|
const pk = this.getAttribute(constructor.primaryKey);
|
|
935
934
|
if (!pk)
|
|
936
935
|
return false;
|
|
936
|
+
await ObserverRegistry.dispatch("deleting", this);
|
|
937
937
|
if (constructor.softDeletes) {
|
|
938
938
|
const deletedAt = this.freshTimestamp();
|
|
939
939
|
const connection = this.getConnection();
|
|
@@ -941,8 +941,8 @@ export class Model {
|
|
|
941
941
|
.where(constructor.primaryKey, pk)
|
|
942
942
|
.update({ [constructor.deletedAtColumn]: deletedAt });
|
|
943
943
|
this.$attributes[constructor.deletedAtColumn] = deletedAt;
|
|
944
|
+
delete this.$castCache[constructor.deletedAtColumn];
|
|
944
945
|
this.$original = { ...this.$attributes };
|
|
945
|
-
this.syncAttributeProperties();
|
|
946
946
|
}
|
|
947
947
|
else {
|
|
948
948
|
const connection = this.getConnection();
|
|
@@ -970,9 +970,9 @@ export class Model {
|
|
|
970
970
|
.where(constructor.primaryKey, pk)
|
|
971
971
|
.update({ [constructor.deletedAtColumn]: null });
|
|
972
972
|
this.$attributes[constructor.deletedAtColumn] = null;
|
|
973
|
+
delete this.$castCache[constructor.deletedAtColumn];
|
|
973
974
|
this.$original = { ...this.$attributes };
|
|
974
975
|
this.$exists = true;
|
|
975
|
-
this.syncAttributeProperties();
|
|
976
976
|
return true;
|
|
977
977
|
}
|
|
978
978
|
async forceDelete() {
|
|
@@ -1005,7 +1005,7 @@ export class Model {
|
|
|
1005
1005
|
if (result) {
|
|
1006
1006
|
this.$attributes = result.$attributes;
|
|
1007
1007
|
this.$original = { ...result.$attributes };
|
|
1008
|
-
this
|
|
1008
|
+
this.$castCache = {};
|
|
1009
1009
|
// Ensure this instance is the canonical one in the identity map
|
|
1010
1010
|
if (identityMap) {
|
|
1011
1011
|
IdentityMap.set(constructor.getTable(), pk, this);
|
|
@@ -32,8 +32,10 @@ export declare class Builder<T = Record<string, any>> {
|
|
|
32
32
|
updateJoins: string[];
|
|
33
33
|
bindings: any[];
|
|
34
34
|
private parameterize;
|
|
35
|
+
private sqlCache?;
|
|
35
36
|
constructor(connection: Connection, table: string);
|
|
36
37
|
private get grammar();
|
|
38
|
+
private invalidateSqlCache;
|
|
37
39
|
setModel(model: ModelConstructor): this;
|
|
38
40
|
table(table: string): this;
|
|
39
41
|
select(...columns: ModelColumn<T>[]): this;
|
|
@@ -168,6 +170,7 @@ export declare class Builder<T = Record<string, any>> {
|
|
|
168
170
|
insertGetId(data: ModelAttributeInput<T>, idColumn?: ModelColumn<T>): Promise<any>;
|
|
169
171
|
insertOrIgnore(data: ModelAttributeInput<T> | ModelAttributeInput<T>[]): Promise<any>;
|
|
170
172
|
upsert(data: ModelAttributeInput<T> | ModelAttributeInput<T>[], uniqueBy: ModelColumn<T> | ModelColumn<T>[], updateColumns?: ModelColumn<T>[]): Promise<any>;
|
|
173
|
+
private getUniformColumns;
|
|
171
174
|
update(data: ModelAttributeInput<T>): Promise<any>;
|
|
172
175
|
delete(): Promise<any>;
|
|
173
176
|
increment(column: ModelColumn<T>, amount?: number, extra?: ModelAttributeInput<T>): Promise<any>;
|
|
@@ -21,6 +21,7 @@ export class Builder {
|
|
|
21
21
|
updateJoins = [];
|
|
22
22
|
bindings = [];
|
|
23
23
|
parameterize = false;
|
|
24
|
+
sqlCache;
|
|
24
25
|
constructor(connection, table) {
|
|
25
26
|
this.connection = connection;
|
|
26
27
|
this.tableName = table;
|
|
@@ -28,19 +29,25 @@ export class Builder {
|
|
|
28
29
|
get grammar() {
|
|
29
30
|
return this.connection.getGrammar();
|
|
30
31
|
}
|
|
32
|
+
invalidateSqlCache() {
|
|
33
|
+
this.sqlCache = undefined;
|
|
34
|
+
}
|
|
31
35
|
setModel(model) {
|
|
32
36
|
this.model = model;
|
|
33
37
|
return this;
|
|
34
38
|
}
|
|
35
39
|
table(table) {
|
|
40
|
+
this.invalidateSqlCache();
|
|
36
41
|
this.tableName = table;
|
|
37
42
|
return this;
|
|
38
43
|
}
|
|
39
44
|
select(...columns) {
|
|
45
|
+
this.invalidateSqlCache();
|
|
40
46
|
this.columns = columns;
|
|
41
47
|
return this;
|
|
42
48
|
}
|
|
43
49
|
distinct() {
|
|
50
|
+
this.invalidateSqlCache();
|
|
44
51
|
this.distinctFlag = true;
|
|
45
52
|
return this;
|
|
46
53
|
}
|
|
@@ -58,6 +65,7 @@ export class Builder {
|
|
|
58
65
|
value = operator;
|
|
59
66
|
operator = "=";
|
|
60
67
|
}
|
|
68
|
+
this.invalidateSqlCache();
|
|
61
69
|
this.wheres.push({ type: "basic", column, operator, value, boolean, scope });
|
|
62
70
|
return this;
|
|
63
71
|
}
|
|
@@ -65,6 +73,7 @@ export class Builder {
|
|
|
65
73
|
const nested = new Builder(this.connection, this.tableName);
|
|
66
74
|
callback(nested);
|
|
67
75
|
if (nested.wheres.length > 0) {
|
|
76
|
+
this.invalidateSqlCache();
|
|
68
77
|
this.wheres.push({ type: "nested", column: "", query: nested.wheres, boolean, scope: undefined });
|
|
69
78
|
}
|
|
70
79
|
return this;
|
|
@@ -85,26 +94,32 @@ export class Builder {
|
|
|
85
94
|
return this.whereNot(column, value, "or");
|
|
86
95
|
}
|
|
87
96
|
whereIn(column, values, boolean = "and", scope) {
|
|
97
|
+
this.invalidateSqlCache();
|
|
88
98
|
this.wheres.push({ type: "in", column, value: values, boolean, scope });
|
|
89
99
|
return this;
|
|
90
100
|
}
|
|
91
101
|
whereNotIn(column, values, boolean = "and", scope) {
|
|
102
|
+
this.invalidateSqlCache();
|
|
92
103
|
this.wheres.push({ type: "in", column, value: values, boolean, operator: "NOT IN", scope });
|
|
93
104
|
return this;
|
|
94
105
|
}
|
|
95
106
|
whereNull(column, boolean = "and", scope) {
|
|
107
|
+
this.invalidateSqlCache();
|
|
96
108
|
this.wheres.push({ type: "null", column, boolean, scope });
|
|
97
109
|
return this;
|
|
98
110
|
}
|
|
99
111
|
whereNotNull(column, boolean = "and", scope) {
|
|
112
|
+
this.invalidateSqlCache();
|
|
100
113
|
this.wheres.push({ type: "null", column, boolean, operator: "NOT NULL", scope });
|
|
101
114
|
return this;
|
|
102
115
|
}
|
|
103
116
|
whereBetween(column, values, boolean = "and", scope) {
|
|
117
|
+
this.invalidateSqlCache();
|
|
104
118
|
this.wheres.push({ type: "between", column, value: values, boolean, scope });
|
|
105
119
|
return this;
|
|
106
120
|
}
|
|
107
121
|
whereNotBetween(column, values, boolean = "and", scope) {
|
|
122
|
+
this.invalidateSqlCache();
|
|
108
123
|
this.wheres.push({ type: "between", column, value: values, boolean, operator: "NOT BETWEEN", scope });
|
|
109
124
|
return this;
|
|
110
125
|
}
|
|
@@ -139,14 +154,17 @@ export class Builder {
|
|
|
139
154
|
return this.whereTime(column, operator, value, "or");
|
|
140
155
|
}
|
|
141
156
|
whereRaw(sql, boolean = "and", scope) {
|
|
157
|
+
this.invalidateSqlCache();
|
|
142
158
|
this.wheres.push({ type: "raw", column: sql, boolean, scope });
|
|
143
159
|
return this;
|
|
144
160
|
}
|
|
145
161
|
whereColumn(first, operator, second, boolean = "and") {
|
|
162
|
+
this.invalidateSqlCache();
|
|
146
163
|
this.wheres.push({ type: "column", column: first, operator, value: second, boolean });
|
|
147
164
|
return this;
|
|
148
165
|
}
|
|
149
166
|
whereExists(sql, boolean = "and", not = false) {
|
|
167
|
+
this.invalidateSqlCache();
|
|
150
168
|
this.wheres.push({ type: "exists", column: sql, boolean, operator: not ? "NOT EXISTS" : "EXISTS" });
|
|
151
169
|
return this;
|
|
152
170
|
}
|
|
@@ -181,6 +199,7 @@ export class Builder {
|
|
|
181
199
|
return this.whereRaw(sql, "or", scope);
|
|
182
200
|
}
|
|
183
201
|
whereJsonContains(column, value, boolean = "and", not = false) {
|
|
202
|
+
this.invalidateSqlCache();
|
|
184
203
|
this.wheres.push({ type: "json_contains", column, value, boolean, scope: undefined, not });
|
|
185
204
|
return this;
|
|
186
205
|
}
|
|
@@ -189,10 +208,12 @@ export class Builder {
|
|
|
189
208
|
value = operator;
|
|
190
209
|
operator = "=";
|
|
191
210
|
}
|
|
211
|
+
this.invalidateSqlCache();
|
|
192
212
|
this.wheres.push({ type: "json_length", column, operator: String(operator), value, boolean, scope: undefined, not });
|
|
193
213
|
return this;
|
|
194
214
|
}
|
|
195
215
|
whereLike(column, value, boolean = "and", not = false) {
|
|
216
|
+
this.invalidateSqlCache();
|
|
196
217
|
this.wheres.push({ type: "like", column, value, boolean, scope: undefined, not });
|
|
197
218
|
return this;
|
|
198
219
|
}
|
|
@@ -200,23 +221,28 @@ export class Builder {
|
|
|
200
221
|
return this.whereLike(column, value, "and", true);
|
|
201
222
|
}
|
|
202
223
|
whereRegexp(column, value, boolean = "and", not = false) {
|
|
224
|
+
this.invalidateSqlCache();
|
|
203
225
|
this.wheres.push({ type: "regexp", column, value, boolean, scope: undefined, not });
|
|
204
226
|
return this;
|
|
205
227
|
}
|
|
206
228
|
whereFullText(columns, value, boolean = "and", not = false) {
|
|
207
229
|
const cols = Array.isArray(columns) ? columns : [columns];
|
|
230
|
+
this.invalidateSqlCache();
|
|
208
231
|
this.wheres.push({ type: "fulltext", column: "", columns: cols, value, boolean, scope: undefined, not });
|
|
209
232
|
return this;
|
|
210
233
|
}
|
|
211
234
|
whereAll(columns, operator, value, boolean = "and") {
|
|
235
|
+
this.invalidateSqlCache();
|
|
212
236
|
this.wheres.push({ type: "all", column: "", columns: columns, operator, value, boolean, scope: undefined });
|
|
213
237
|
return this;
|
|
214
238
|
}
|
|
215
239
|
whereAny(columns, operator, value, boolean = "and") {
|
|
240
|
+
this.invalidateSqlCache();
|
|
216
241
|
this.wheres.push({ type: "any", column: "", columns: columns, operator, value, boolean, scope: undefined });
|
|
217
242
|
return this;
|
|
218
243
|
}
|
|
219
244
|
orderBy(column, direction = "asc") {
|
|
245
|
+
this.invalidateSqlCache();
|
|
220
246
|
this.orders.push({ column, direction });
|
|
221
247
|
return this;
|
|
222
248
|
}
|
|
@@ -227,6 +253,7 @@ export class Builder {
|
|
|
227
253
|
return this.orderBy(column, "asc");
|
|
228
254
|
}
|
|
229
255
|
inRandomOrder() {
|
|
256
|
+
this.invalidateSqlCache();
|
|
230
257
|
this.randomOrderFlag = true;
|
|
231
258
|
return this;
|
|
232
259
|
}
|
|
@@ -234,6 +261,7 @@ export class Builder {
|
|
|
234
261
|
return this.orderBy(column, "desc");
|
|
235
262
|
}
|
|
236
263
|
reorder(column, direction = "asc") {
|
|
264
|
+
this.invalidateSqlCache();
|
|
237
265
|
this.orders = [];
|
|
238
266
|
this.randomOrderFlag = false;
|
|
239
267
|
if (column) {
|
|
@@ -242,18 +270,22 @@ export class Builder {
|
|
|
242
270
|
return this;
|
|
243
271
|
}
|
|
244
272
|
groupBy(...columns) {
|
|
273
|
+
this.invalidateSqlCache();
|
|
245
274
|
this.groups.push(...columns);
|
|
246
275
|
return this;
|
|
247
276
|
}
|
|
248
277
|
having(column, operator, value) {
|
|
278
|
+
this.invalidateSqlCache();
|
|
249
279
|
this.havings.push({ column, operator, value, boolean: "and" });
|
|
250
280
|
return this;
|
|
251
281
|
}
|
|
252
282
|
orHaving(column, operator, value) {
|
|
283
|
+
this.invalidateSqlCache();
|
|
253
284
|
this.havings.push({ column, operator, value, boolean: "or" });
|
|
254
285
|
return this;
|
|
255
286
|
}
|
|
256
287
|
havingRaw(sql, boolean = "and") {
|
|
288
|
+
this.invalidateSqlCache();
|
|
257
289
|
this.havings.push({ sql, boolean });
|
|
258
290
|
return this;
|
|
259
291
|
}
|
|
@@ -261,10 +293,12 @@ export class Builder {
|
|
|
261
293
|
return this.havingRaw(sql, "or");
|
|
262
294
|
}
|
|
263
295
|
limit(count) {
|
|
296
|
+
this.invalidateSqlCache();
|
|
264
297
|
this.limitValue = count;
|
|
265
298
|
return this;
|
|
266
299
|
}
|
|
267
300
|
offset(count) {
|
|
301
|
+
this.invalidateSqlCache();
|
|
268
302
|
this.offsetValue = count;
|
|
269
303
|
return this;
|
|
270
304
|
}
|
|
@@ -273,6 +307,7 @@ export class Builder {
|
|
|
273
307
|
}
|
|
274
308
|
join(table, first, operator, second, type = "INNER") {
|
|
275
309
|
const joinSql = `${type} JOIN ${this.grammar.wrap(table)} ON ${this.grammar.wrap(first)} ${operator} ${this.grammar.wrap(second)}`;
|
|
310
|
+
this.invalidateSqlCache();
|
|
276
311
|
this.joins.push(joinSql);
|
|
277
312
|
return this;
|
|
278
313
|
}
|
|
@@ -283,11 +318,13 @@ export class Builder {
|
|
|
283
318
|
return this.join(table, first, operator, second, "RIGHT");
|
|
284
319
|
}
|
|
285
320
|
crossJoin(table) {
|
|
321
|
+
this.invalidateSqlCache();
|
|
286
322
|
this.joins.push(`CROSS JOIN ${this.grammar.wrap(table)}`);
|
|
287
323
|
return this;
|
|
288
324
|
}
|
|
289
325
|
union(query, all = false) {
|
|
290
326
|
const sql = typeof query === "string" ? query : query.toSql();
|
|
327
|
+
this.invalidateSqlCache();
|
|
291
328
|
this.unions.push({ query: sql, all });
|
|
292
329
|
return this;
|
|
293
330
|
}
|
|
@@ -299,10 +336,12 @@ export class Builder {
|
|
|
299
336
|
return this;
|
|
300
337
|
}
|
|
301
338
|
withoutGlobalScope(scope) {
|
|
339
|
+
this.invalidateSqlCache();
|
|
302
340
|
this.wheres = this.wheres.filter((where) => where.scope !== scope);
|
|
303
341
|
return this;
|
|
304
342
|
}
|
|
305
343
|
withoutGlobalScopes() {
|
|
344
|
+
this.invalidateSqlCache();
|
|
306
345
|
this.wheres = this.wheres.filter((where) => !where.scope);
|
|
307
346
|
return this;
|
|
308
347
|
}
|
|
@@ -407,6 +446,7 @@ export class Builder {
|
|
|
407
446
|
return this.withAggregate(relationName, column, "MAX", alias);
|
|
408
447
|
}
|
|
409
448
|
addSelect(...columns) {
|
|
449
|
+
this.invalidateSqlCache();
|
|
410
450
|
if (this.columns.length === 1 && this.columns[0] === "*") {
|
|
411
451
|
this.columns = [`${this.tableName}.*`];
|
|
412
452
|
}
|
|
@@ -414,15 +454,18 @@ export class Builder {
|
|
|
414
454
|
return this;
|
|
415
455
|
}
|
|
416
456
|
selectRaw(sql) {
|
|
457
|
+
this.invalidateSqlCache();
|
|
417
458
|
this.columns.push(sql);
|
|
418
459
|
return this;
|
|
419
460
|
}
|
|
420
461
|
fromSub(query, as) {
|
|
421
462
|
const sql = typeof query === "string" ? query : query.toSql();
|
|
463
|
+
this.invalidateSqlCache();
|
|
422
464
|
this.fromRaw = `(${sql}) AS ${this.grammar.wrap(as)}`;
|
|
423
465
|
return this;
|
|
424
466
|
}
|
|
425
467
|
updateFrom(table, first, operator, second) {
|
|
468
|
+
this.invalidateSqlCache();
|
|
426
469
|
this.updateJoins.push(`INNER JOIN ${this.grammar.wrap(table)} ON ${this.grammar.wrap(first)} ${operator} ${this.grammar.wrap(second)}`);
|
|
427
470
|
return this;
|
|
428
471
|
}
|
|
@@ -598,6 +641,8 @@ export class Builder {
|
|
|
598
641
|
return column.includes("(") || /\s+as\s+/i.test(column) || /^[0-9]+$/.test(column);
|
|
599
642
|
}
|
|
600
643
|
toSql() {
|
|
644
|
+
if (!this.parameterize && this.sqlCache)
|
|
645
|
+
return this.sqlCache;
|
|
601
646
|
const distinct = this.distinctFlag ? "DISTINCT " : "";
|
|
602
647
|
const from = this.fromRaw || this.grammar.wrap(this.tableName);
|
|
603
648
|
let sql = `SELECT ${distinct}${this.compileColumns()} FROM ${from}`;
|
|
@@ -613,7 +658,10 @@ export class Builder {
|
|
|
613
658
|
for (const union of this.unions) {
|
|
614
659
|
sql += ` UNION${union.all ? " ALL" : ""} ${union.query}`;
|
|
615
660
|
}
|
|
616
|
-
|
|
661
|
+
const compiled = sql.replace(/\s+/g, " ").trim();
|
|
662
|
+
if (!this.parameterize)
|
|
663
|
+
this.sqlCache = compiled;
|
|
664
|
+
return compiled;
|
|
617
665
|
}
|
|
618
666
|
async get() {
|
|
619
667
|
this.bindings = [];
|
|
@@ -636,12 +684,7 @@ export class Builder {
|
|
|
636
684
|
}
|
|
637
685
|
}
|
|
638
686
|
}
|
|
639
|
-
const instance =
|
|
640
|
-
instance.$exists = true;
|
|
641
|
-
instance.$original = { ...row };
|
|
642
|
-
if (typeof instance.setConnection === "function") {
|
|
643
|
-
instance.setConnection(this.connection);
|
|
644
|
-
}
|
|
687
|
+
const instance = this.model.hydrate(row, this.connection);
|
|
645
688
|
if (identityMap) {
|
|
646
689
|
const pk = row[primaryKey];
|
|
647
690
|
if (pk !== null && pk !== undefined) {
|
|
@@ -717,10 +760,16 @@ export class Builder {
|
|
|
717
760
|
return results.map((row) => row[column]);
|
|
718
761
|
}
|
|
719
762
|
async aggregate(sql, alias) {
|
|
720
|
-
const
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
763
|
+
const query = this.clone();
|
|
764
|
+
query.model = undefined;
|
|
765
|
+
query.columns = [`${sql} as ${alias}`];
|
|
766
|
+
query.orders = [];
|
|
767
|
+
query.limitValue = undefined;
|
|
768
|
+
query.offsetValue = undefined;
|
|
769
|
+
query.eagerLoads = [];
|
|
770
|
+
query.lockMode = undefined;
|
|
771
|
+
query.invalidateSqlCache();
|
|
772
|
+
const result = await query.first();
|
|
724
773
|
return result ? result[alias] : null;
|
|
725
774
|
}
|
|
726
775
|
async count(column = "*") {
|
|
@@ -739,7 +788,12 @@ export class Builder {
|
|
|
739
788
|
return await this.aggregate(`MAX(${column})`, "max");
|
|
740
789
|
}
|
|
741
790
|
async paginate(perPage = 15, page = 1) {
|
|
742
|
-
const
|
|
791
|
+
const countQuery = this.clone();
|
|
792
|
+
countQuery.limitValue = undefined;
|
|
793
|
+
countQuery.offsetValue = undefined;
|
|
794
|
+
countQuery.orders = [];
|
|
795
|
+
countQuery.invalidateSqlCache();
|
|
796
|
+
const total = await countQuery.count();
|
|
743
797
|
const data = await this.clone().forPage(page, perPage).get();
|
|
744
798
|
return {
|
|
745
799
|
data,
|
|
@@ -867,7 +921,7 @@ export class Builder {
|
|
|
867
921
|
const records = Array.isArray(data) ? data : [data];
|
|
868
922
|
if (records.length === 0)
|
|
869
923
|
return;
|
|
870
|
-
const columns =
|
|
924
|
+
const columns = this.getUniformColumns(records);
|
|
871
925
|
const bindings = [];
|
|
872
926
|
const values = records.map((record) => {
|
|
873
927
|
return `(${columns.map((col) => {
|
|
@@ -886,7 +940,7 @@ export class Builder {
|
|
|
886
940
|
const records = Array.isArray(data) ? data : [data];
|
|
887
941
|
if (records.length === 0)
|
|
888
942
|
return;
|
|
889
|
-
const columns =
|
|
943
|
+
const columns = this.getUniformColumns(records);
|
|
890
944
|
const bindings = [];
|
|
891
945
|
const values = records.map((record) => {
|
|
892
946
|
return `(${columns.map((col) => {
|
|
@@ -901,7 +955,7 @@ export class Builder {
|
|
|
901
955
|
const records = Array.isArray(data) ? data : [data];
|
|
902
956
|
if (records.length === 0)
|
|
903
957
|
return;
|
|
904
|
-
const columns =
|
|
958
|
+
const columns = this.getUniformColumns(records);
|
|
905
959
|
const bindings = [];
|
|
906
960
|
const values = records.map((record) => {
|
|
907
961
|
return `(${columns.map((col) => {
|
|
@@ -914,6 +968,17 @@ export class Builder {
|
|
|
914
968
|
const sql = this.grammar.compileUpsert(this.grammar.wrap(this.tableName), columns, values, uniqueCols, updateCols);
|
|
915
969
|
return await this.connection.run(sql, bindings);
|
|
916
970
|
}
|
|
971
|
+
getUniformColumns(records) {
|
|
972
|
+
const columns = Object.keys(records[0]);
|
|
973
|
+
const signature = [...columns].sort().join("\0");
|
|
974
|
+
for (let i = 1; i < records.length; i++) {
|
|
975
|
+
const recordSignature = Object.keys(records[i]).sort().join("\0");
|
|
976
|
+
if (recordSignature !== signature) {
|
|
977
|
+
throw new Error("Bulk insert records must have the same columns.");
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
return columns;
|
|
981
|
+
}
|
|
917
982
|
async update(data) {
|
|
918
983
|
this.bindings = [];
|
|
919
984
|
this.parameterize = true;
|
|
@@ -1003,6 +1068,7 @@ export class Builder {
|
|
|
1003
1068
|
lockForUpdate() {
|
|
1004
1069
|
const driver = this.connection.getDriverName();
|
|
1005
1070
|
if (driver !== "sqlite") {
|
|
1071
|
+
this.invalidateSqlCache();
|
|
1006
1072
|
this.lockMode = "FOR UPDATE";
|
|
1007
1073
|
}
|
|
1008
1074
|
return this;
|
|
@@ -1010,21 +1076,25 @@ export class Builder {
|
|
|
1010
1076
|
sharedLock() {
|
|
1011
1077
|
const driver = this.connection.getDriverName();
|
|
1012
1078
|
if (driver === "mysql") {
|
|
1079
|
+
this.invalidateSqlCache();
|
|
1013
1080
|
this.lockMode = "LOCK IN SHARE MODE";
|
|
1014
1081
|
}
|
|
1015
1082
|
else if (driver === "postgres") {
|
|
1083
|
+
this.invalidateSqlCache();
|
|
1016
1084
|
this.lockMode = "FOR SHARE";
|
|
1017
1085
|
}
|
|
1018
1086
|
return this;
|
|
1019
1087
|
}
|
|
1020
1088
|
skipLocked() {
|
|
1021
1089
|
if (this.lockMode) {
|
|
1090
|
+
this.invalidateSqlCache();
|
|
1022
1091
|
this.lockMode += " SKIP LOCKED";
|
|
1023
1092
|
}
|
|
1024
1093
|
return this;
|
|
1025
1094
|
}
|
|
1026
1095
|
noWait() {
|
|
1027
1096
|
if (this.lockMode) {
|
|
1097
|
+
this.invalidateSqlCache();
|
|
1028
1098
|
this.lockMode += " NOWAIT";
|
|
1029
1099
|
}
|
|
1030
1100
|
return this;
|
|
@@ -1034,6 +1104,7 @@ export class Builder {
|
|
|
1034
1104
|
value = operator;
|
|
1035
1105
|
operator = "=";
|
|
1036
1106
|
}
|
|
1107
|
+
this.invalidateSqlCache();
|
|
1037
1108
|
this.wheres.push({ type: "date", column: column, operator, value, boolean, scope: undefined, dateType: type });
|
|
1038
1109
|
return this;
|
|
1039
1110
|
}
|
package/package.json
CHANGED