Commit 0dcbc56
authored
feat(tables): per-table mutation locks (schema/insert/update/delete) (#5960)
* feat(tables): per-table mutation locks (schema/insert/update/delete)
Adds four independent, admin-only locks to a table so a workspace can make it
append-only, read-only, or schema-frozen. Enforced at the lib/table service
layer rather than the routes, so the API, workflow blocks, and Mothership (which
calls the services directly) are all covered by one assert. Violations return
423; changing a lock requires workspace admin and returns 403.
* fix(tables): address review — CI snapshot, lock-aware jobs, UI gating
- Add the drizzle meta snapshot the hand-written migration was missing, which
made CI regenerate the lock columns as a phantom 0271
- Stop a queued or retried delete/update job that starts after its lock is
enabled; a run that has already committed pages still finishes, since pages
are never rolled back and aborting would leave an uncancelled half-done state
- Map TableLockedError to 423 on the internal single-row DELETE (was a 500)
- Gate the lock settings UI behind NEXT_PUBLIC_TABLE_LOCKS so it can't open
onto a Save that 403s against the server-side flag
- Respect the delete lock on column drops in the grid, matching
assertColumnDestructive, instead of failing only on click
* fix(tables): close lock gaps in async import and undo/redo
- Assert insert (and delete for replace) at the start of the import worker.
Replace deleted every row before the first insert assert, so an insert-locked
table was wiped and then failed, leaving it empty.
- Run the delete/update worker start checks through assertRowDelete /
assertRowUpdate instead of reading the lock flags directly, so the worker and
its enqueue site apply identical rules — including the workflow-column
exemption a bulk update was being cancelled despite.
- Make undo/redo verb mapping direction-aware for column ops: dropping or
retyping a column needs the delete lock clear too, matching
assertColumnDestructive, so those steps are skipped instead of stranded.
* fix(tables): stop locks over-blocking workflow runs, duplicate, and imports
- Run / Re-run / Stop no longer inherit the cell-edit gate: they write only
workflow-output columns (which the update lock exempts) and Stop is a cancel
- Duplicate needs only the insert lock — it inserts a full copied row, so it
stays available on an append-only table
- updateWorkflowGroup asserts the destructive rule only when a patch actually
drops or remaps output columns; rename / autoRun / mapping edits need just
the schema lock
- Gate the import-async enqueue on insert (and delete for replace) so a locked
table 423s instead of claiming the write-job slot and failing in the worker
- Disable Import CSV under an insert lock, and wire the expanded cell editor
and the previously-unused stable add-row handler to the lock-aware flags
* fix(tables): keep column menu usable and let locks always be cleared
- Pass the blocked-delete handler instead of undefined so ColumnOptionsMenu
still mounts on a delete-locked table; withholding it hid the entire menu,
including Insert column, which the delete lock must not affect
- Restore ColumnHeaderMenu readOnly to permission-only: it swaps the header for
a static label, which was also disabling pin, column-select and open-config
- Assert the schema lock at the import-async enqueue when createColumns is set
- Allow a locks PATCH that only clears locks even when the feature flag is off,
and keep the settings entry reachable on a locked table, so flipping the kill
switch can't strand a table with locks nobody can remove
* fix(tables): scope the update-lock exemption and re-check locks per page
- Make the workflow-output carve-out opt-in via `computedWrite`, set only by
the cell-write path. It was caller-agnostic, so an ordinary API caller could
PATCH a workflow-output column on an update-locked table
- Re-read the lock before every page in the delete and update runners. The
single worker-start check went stale immediately, so enabling a lock could
not stop a job already deleting or overwriting rows. Committed pages still
stay committed, exactly as with an explicit cancel. Import keeps its
one-shot check: a half-imported table needs the deletes the lock now forbids
- Route Insert column to the locked-action modal under a schema lock instead
of leaving it live (regular header) or hiding the whole menu (group header)
* fix(tables): revalidate locks in-transaction and unbreak backfill + partial unlock
- Re-assert the lock inside each page-write transaction, under the same
advisory lock updateTableLocks takes, so check-then-write is atomic against
a lock change. The per-page check alone still left the page-selection await
between the assert and the write
- Pass computedWrite from the backfill runner: batchUpdateRows is the
workflow-output backfill path, so scoping the exemption had made rebuilding
outputs 423 on an update-locked table while live cell writes still worked
- Gate the feature flag on a lock actually going off->on rather than on an
all-false payload. The settings UI always submits all four flags, so with
the flag off an admin could not clear one lock while another stayed on
- Hoist the lock-kind -> flag map to lib/table/types as TABLE_LOCK_FLAGS
* fix(tables): guard import batches in-transaction and split paste by verb
- Re-assert the insert lock inside each import batch's insert transaction,
under the same advisory lock updateTableLocks takes. Reversing the earlier
"let the file finish" call: an admin can lift the lock to clean up a partial
import, so honouring it beats letting the rest of the file land
- Gate paste per verb. Overwriting existing rows is an update and extending
past the last row is a full-row insert, so a paste-append still works on an
append-only table while an overwrite explains itself. Refuses the whole
paste rather than applying half of it
- Space opens the same row editor as double-click, so it now follows the
update lock instead of filling in a form that 423s on save
* fix(tables): guard the remaining import writes and explain blocked Shift+Enter
- Route the replace-mode wipe, the inferred-schema write and createColumns
through the same guardBatch transaction as the batch inserts, so a delete or
schema lock committed while the file is downloading or being sampled is seen
instead of the job-start snapshot
- Shift+Enter takes the manual-add path, so it now opens the lock modal like
the Add row button instead of returning silently
* improvement(tables): surface blocked lock actions as a toast, not a modal
- Replace TableLockedModal with a warning toast carrying a "Lock settings"
action button for admins. Being told you can't edit shouldn't cost a dismiss
click, and the button still routes admins straight to the panel
- Dedupe by id so repeated attempts on a locked cell replace one notice
instead of stacking a column of them
- Move the copy into lock-copy.ts alongside the rest of the lock vocabulary
and tighten it for toast length
* fix(tables): don't show the update-lock notice to users without write access
Double-click checked the update lock before any permission check, so a
read-only member on a locked table got "Editing rows is locked" — misleading
(the lock isn't why they can't edit) and it swallowed the expanded-cell
viewer, which is a legitimate read-only affordance.
* improvement(tables): trim the update-lock toast copy
* fix(tables): map append-import locks to 423 and stop conflating locks with permissions
- The sync append branch returns instead of rethrowing, so the outer catch's
mapper never saw a TableLockedError and every lock violation became a 500.
Map it in that catch, with a regression test (replace mode already rethrows)
- Stop mounting the workflow-group column menu for users without edit access:
passing the blocked handlers unconditionally made it appear for read-only
members and report a lock even on an unlocked table
- Enter/F2 now raises the same lock notice as double-click and Space instead
of silently doing nothing
- guardBatch returns the freshly-read definition so addTableColumnsWithTx
asserts live state; a schema lock cleared mid-import no longer fails the
createColumns step. The snapshot pre-asserts now only run as the fallback
for callers that pass no revalidator
- Append-only no longer labels a table whose schema is also locked, which
claimed columns were mutable when they weren't
- Import dialog withholds Replace on a delete-locked table and create-column
on a schema-locked one, instead of offering a configuration that only 423s
* fix(tables): revalidate sync imports under the advisory lock, explain every blocked key
- The sync append/replace paths asserted only the request-start snapshot, so a
lock committed while the CSV was parsed still let the write through. Both now
re-read under the schema advisory lock at the top of their own transaction,
taken before acquireRowOrderLock so the order stays advisory -> rows_pos ->
definitions
- Delete/Backspace, Cmd+D, typeahead and cut raised no notice on an
update-locked table; they now explain the lock, and stay silent for users
without write access
- The multipart import fetch threw a plain Error, dropping the 423 status, so
the lock self-heal never ran and the stale detail cache survived. It throws
ApiClientError now and both import-into-table hooks call
handleTableLockRejection
- Force append at submit when the delete lock landed while the dialog was open
with Replace already selected
* fix(tables): restore the workspace ownership check on copilot row deletes
Switching deleteRow/deleteRowsByIds to take a TableDefinition dropped the
workspaceId argument that previously scoped the query, and the two branches
I added loaded the table without the ownership comparison every other
operation in the tool performs — letting a caller delete rows from a table
in another workspace. Both now reject a foreign table as not found.1 parent 8329dac commit 0dcbc56
65 files changed
Lines changed: 19937 additions & 165 deletions
File tree
- apps/sim
- app
- api
- table
- [tableId]
- columns
- delete-async
- groups
- import-async
- import
- rows
- [rowId]
- v1/tables
- [tableId]
- columns
- rows
- [rowId]
- upsert
- workspace/[workspaceId]/tables
- [tableId]
- components
- lock-settings-modal
- new-column-dropdown
- table-grid
- hooks
- components/import-csv-dialog
- ee/workspace-forking/lib/copy
- hooks
- queries
- lib
- api/contracts
- copilot
- request/tools
- tools/server/table
- core/config
- table
- __tests__
- columns
- rows
- workflow-groups
- packages
- db
- migrations
- meta
- testing/src/mocks
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
22 | | - | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
23 | 29 | | |
24 | 30 | | |
25 | 31 | | |
| |||
61 | 67 | | |
62 | 68 | | |
63 | 69 | | |
| 70 | + | |
| 71 | + | |
64 | 72 | | |
65 | 73 | | |
66 | 74 | | |
| |||
190 | 198 | | |
191 | 199 | | |
192 | 200 | | |
| 201 | + | |
| 202 | + | |
193 | 203 | | |
194 | 204 | | |
195 | 205 | | |
| |||
255 | 265 | | |
256 | 266 | | |
257 | 267 | | |
| 268 | + | |
| 269 | + | |
258 | 270 | | |
259 | 271 | | |
260 | 272 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| 13 | + | |
13 | 14 | | |
14 | 15 | | |
15 | 16 | | |
| |||
55 | 56 | | |
56 | 57 | | |
57 | 58 | | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
58 | 64 | | |
59 | 65 | | |
60 | 66 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
18 | | - | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
19 | 24 | | |
20 | 25 | | |
21 | 26 | | |
| |||
47 | 52 | | |
48 | 53 | | |
49 | 54 | | |
| 55 | + | |
| 56 | + | |
50 | 57 | | |
51 | 58 | | |
52 | 59 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| 13 | + | |
13 | 14 | | |
14 | 15 | | |
15 | 16 | | |
| |||
53 | 54 | | |
54 | 55 | | |
55 | 56 | | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
56 | 63 | | |
57 | 64 | | |
58 | 65 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
31 | 31 | | |
32 | 32 | | |
33 | 33 | | |
| 34 | + | |
34 | 35 | | |
35 | 36 | | |
36 | 37 | | |
37 | 38 | | |
38 | 39 | | |
39 | 40 | | |
40 | 41 | | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
41 | 46 | | |
42 | 47 | | |
43 | 48 | | |
| |||
74 | 79 | | |
75 | 80 | | |
76 | 81 | | |
| 82 | + | |
77 | 83 | | |
78 | 84 | | |
79 | 85 | | |
| |||
377 | 383 | | |
378 | 384 | | |
379 | 385 | | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
380 | 401 | | |
381 | 402 | | |
382 | 403 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
44 | 44 | | |
45 | 45 | | |
46 | 46 | | |
| 47 | + | |
47 | 48 | | |
48 | 49 | | |
49 | 50 | | |
| |||
336 | 337 | | |
337 | 338 | | |
338 | 339 | | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
339 | 346 | | |
340 | 347 | | |
341 | 348 | | |
| |||
408 | 415 | | |
409 | 416 | | |
410 | 417 | | |
| 418 | + | |
| 419 | + | |
411 | 420 | | |
412 | 421 | | |
413 | 422 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
6 | 6 | | |
| 7 | + | |
7 | 8 | | |
8 | 9 | | |
9 | 10 | | |
10 | | - | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
11 | 19 | | |
12 | | - | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
13 | 27 | | |
14 | 28 | | |
15 | 29 | | |
| |||
65 | 79 | | |
66 | 80 | | |
67 | 81 | | |
| 82 | + | |
68 | 83 | | |
69 | 84 | | |
70 | 85 | | |
| |||
104 | 119 | | |
105 | 120 | | |
106 | 121 | | |
107 | | - | |
| 122 | + | |
108 | 123 | | |
109 | 124 | | |
110 | 125 | | |
| |||
116 | 131 | | |
117 | 132 | | |
118 | 133 | | |
| 134 | + | |
| 135 | + | |
119 | 136 | | |
120 | 137 | | |
121 | 138 | | |
| |||
125 | 142 | | |
126 | 143 | | |
127 | 144 | | |
128 | | - | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
129 | 179 | | |
130 | 180 | | |
131 | 181 | | |
132 | 182 | | |
133 | 183 | | |
134 | 184 | | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
135 | 188 | | |
136 | 189 | | |
137 | 190 | | |
138 | 191 | | |
139 | | - | |
| 192 | + | |
140 | 193 | | |
141 | | - | |
| 194 | + | |
142 | 195 | | |
143 | 196 | | |
144 | 197 | | |
| |||
188 | 241 | | |
189 | 242 | | |
190 | 243 | | |
| 244 | + | |
| 245 | + | |
191 | 246 | | |
192 | 247 | | |
193 | 248 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
| 24 | + | |
24 | 25 | | |
25 | 26 | | |
26 | 27 | | |
| |||
211 | 212 | | |
212 | 213 | | |
213 | 214 | | |
214 | | - | |
| 215 | + | |
215 | 216 | | |
216 | 217 | | |
217 | 218 | | |
| |||
221 | 222 | | |
222 | 223 | | |
223 | 224 | | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
224 | 228 | | |
225 | 229 | | |
226 | 230 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
433 | 433 | | |
434 | 434 | | |
435 | 435 | | |
| 436 | + | |
436 | 437 | | |
437 | 438 | | |
438 | 439 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
127 | 127 | | |
128 | 128 | | |
129 | 129 | | |
| 130 | + | |
130 | 131 | | |
131 | 132 | | |
132 | 133 | | |
| |||
206 | 207 | | |
207 | 208 | | |
208 | 209 | | |
| 210 | + | |
209 | 211 | | |
210 | 212 | | |
211 | 213 | | |
| |||
0 commit comments