176 lines
5.3 KiB
JavaScript
176 lines
5.3 KiB
JavaScript
class InsertStatement {
|
|
order;
|
|
#keys;
|
|
#data;
|
|
constructor(keys, data, order) {
|
|
this.order = order;
|
|
this.#keys = keys;
|
|
this.#data = data;
|
|
}
|
|
getHash() {
|
|
return JSON.stringify(this.#data);
|
|
}
|
|
getData() {
|
|
const data = {};
|
|
this.#keys.forEach((key, idx) => (data[key] = this.#data[idx]));
|
|
return data;
|
|
}
|
|
}
|
|
class DeleteStatement {
|
|
#keys;
|
|
#cond;
|
|
constructor(keys, cond) {
|
|
this.#keys = keys;
|
|
this.#cond = cond;
|
|
}
|
|
getHash() {
|
|
return JSON.stringify(this.#cond);
|
|
}
|
|
getCondition() {
|
|
const cond = {};
|
|
this.#keys.forEach((key, idx) => (cond[key] = this.#cond[idx]));
|
|
return cond;
|
|
}
|
|
}
|
|
export class PivotCollectionPersister {
|
|
#inserts = new Map();
|
|
#upserts = new Map();
|
|
#deletes = new Map();
|
|
#batchSize;
|
|
#order = 0;
|
|
#meta;
|
|
#driver;
|
|
#ctx;
|
|
#schema;
|
|
#loggerContext;
|
|
constructor(meta, driver, ctx, schema, loggerContext) {
|
|
this.#meta = meta;
|
|
this.#driver = driver;
|
|
this.#ctx = ctx;
|
|
this.#schema = schema;
|
|
this.#loggerContext = loggerContext;
|
|
this.#batchSize = this.#driver.config.get('batchSize');
|
|
}
|
|
enqueueUpdate(prop, insertDiff, deleteDiff, pks, isInitialized = true) {
|
|
if (insertDiff.length) {
|
|
if (isInitialized) {
|
|
this.enqueueInsert(prop, insertDiff, pks);
|
|
} else {
|
|
this.enqueueUpsert(prop, insertDiff, pks);
|
|
}
|
|
}
|
|
if (deleteDiff === true || (Array.isArray(deleteDiff) && deleteDiff.length)) {
|
|
this.enqueueDelete(prop, deleteDiff, pks);
|
|
}
|
|
}
|
|
enqueueInsert(prop, insertDiff, pks) {
|
|
for (const fks of insertDiff) {
|
|
const statement = this.createInsertStatement(prop, fks, pks);
|
|
const hash = statement.getHash();
|
|
if (prop.owner || !this.#inserts.has(hash)) {
|
|
this.#inserts.set(hash, statement);
|
|
}
|
|
}
|
|
}
|
|
enqueueUpsert(prop, insertDiff, pks) {
|
|
for (const fks of insertDiff) {
|
|
const statement = this.createInsertStatement(prop, fks, pks);
|
|
const hash = statement.getHash();
|
|
if (prop.owner || !this.#upserts.has(hash)) {
|
|
this.#upserts.set(hash, statement);
|
|
}
|
|
}
|
|
}
|
|
createInsertStatement(prop, fks, pks) {
|
|
const { data, keys } = this.buildPivotKeysAndData(prop, fks, pks);
|
|
return new InsertStatement(keys, data, this.#order++);
|
|
}
|
|
enqueueDelete(prop, deleteDiff, pks) {
|
|
if (deleteDiff === true) {
|
|
const { data, keys } = this.buildPivotKeysAndData(prop, [], pks, true);
|
|
const statement = new DeleteStatement(keys, data);
|
|
this.#deletes.set(statement.getHash(), statement);
|
|
return;
|
|
}
|
|
for (const fks of deleteDiff) {
|
|
const { data, keys } = this.buildPivotKeysAndData(prop, fks, pks);
|
|
const statement = new DeleteStatement(keys, data);
|
|
this.#deletes.set(statement.getHash(), statement);
|
|
}
|
|
}
|
|
/**
|
|
* Build the keys and data arrays for pivot table operations.
|
|
* Handles polymorphic M:N by prepending the discriminator column/value.
|
|
*/
|
|
buildPivotKeysAndData(prop, fks, pks, deleteAll = false) {
|
|
let data;
|
|
let keys;
|
|
if (deleteAll) {
|
|
data = pks;
|
|
keys = prop.joinColumns;
|
|
} else {
|
|
data = prop.owner ? [...fks, ...pks] : [...pks, ...fks];
|
|
keys = prop.owner
|
|
? [...prop.inverseJoinColumns, ...prop.joinColumns]
|
|
: [...prop.joinColumns, ...prop.inverseJoinColumns];
|
|
}
|
|
if (prop.polymorphic && prop.discriminatorColumn && prop.discriminatorValue) {
|
|
data = [prop.discriminatorValue, ...data];
|
|
keys = [prop.discriminatorColumn, ...keys];
|
|
}
|
|
return { data, keys };
|
|
}
|
|
collectStatements(statements) {
|
|
const items = [];
|
|
for (const statement of statements.values()) {
|
|
items[statement.order] = statement.getData();
|
|
}
|
|
return items.filter(Boolean);
|
|
}
|
|
async execute() {
|
|
if (this.#deletes.size > 0) {
|
|
const deletes = [...this.#deletes.values()];
|
|
for (let i = 0; i < deletes.length; i += this.#batchSize) {
|
|
const chunk = deletes.slice(i, i + this.#batchSize);
|
|
const cond = { $or: [] };
|
|
for (const item of chunk) {
|
|
cond.$or.push(item.getCondition());
|
|
}
|
|
await this.#driver.nativeDelete(this.#meta.class, cond, {
|
|
ctx: this.#ctx,
|
|
schema: this.#schema,
|
|
loggerContext: this.#loggerContext,
|
|
});
|
|
}
|
|
}
|
|
if (this.#inserts.size > 0) {
|
|
const filtered = this.collectStatements(this.#inserts);
|
|
for (let i = 0; i < filtered.length; i += this.#batchSize) {
|
|
const chunk = filtered.slice(i, i + this.#batchSize);
|
|
await this.#driver.nativeInsertMany(this.#meta.class, chunk, {
|
|
ctx: this.#ctx,
|
|
schema: this.#schema,
|
|
convertCustomTypes: false,
|
|
processCollections: false,
|
|
loggerContext: this.#loggerContext,
|
|
});
|
|
}
|
|
}
|
|
if (this.#upserts.size > 0) {
|
|
const filtered = this.collectStatements(this.#upserts);
|
|
for (let i = 0; i < filtered.length; i += this.#batchSize) {
|
|
const chunk = filtered.slice(i, i + this.#batchSize);
|
|
await this.#driver.nativeUpdateMany(this.#meta.class, [], chunk, {
|
|
ctx: this.#ctx,
|
|
schema: this.#schema,
|
|
convertCustomTypes: false,
|
|
processCollections: false,
|
|
upsert: true,
|
|
onConflictAction: 'ignore',
|
|
loggerContext: this.#loggerContext,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|