[Fix] Bug with one to many update in table #16340#17416
[Fix] Bug with one to many update in table #16340#17416lucasbordeau merged 5 commits intotwentyhq:mainfrom
Conversation
Greptile OverviewGreptile SummaryThis PR fixes a cache corruption issue in one-to-many relationship updates. Previously, when attaching or detaching relations, the entire cached record (including all fields and nested relations) was passed to The fix extracts only the essential fields before upserting to the store:
This ensures that only the field being modified is updated in the store, preventing unintended side effects on other fields and relations. The change is applied consistently in both Confidence Score: 5/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant Client
participant Cache as Apollo Cache
participant AttachFn as triggerAttachRelationOptimisticEffect
participant GetRecord as getRecordFromCache
participant Upsert as upsertRecordsInStore
participant Store as Record Store
Client->>AttachFn: Attach relation (sourceId, targetId, fieldName)
AttachFn->>Cache: cache.modify() - Update relation field
Note over Cache: Adds/updates relation reference in cache
AttachFn->>GetRecord: Fetch updated record from cache
GetRecord->>Cache: Read fragment with all fields
Cache-->>GetRecord: Return record with ALL fields & relations
Note over AttachFn: BEFORE: Passed entire record (corrupted fields)<br/>AFTER: Extract only id, __typename, and updated field
AttachFn->>AttachFn: Create partialRecordForStore<br/>{id, __typename, [fieldName]}
AttachFn->>Upsert: upsertRecordsInStore(partialRecordForStore)
Upsert->>Store: Merge partial record with existing store
Note over Store: Only specified field updated,<br/>other fields preserved
|
|
🚀 Preview Environment Ready! Your preview environment is available at: http://bore.pub:27084 This environment will automatically shut down when the PR is closed or after 5 hours. |
|
Hi @carbonFibreCode, I've been testing your solution but it felt like it missed the point because it did not fix the root cause but only avoided the side effect of the root cause, it could be enough for a fix, but in this part of the app, it is better to fix the root cause because the side effects could arise later and elsewhere as it is a central part. The root cause in this case was related to the special treatment we have for activities, which have a junction activityTarget table, ie We already fixed a similar issue related to the computation of the gql fields of a note or task for regular find requests, in order to optimize our queries. Here we have the opposite problem, we miss the label identifier fields of a note or task, so it overwrites the note or task in the record store with the id field only. So the root cause here is that we had to fix the generation of the record gql fields, inside Here are both the Apollo cache read fragment before and after this fix, inside fragment PersonFragment on Person {
__typename
id
noteTargets {
edges {
node {
__typename
id
}
}
}
taskTargets {
edges {
node {
__typename
id
}
}
}
// Other fields ...
}fragment PersonFragment on Person {
__typename
id
noteTargets {
edges {
node {
__typename
id
note {
__typename
id
title
}
}
}
}
taskTargets {
edges {
node {
__typename
id
task {
__typename
id
title
}
}
}
}
// Other fields ...
} |
|
Also I wanted to keep your fix as it seems like a good idea to only update what's relevant, but it introduced regression while doing a QA on relation modification, notably detaching a relation from a card, so I think it's safer to let what's in the cache overwrite the record store and only add the relevant missing fields to not risk introducing regression. |
There was a problem hiding this comment.
1 issue found across 3 files (changes from recent commits).
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerDetachRelationOptimisticEffect.ts">
<violation number="1">
P2: Passing the full cached record into the store can overwrite unrelated fields with undefined when the cache contains partial data.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
Hey @lucasbordeau, thanks a lot for digging into this and fixing the root cause. Your explanation and the updated fragments make a lot of sense . I appreciate you taking the time to walk through the reasoning. |
|
Hey @lucasbordeau! After you've done the QA of your Pull Request, you can mark it as done here. Thank you! |
1 similar comment
|
Hey @lucasbordeau! After you've done the QA of your Pull Request, you can mark it as done here. Thank you! |
…7416) fixes twentyhq#16340 we are updating the cache partially to prevent the flow of the corrupted fields into the cache the fields get corrupted during the mutation process potentially overriding the recoil cache as we are passing the `newRecordCache` directly in `upsertRecordsInStore`, the newRecordCache is thin and hence the recoil wipes out the fields that are undefined or empty we prevent this by extracting the partial data ( only the data which is being updated in the field ) and passing it to `upsertRecordsInStore`, as it only touched the specific fields and updates the data, leaving the other fields untouched. https://github.com/user-attachments/assets/5256bef7-70c3-47b3-b2ce-dd02ee1a2de8 --------- Co-authored-by: Arun kumar <arunkumar@Aruns-MacBook-Air.local> Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
fixes #16340
we are updating the cache partially to prevent the flow of the corrupted fields into the cache
the fields get corrupted during the mutation process potentially overriding the recoil cache as we are passing the
newRecordCachedirectly inupsertRecordsInStore, the newRecordCache is thin and hence the recoil wipes out the fields that are undefined or emptywe prevent this by extracting the partial data ( only the data which is being updated in the field ) and passing it to
upsertRecordsInStore, as it only touched the specific fields and updates the data, leaving the other fields untouched.Screen.Recording.2026-01-24.at.10.40.29.PM.mov