Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions src/services/crud.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,15 +300,23 @@ private async prepareManyToManyAuditSnapshot(entity: T,id: number,modelSingularN
field.relationType !== 'one-to-many'
);
if (auditRelationFields.length > 0) {
const relations: any = {};
auditRelationFields.forEach(field => relations[field.name] = true);
// Fetch each audit-tracked relation independently rather than joining all of
// them into a single query: for an entity with several many-to-many
// relations, one combined query multiplies row counts across every joined
// relation at once and can blow past the statement timeout as data grows.
const auditBeforeEntity = await this.repo.findOne({
where: {
id: id,
} as unknown as FindOptionsWhere<T>,
relations: relations as any,
});
if (auditBeforeEntity) {
for (const field of auditRelationFields) {
(auditBeforeEntity as any)[field.name] = await this.repo.manager
.createQueryBuilder()
.relation(this.repo.target, field.name)
.of(id)
.loadMany();
}
Object.defineProperty(entity, AUDIT_BEFORE_SNAPSHOT, {
configurable: true,
enumerable: false,
Expand Down
23 changes: 15 additions & 8 deletions src/subscribers/audit.subscriber.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,19 +98,26 @@ export class AuditSubscriber implements EntitySubscriberInterface {
return null;
}

const relations: Record<string, boolean> = {};

auditRelationFields.forEach(field => {
relations[field.name] = true;
});

const relationBefore = event.entity?.[AUDIT_BEFORE_SNAPSHOT] ?? null;

const relationAfter = await event.queryRunner.manager.getRepository(event.metadata.target as any).findOne({
// Same fix as CRUDService.prepareManyToManyAuditSnapshot: load each audit-tracked
// relation independently instead of joining them all into one query, so the row
// count of one relation doesn't multiply against every other joined relation.
const targetRepo = event.queryRunner.manager.getRepository(event.metadata.target as any);
const relationAfter = await targetRepo.findOne({
where: { id: entityId } as any,
relations: relations as any,
});

if (relationAfter) {
for (const field of auditRelationFields) {
(relationAfter as any)[field.name] = await event.queryRunner.manager
.createQueryBuilder()
.relation(event.metadata.target as any, field.name)
.of(entityId)
.loadMany();
}
}

if (relationBefore && relationAfter) {
auditRelationFields.forEach(field => {
const oldIds = Array.isArray(relationBefore[field.name])
Expand Down