fix(tables): write single-cell edits as an atomic JSONB patch (fix row-level LWW)#5970
fix(tables): write single-cell edits as an atomic JSONB patch (fix row-level LWW)#5970waleedlatif1 wants to merge 2 commits into
Conversation
…w-level LWW) A table row stores every cell in one jsonb `data` column, and the row-edit paths called updateRow in the default replace mode — a full-object read-modify-write. Two users (or a user and an agent/copilot) editing DIFFERENT cells of the same row concurrently would clobber each other: whichever write committed last overwrote the whole row from its stale snapshot. Pass dataWriteMode: 'patch' (Postgres `data = data || patch::jsonb`) from the four partial-update callers — the UI row route, the v1 API row route, the copilot table tool, and the workflow-group cancel-write — so each cell edit merges atomically under the row lock. upsertRow (whole-row by design) and updateRowsByFilter (already uses ||) are unchanged. computedWrite stays unset on the user paths, preserving the update lock. Adds a route test asserting the UI route opts into patch mode; the updateRow patch mechanism itself is already covered.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview Partial-update callers now pass Adds a route test that the UI PATCH handler passes Reviewed by Cursor Bugbot for commit 7aff1ee. Configure here. |
Greptile SummaryThis PR changes partial table-row writes from full JSONB replacement to atomic JSONB patches.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| apps/sim/app/api/table/[tableId]/rows/[rowId]/route.test.ts | Adds focused coverage confirming that the UI row-edit route opts into atomic patch mode. |
| apps/sim/app/api/table/[tableId]/rows/[rowId]/route.ts | Changes UI partial row edits to merge only supplied cells through the existing JSONB patch mechanism. |
| apps/sim/app/api/v1/tables/[tableId]/rows/[rowId]/route.ts | Changes v1 partial row edits to use the same cell-level atomic patch behavior. |
| apps/sim/lib/copilot/tools/server/table/user-table.ts | Prevents Copilot row edits from replacing unrelated cells using a stale row snapshot. |
| apps/sim/lib/table/workflow-columns.ts | Makes execution-only cancellation writes leave the row data JSONB value unchanged. |
Sequence Diagram
sequenceDiagram
participant A as Editor A
participant B as Editor B
participant API as Row update caller
participant DB as PostgreSQL
A->>API: PATCH cell A
B->>API: PATCH cell B
API->>DB: "data = data || patchA::jsonb"
DB-->>API: Row with cell A updated
API->>DB: "data = data || patchB::jsonb"
DB-->>API: Row preserving both edits
Reviews (2): Last reviewed commit: "test(tables): drop banned inline error-m..." | Re-trigger Greptile
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 7aff1ee. Configure here.
Summary
datacolumn. The row-edit paths calledupdateRowin the default replace mode (a full-object read-modify-write), so two users — or a user and an agent/copilot — editing different cells of the same row concurrently would clobber each other: the last write overwrote the whole row from its stale snapshot (row-level last-write-wins).dataWriteMode: 'patch'(Postgresdata = data || patch::jsonb) from the four partial-update callers so each cell edit merges atomically under the row lock:PATCH /api/table/[tableId]/rows/[rowId])PATCH /api/v1/tables/[tableId]/rows/[rowId])update_rowtooldata: {}; patch makes it a true no-op instead of rewriting — and reverting — the whole column)upsertRow(whole-row by design) andupdateRowsByFilter(already uses||) are unchanged.computedWritestays unset on the user paths, preserving the normal update lock.Type of Change
Testing
updateRowJSONB-patch mechanism is already covered inupdate-row.test.ts.bun run check:api-validation:strict,tsc --noEmit, and the table + copilot suites (481 + 46 tests) pass.Checklist