-
-
Notifications
You must be signed in to change notification settings - Fork 611
Description
Describe the bug
In following scenario there is a "parasitic" useless UPDATEs following readonly queries.
In short, this:
await em.transactional(async (tx) => {
await tx.findOneOrFail(User, user.id, { populate: ['contacts.contact'] });
});leads to this:
console.log
[query] begin
at DefaultLogger.log (node_modules/@mikro-orm/core/logging/DefaultLogger.js:38:14)
console.log
[query] select "u0".*, "c1"."user_id" as "c1__user_id", "c1"."contact_id" as "c1__contact_id", "c2"."id" as "c2__id", "c2"."emails" as "c2__emails" from "test"."user" as "u0" left join "test"."user_contact" as "c1" on "u0"."id" = "c1"."user_id" left join "test"."contact" as "c2" on "c1"."contact_id" = "c2"."id" where "u0"."id" = 1 [took 1 ms, 1 result]
at DefaultLogger.log (node_modules/@mikro-orm/core/logging/DefaultLogger.js:38:14)
console.log
[query] update "test"."contact" set "emails" = '{foo}' where "id" = 1 [took 0 ms, 1 row affected]
at DefaultLogger.log (node_modules/@mikro-orm/core/logging/DefaultLogger.js:38:14)
console.log
[query] commit
at DefaultLogger.log (node_modules/@mikro-orm/core/logging/DefaultLogger.js:38:14)
which is extremely unexpected and actually harmful in scenarios with triggers. Because the triggers will be triggered not only with real updates, but also with read-only requests which are usually many many more than write requests.
I think the problem starts somewhere here:
private computePayload<T extends object>(entity: T, ignoreUndefined = false): EntityData<T> {
const data = this.comparator.prepareEntity(entity);
...
const diff = comparator(originalEntityData, data);
The problem is originalEntityData did not undergo a prepareEntity, but one of the things prepareEntity does with Postgres array fields like string[] is convertToDatabaseValue_*.
I see this in debugger for em.getUnitOfWork().comparator.getSnapshotGenerator('Contact').toString():
if (typeof entity.emails !== 'undefined') {
ret.emails = clone(convertToDatabaseValue_emails(entity.emails));
}
So, original value ['foo@example.com'] from originalEntityData is being compared to "prepared" value '{foo@example.com}' from data, of course being different. And this provokes a parasite update. Updating nothing, but still an update.
Reproduction
https://github.com/KazimirPodolski/mikro-orm-parasite-update-repro
Reproes with either em.transactional or em.fork. Does not repro without one.
Does not repro on sqlite in-memory driver.
Does not repro without a relation (e.g. if it was just emails in User).
What driver are you using?
@mikro-orm/postgresql
MikroORM version
At least 6.3.11 and up
Node.js version
20
Operating system
No response
Validations
- Read the Contributing Guidelines.
- Read the docs.
- Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
- Check that this is a concrete bug. For Q&A open a GitHub Discussion or join our Discord.
- The provided reproduction is a minimal reproducible example of the bug.