Commit 24a6086
authored
feat(tables): fractional order keys for O(log n) row insert/delete (flag-gated, default off) (#4890)
* feat(tables): add order_key column, fractional-indexing util, and ordering flag (off)
* feat(tables): write order_key on insert, flag-gate delete reindex + query ordering, add backfill
Flag off (default) = identical behavior. Single-insert assigns a fractional
order_key; queryRows orders by order_key when the flag is on; deletes skip the
O(N) reindex when on. Per-table-atomic backfill script populates existing rows.
* feat(tables): write order_key on all insert paths (batch, upsert, replace, import, create, copilot)
Completes the always-write-keys prerequisite: every row insert now assigns a
fractional order_key consistent with position order, so the flag can be flipped
safely after backfill. Flag off (default) still = identical behavior.
* feat(tables): insert-by-neighbor-id + orderKey on wire + client order-by-key
Inserts express intent as afterRowId/beforeRowId (O(1) key mint via the
(table_id,order_key,id) index); orderKey is returned on every row; client
reconcile/undo place by orderKey (no neighbor bump) with position fallback.
Flag off = unchanged. 205 table tests pass.
* feat(tables): resolve position-based inserts by key ordinal under the flag
Position-based callers (mothership tool, v1 API, undo fallback, transient old
clients) resolve their insert neighbor by order_key ordinal (OFFSET) when the
flag is on — positions are gappy then, so WHERE position=N would miss. Flag off
keeps the indexed position lookup. The mothership tool itself is unchanged.
* test(tables): flag-on coverage — delete skips reindex, insert mints key + no shift
* fix lint
* chore(db): regenerate order_key migration with default drizzle name
* fix(tables): address review — guard neighbor insert + mutual-exclusion + safe reconcile
- resolveInsertByNeighbor throws when the anchor row is missing (was silently
inserting at the front) and when its order_key is null under the flag.
- insert contract: afterRowId/beforeRowId are mutually exclusive (refine).
- reconcileCreatedRow only key-sorts when every cached row is keyed, so mid-
backfill un-keyed rows aren't yanked to the front.
* fix(kb): restore non-null guard in storage-key filter (unsafe-lint regression)
* refactor(tables): extract maxOrderKey + thread import append key
- Extract maxOrderKey(executor, tableId) helper; replaces three identical
max(order_key) selects (single/batch insert append + import).
- Import: read the append anchor once up front and thread each batch's last
key forward (nextImportStartOrderKey + afterOrderKey) instead of re-scanning
max(order_key) per batch over a growing table — one scan per import, not
one per 1k-row batch.
* fix(tables): keep insert body base omittable for v1 contract
The afterRowId/beforeRowId mutual-exclusion .refine() turned the schema into a
ZodEffects, which Zod forbids .omit() on — v1's insertTableRowBodySchema.omit({
position }) threw at module load (runtime-only; tsc misses it). Split the plain
object base out, apply the shared refine on top, and have v1 omit from the base
then re-apply it.
* fix(tables): chunk backfill order-key writes
A single UPDATE … FROM (VALUES …) over a whole large table overflows the JS call
stack while drizzle assembles the VALUES list (and would blow past Postgres's
65535 bound-param limit at ~32k rows) — large tables failed with 'Maximum call
stack size exceeded'. Write in 1000-row chunks inside the same per-table
transaction so keying stays atomic.
* fix(tables): emit orderKey in insert responses
The single-row and batch insert handlers dropped orderKey from the JSON
response even though the service returns it, so reconcileCreatedRow always fell
back to position-sorting and could place neighbor inserts wrong under the
fractional-ordering flag. Serialize orderKey alongside position.
* fix(tables): restore by orderKey, not position, under fractional flag
A saved position is the gappy column value, but under the flag insert reads
position as a visual rank (OFFSET) — so position-based restore misplaces rows.
- create-row redo now goes through the batch path carrying the saved orderKey
(the single-insert API has no orderKey field); drop the now-unused single
create mutation.
- resolveBatchInsertOrderKeys appends under the flag instead of feeding gappy
positions to resolveInsertOrderKey; positions remain the flag-off path.
* perf(tables): backfill writes 5000 rows/chunk (was 1000)
5x fewer round-trips per table; ~10k bound params stays well under Postgres's
65535 ceiling and far below the single-statement size that overflows the stack.
* fix(tables): drop rowNumber from table trigger payload
position is gappy under the fractional-ordering flag, so rowNumber (= row.position)
no longer reflects a contiguous visual rank. Rather than compute-on-read, remove
it from the trigger payload, output schema, and column-execution input.
Also pin isTablesFractionalOrderingEnabled=false in update-row.test.ts so its
flag-off position-shift assertions are deterministic regardless of local env.
* chore(db): format generated 0226 migration metadata
biome check . flagged the drizzle-generated _journal.json and 0226_snapshot.json;
apply the formatter so packages/db lint:check passes in CI.
* docs(triggers): drop rowNumber from table trigger outputs
rowNumber was removed from the table trigger payload; remove it from the
documented output fields to match.
* test(tables): remove flag-on fractional-ordering unit suite
Flag-on behavior is covered by manual large-table verification; the heavily-
mocked DB-chain suite added little signal.1 parent f7f7840 commit 24a6086
27 files changed
Lines changed: 17897 additions & 64 deletions
File tree
- apps
- docs/content/docs/en/triggers
- sim
- app
- api/table/[tableId]/rows
- workspace/[workspaceId]/tables/[tableId]/components/table-grid
- background
- hooks
- queries
- lib
- api/contracts
- v1/tables
- copilot/request/tools
- core/config
- table
- __tests__
- scripts
- stores/table
- triggers/table
- packages/db
- migrations
- meta
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
41 | | - | |
42 | 41 | | |
43 | 42 | | |
44 | 43 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
71 | 71 | | |
72 | 72 | | |
73 | 73 | | |
| 74 | + | |
74 | 75 | | |
75 | 76 | | |
76 | 77 | | |
| |||
83 | 84 | | |
84 | 85 | | |
85 | 86 | | |
| 87 | + | |
86 | 88 | | |
87 | 89 | | |
88 | 90 | | |
| |||
162 | 164 | | |
163 | 165 | | |
164 | 166 | | |
| 167 | + | |
| 168 | + | |
165 | 169 | | |
166 | 170 | | |
167 | 171 | | |
| |||
174 | 178 | | |
175 | 179 | | |
176 | 180 | | |
| 181 | + | |
177 | 182 | | |
178 | 183 | | |
179 | 184 | | |
| 185 | + | |
180 | 186 | | |
181 | 187 | | |
182 | 188 | | |
| |||
Lines changed: 5 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
837 | 837 | | |
838 | 838 | | |
839 | 839 | | |
| 840 | + | |
| 841 | + | |
| 842 | + | |
840 | 843 | | |
841 | 844 | | |
842 | | - | |
| 845 | + | |
843 | 846 | | |
844 | 847 | | |
845 | 848 | | |
| |||
904 | 907 | | |
905 | 908 | | |
906 | 909 | | |
907 | | - | |
| 910 | + | |
908 | 911 | | |
909 | 912 | | |
910 | 913 | | |
| |||
Lines changed: 6 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
302 | 302 | | |
303 | 303 | | |
304 | 304 | | |
305 | | - | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
306 | 311 | | |
307 | 312 | | |
308 | 313 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
589 | 589 | | |
590 | 590 | | |
591 | 591 | | |
592 | | - | |
593 | 592 | | |
594 | 593 | | |
595 | 594 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
512 | 512 | | |
513 | 513 | | |
514 | 514 | | |
515 | | - | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
516 | 522 | | |
517 | 523 | | |
518 | 524 | | |
| |||
592 | 598 | | |
593 | 599 | | |
594 | 600 | | |
595 | | - | |
596 | | - | |
597 | | - | |
598 | | - | |
599 | | - | |
600 | | - | |
601 | | - | |
602 | | - | |
603 | | - | |
604 | | - | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
| 609 | + | |
| 610 | + | |
| 611 | + | |
| 612 | + | |
| 613 | + | |
| 614 | + | |
| 615 | + | |
| 616 | + | |
| 617 | + | |
| 618 | + | |
| 619 | + | |
| 620 | + | |
| 621 | + | |
| 622 | + | |
| 623 | + | |
| 624 | + | |
| 625 | + | |
| 626 | + | |
| 627 | + | |
| 628 | + | |
605 | 629 | | |
606 | 630 | | |
607 | 631 | | |
608 | 632 | | |
609 | | - | |
610 | | - | |
611 | | - | |
| 633 | + | |
612 | 634 | | |
613 | | - | |
614 | | - | |
| 635 | + | |
615 | 636 | | |
616 | 637 | | |
617 | 638 | | |
618 | 639 | | |
619 | 640 | | |
620 | | - | |
621 | | - | |
622 | | - | |
623 | | - | |
| 641 | + | |
624 | 642 | | |
625 | 643 | | |
626 | 644 | | |
| |||
655 | 673 | | |
656 | 674 | | |
657 | 675 | | |
| 676 | + | |
658 | 677 | | |
659 | 678 | | |
660 | 679 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
8 | | - | |
9 | 8 | | |
10 | 9 | | |
11 | 10 | | |
| |||
56 | 55 | | |
57 | 56 | | |
58 | 57 | | |
59 | | - | |
60 | 58 | | |
61 | 59 | | |
62 | 60 | | |
| |||
137 | 135 | | |
138 | 136 | | |
139 | 137 | | |
140 | | - | |
141 | | - | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
142 | 147 | | |
143 | 148 | | |
144 | | - | |
| 149 | + | |
145 | 150 | | |
146 | 151 | | |
147 | 152 | | |
| |||
165 | 170 | | |
166 | 171 | | |
167 | 172 | | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
168 | 176 | | |
169 | 177 | | |
170 | 178 | | |
| |||
187 | 195 | | |
188 | 196 | | |
189 | 197 | | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
190 | 201 | | |
191 | 202 | | |
192 | 203 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
147 | 147 | | |
148 | 148 | | |
149 | 149 | | |
150 | | - | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
151 | 156 | | |
152 | 157 | | |
153 | 158 | | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
154 | 163 | | |
155 | 164 | | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
156 | 173 | | |
157 | 174 | | |
158 | 175 | | |
| |||
175 | 192 | | |
176 | 193 | | |
177 | 194 | | |
| 195 | + | |
| 196 | + | |
178 | 197 | | |
179 | 198 | | |
180 | 199 | | |
181 | 200 | | |
182 | 201 | | |
183 | 202 | | |
184 | 203 | | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
185 | 207 | | |
186 | 208 | | |
187 | 209 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
| 7 | + | |
| 8 | + | |
8 | 9 | | |
9 | 10 | | |
10 | 11 | | |
| |||
60 | 61 | | |
61 | 62 | | |
62 | 63 | | |
63 | | - | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
64 | 67 | | |
65 | 68 | | |
66 | 69 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
| 16 | + | |
16 | 17 | | |
17 | 18 | | |
18 | 19 | | |
| |||
103 | 104 | | |
104 | 105 | | |
105 | 106 | | |
| 107 | + | |
| 108 | + | |
106 | 109 | | |
107 | 110 | | |
108 | 111 | | |
| |||
113 | 116 | | |
114 | 117 | | |
115 | 118 | | |
| 119 | + | |
116 | 120 | | |
117 | 121 | | |
118 | 122 | | |
| |||
246 | 250 | | |
247 | 251 | | |
248 | 252 | | |
| 253 | + | |
| 254 | + | |
249 | 255 | | |
250 | 256 | | |
251 | 257 | | |
| |||
256 | 262 | | |
257 | 263 | | |
258 | 264 | | |
| 265 | + | |
259 | 266 | | |
260 | 267 | | |
261 | 268 | | |
| |||
0 commit comments