Skip to content

Compose the named-stage master per object; harden cross-domain catalog pairing#1660

Merged
timsehn merged 1 commit into
masterfrom
fix/staging-pairing-followups
Jul 16, 2026
Merged

Compose the named-stage master per object; harden cross-domain catalog pairing#1660
timsehn merged 1 commit into
masterfrom
fix/staging-pairing-followups

Conversation

@timsehn

@timsehn timsehn commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Follow-ups to #1654/#1656 on named staging with schema objects.

Bugs fixed

  1. Unstaged schema changes leaked into named-add commits. A named dolt_add adopted the entire working master root as the staged master, so an unstaged ALTER TABLE p ADD COLUMN on an untouched table rode into the commit, and an unstaged DROP INDEX destroyed a staged index. Dolt keeps both out. The staged master is now composed per object: rows for the named objects (adds, staged drops, vtab shadows, retired rename names) come from working; every other table/index row keeps the previously staged state; views and triggers always keep staged state.

  2. Cross-domain numbering collisions in the composed master. Composed rows span two catalog numbering domains. Old-sourced table rows are renumbered to the working number their aligned entries carry (mirroring addAlignStagedEntriesToWorking), otherwise a staged FK child scenario collided numbers, appendMissingSchemaCatalogRows appended a duplicate row with an empty tbl_name, and the committed catalog was malformed (create_fk_child_staged caught this).

  3. Serializer pairing hardening for constructed arrays: an unnamed entry (always an index) never pairs with a table row on a bare number match, and a named working table only pairs with an unnamed staged entry when the staged catalog's own schema rows identify that entry as the same table.

Also consolidates the index schema-row comparison shared by status and diff summary into one helper.

Tests

  • test/doltlite_commit.sh +2 gating tests (unstaged_alter_stays_out, staged_index_survives_unstaged_drop) — both fail before this change and pass after.
  • Full local regression green: shell battery 88/88 suites, C corpus 309,830/309,830, staging 31/31, commit 62/62, index×VC matrix 38/38, oracles: status-schema 18/18, status 40/40, add 34/34, diff 63/63, commit 37/37, reset 43/43.

🤖 Generated with Claude Code

… wholesale

A named add previously adopted the whole working master root into the
staged catalog, dragging unstaged schema changes into the commit: an
unstaged ALTER on another table rode along, and an unstaged DROP INDEX
destroyed a staged index. The staged master is now composed row by row:
table and index rows of the named objects (adds, staged drops, vtab
shadows, retired rename names) come from working, everything else keeps
the previously staged rows, and views and triggers always keep staged
state.

Composed rows cross catalog numbering domains, so old-sourced table rows
are renumbered to the working table number their aligned entries carry,
and the serializer rejects two cross-domain pairings in constructed
arrays: an unnamed entry never pairs with a table row, and a named add
only pairs with an unnamed staged entry when the staged schema rows
identify that entry as the same table.

The index schema row comparison shared by status and diff summary moves
to a single helper.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Version-Control Performance Ceilings

Runs: median of 3 executions per benchmark, excluding fixture setup. The
working-set fixtures use 800 committed tables with 125 rows each
(100000 rows total); data-dirty cases update 1 row in 40
tables, and schema-dirty cases update 1 row plus add a column in 20 tables.
Branch tests use 300 branches, checkout uses a clean 400-table
branch switch over 100000 rows, and merge
tests use 100000-row tables with 2000 changed or conflicting
rows per side.

Benchmark Median ms Ceiling ms Result
status_clean_many_tables 63 200 PASS
status_dirty_many_tables 65 200 PASS
diff_regular_working_one_table 59 150 PASS
diff_regular_working_many_tables 69 200 PASS
diff_stat_working_many_tables 70 200 PASS
diff_schema_working_many_tables 71 200 PASS
branch_list_many_branches 21 100 PASS
branch_create_delete 22 100 PASS
checkout_branch_clean 47 200 PASS
merge_data_no_conflicts 25 150 PASS
merge_schema_no_conflicts 21 100 PASS
merge_data_conflicts 76 250 PASS
merge_data_conflicts_with_resolve 76 250 PASS

@github-actions

Copy link
Copy Markdown

Sysbench-Style Benchmark: Doltlite vs SQLite

In-Memory

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 23,994 29,514 1.23
oltp_range_select 9,994 12,879 1.29
oltp_sum_range 9,097 11,758 1.29
oltp_order_range 2,521 2,844 1.13
oltp_distinct_range 3,412 3,843 1.13
oltp_index_scan 3,864 5,226 1.35
select_random_points 10,138 10,962 1.08
select_random_ranges 2,872 4,037 1.41
covering_index_scan 4,139 4,174 1.01
groupby_scan 28,987 32,694 1.13
index_join 5,925 7,803 1.32
index_join_scan 3,302 4,592 1.39
types_table_scan 1,043,509 1,289,826 1.24
table_scan 1,169,528 1,377,678 1.18
oltp_read_only 98,714 118,454 1.20
Average 1.22

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 181,014 252,728 1.40
oltp_insert 15,319 28,618 1.87
oltp_update_index 48,421 87,693 1.81
oltp_update_non_index 32,955 58,229 1.77
oltp_delete_insert 42,916 70,746 1.65
oltp_write_only 20,995 44,203 2.11
types_delete_insert 23,927 39,980 1.67
oltp_read_write 65,558 107,595 1.64
Average 1.74

File-Backed

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 90,711 53,268 0.59
oltp_range_select 16,522 15,686 0.95
oltp_sum_range 16,041 14,463 0.90
oltp_order_range 3,215 3,236 1.01
oltp_distinct_range 4,252 4,195 0.99
oltp_index_scan 10,773 7,962 0.74
select_random_points 16,183 13,797 0.85
select_random_ranges 9,604 6,511 0.68
covering_index_scan 11,136 6,786 0.61
groupby_scan 29,689 32,662 1.10
index_join 9,732 9,759 1.00
index_join_scan 4,252 4,984 1.17
types_table_scan 1,042,512 1,274,990 1.22
table_scan 1,173,190 1,374,307 1.17
oltp_read_only 196,986 155,006 0.79
Average 0.92

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 188,774 269,914 1.43
oltp_insert 21,867 35,669 1.63
oltp_update_index 73,082 113,248 1.55
oltp_update_non_index 54,812 78,030 1.42
oltp_delete_insert 63,806 92,317 1.45
oltp_write_only 41,839 63,778 1.52
types_delete_insert 38,343 52,740 1.38
oltp_read_write 86,717 127,730 1.47
Average 1.48

File-Backed (autocommit)

Each statement runs as its own transaction — exposes per-commit
fixed costs that the wrapped-in-BEGIN/COMMIT tests amortize away.
SQLite uses WAL mode with synchronous=FULL in this section so
the comparison uses SQLite's durable WAL autocommit path.

Reads

Reads have no commit cost; these are the same SQL files as the
File-Backed Reads section, included here for symmetry and to
catch any per-statement overhead doltlite pays on the read path.

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 45,921 53,862 1.17
oltp_range_select 12,156 15,678 1.29
oltp_sum_range 11,608 14,516 1.25
oltp_order_range 2,789 3,193 1.14
oltp_distinct_range 3,861 4,209 1.09
oltp_index_scan 6,220 7,926 1.27
select_random_points 12,103 13,889 1.15
select_random_ranges 5,150 6,523 1.27
covering_index_scan 6,662 6,822 1.02
groupby_scan 29,370 32,567 1.11
index_join 7,377 9,730 1.32
index_join_scan 3,749 5,028 1.34
types_table_scan 1,041,920 1,275,288 1.22
table_scan 1,170,408 1,371,037 1.17
oltp_read_only 131,793 155,095 1.18
Average 1.20

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert_ac 20,506 75,682 3.69
oltp_insert_ac 22,831 91,738 4.02
oltp_update_index_ac 26,698 105,840 3.96
oltp_update_non_index_ac 22,551 85,308 3.78
oltp_delete_insert_ac 23,099 99,410 4.30
oltp_write_only_ac 24,546 96,285 3.92
types_delete_insert_ac 21,315 83,889 3.94
oltp_read_write_ac 27,133 99,931 3.68
Average 3.91

100000 rows, median of 5 invocations per test; autocommit writes use 9, workload-only timing via host monotonic clock when available.

Performance Ceiling Check (2.5x individual, 2x average; autocommit writes: 6x / 5x)

All tests within ceilings.

@github-actions

Copy link
Copy Markdown

Sysbench-Style Benchmark (composite PK): Doltlite vs SQLite

Companion to the classic Sysbench-Style Benchmark. Every workload here
runs against tables with a 2-column INTEGER PRIMARY KEY(a, b) WITHOUT ROWID.

Individual ratios gated at 2.5×; section averages gated at 2×. Autocommit writes use 6× / 5×.

In-Memory

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 34,118 40,258 1.18
oltp_range_select 19,493 21,988 1.13
oltp_sum_range 17,771 21,148 1.19
oltp_order_range 3,469 3,919 1.13
oltp_distinct_range 4,514 5,006 1.11
oltp_index_scan 4,525 6,054 1.34
select_random_points 27,532 32,456 1.18
select_random_ranges 7,739 8,998 1.16
covering_index_scan 4,190 4,187 1.00
groupby_scan 35,643 38,867 1.09
index_join 8,245 10,296 1.25
index_join_scan 4,157 5,487 1.32
types_table_scan 1,076,621 1,274,869 1.18
table_scan 1,226,494 1,388,066 1.13
oltp_read_only 151,181 170,029 1.12
Average 1.17

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 246,785 362,045 1.47
oltp_insert 19,233 37,201 1.93
oltp_update_index 70,564 115,921 1.64
oltp_update_non_index 52,779 84,885 1.61
oltp_delete_insert 50,896 99,086 1.95
oltp_write_only 28,226 59,497 2.11
types_delete_insert 33,101 56,252 1.70
oltp_read_write 104,190 158,325 1.52
Average 1.74

File-Backed

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 102,620 64,686 0.63
oltp_range_select 26,780 25,072 0.94
oltp_sum_range 25,966 24,851 0.96
oltp_order_range 4,882 4,649 0.95
oltp_distinct_range 6,049 5,739 0.95
oltp_index_scan 11,729 8,973 0.77
select_random_points 37,874 36,448 0.96
select_random_ranges 15,054 11,995 0.80
covering_index_scan 11,653 7,246 0.62
groupby_scan 37,644 39,853 1.06
index_join 12,085 12,721 1.05
index_join_scan 5,185 6,051 1.17
types_table_scan 1,061,616 1,271,758 1.20
table_scan 1,276,465 1,375,973 1.08
oltp_read_only 257,534 209,289 0.81
Average 0.93

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 261,815 384,559 1.47
oltp_insert 25,880 46,969 1.81
oltp_update_index 98,920 147,978 1.50
oltp_update_non_index 77,086 105,868 1.37
oltp_delete_insert 77,767 119,998 1.54
oltp_write_only 51,526 78,223 1.52
types_delete_insert 50,282 69,687 1.39
oltp_read_write 128,079 177,393 1.39
Average 1.50

File-Backed (autocommit)

Each statement runs as its own transaction — exposes per-commit
fixed costs that the wrapped-in-BEGIN/COMMIT tests amortize away.
SQLite uses WAL mode with synchronous=FULL in this section so
the comparison uses SQLite's durable WAL autocommit path.

Reads

Reads have no commit cost; these are the same SQL files as the
File-Backed Reads section, included here for symmetry and to
catch any per-statement overhead doltlite pays on the read path.

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 56,709 64,970 1.15
oltp_range_select 22,820 25,044 1.10
oltp_sum_range 21,734 24,360 1.12
oltp_order_range 4,516 4,770 1.06
oltp_distinct_range 5,846 5,784 0.99
oltp_index_scan 7,461 8,998 1.21
select_random_points 31,426 37,161 1.18
select_random_ranges 10,551 11,939 1.13
covering_index_scan 7,299 7,220 0.99
groupby_scan 36,440 39,856 1.09
index_join 9,970 12,930 1.30
index_join_scan 4,741 6,066 1.28
types_table_scan 1,067,534 1,270,996 1.19
table_scan 1,247,794 1,377,375 1.10
oltp_read_only 185,491 211,395 1.14
Average 1.13

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert_ac 24,508 79,493 3.24
oltp_insert_ac 23,622 102,086 4.32
oltp_update_index_ac 26,775 112,239 4.19
oltp_update_non_index_ac 22,607 90,665 4.01
oltp_delete_insert_ac 25,981 101,946 3.92
oltp_write_only_ac 26,560 101,593 3.83
types_delete_insert_ac 23,914 90,517 3.79
oltp_read_write_ac 32,085 110,826 3.45
Average 3.84

100000 rows, median of 5 invocations per test; autocommit writes use 9, workload-only timing via host monotonic clock when available.

Performance Ceiling Check (2.5x individual, 2x average; autocommit writes: 6x / 5x)

All tests within ceilings.

@github-actions

Copy link
Copy Markdown

Sysbench-Style Benchmark (BLOB PK): Doltlite vs SQLite

Companion to the classic Sysbench-Style Benchmark. Every workload here
runs against tables with a 16-byte big-endian BLOB PRIMARY KEY.

Individual ratios gated at 2.5×; section averages gated at 2×. Autocommit writes use 6× / 5×.

In-Memory

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 31,165 37,353 1.20
oltp_range_select 12,600 15,028 1.19
oltp_sum_range 11,930 14,063 1.18
oltp_order_range 2,859 3,156 1.10
oltp_distinct_range 3,900 4,226 1.08
oltp_index_scan 4,501 6,288 1.40
select_random_points 17,514 20,552 1.17
select_random_ranges 4,124 5,320 1.29
covering_index_scan 4,345 4,591 1.06
groupby_scan 31,392 33,704 1.07
index_join 6,671 9,131 1.37
index_join_scan 4,152 5,222 1.26
types_table_scan 1,041,836 1,259,623 1.21
table_scan 1,219,265 1,349,192 1.11
oltp_read_only 117,763 137,536 1.17
Average 1.19

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 241,651 354,241 1.47
oltp_insert 19,825 38,865 1.96
oltp_update_index 68,140 128,182 1.88
oltp_update_non_index 48,854 83,981 1.72
oltp_delete_insert 48,699 100,894 2.07
oltp_write_only 27,958 61,590 2.20
types_delete_insert 32,431 53,719 1.66
oltp_read_write 84,111 138,473 1.65
Average 1.83

File-Backed

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 99,712 60,794 0.61
oltp_range_select 20,000 17,496 0.87
oltp_sum_range 18,876 16,914 0.90
oltp_order_range 3,747 3,563 0.95
oltp_distinct_range 4,836 4,671 0.97
oltp_index_scan 11,564 8,871 0.77
select_random_points 25,733 23,795 0.92
select_random_ranges 11,028 7,692 0.70
covering_index_scan 11,598 7,126 0.61
groupby_scan 32,306 34,347 1.06
index_join 10,942 11,087 1.01
index_join_scan 4,970 5,713 1.15
types_table_scan 1,043,136 1,260,600 1.21
table_scan 1,214,783 1,343,951 1.11
oltp_read_only 225,253 175,034 0.78
Average 0.91

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 255,712 377,592 1.48
oltp_insert 30,785 52,316 1.70
oltp_update_index 103,393 163,873 1.58
oltp_update_non_index 76,838 106,209 1.38
oltp_delete_insert 80,805 129,139 1.60
oltp_write_only 54,467 83,950 1.54
types_delete_insert 51,053 72,059 1.41
oltp_read_write 110,993 159,607 1.44
Average 1.52

File-Backed (autocommit)

Each statement runs as its own transaction — exposes per-commit
fixed costs that the wrapped-in-BEGIN/COMMIT tests amortize away.
SQLite uses WAL mode with synchronous=FULL in this section so
the comparison uses SQLite's durable WAL autocommit path.

Reads

Reads have no commit cost; these are the same SQL files as the
File-Backed Reads section, included here for symmetry and to
catch any per-statement overhead doltlite pays on the read path.

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 54,290 60,596 1.12
oltp_range_select 15,406 17,433 1.13
oltp_sum_range 14,903 16,846 1.13
oltp_order_range 3,247 3,471 1.07
oltp_distinct_range 4,373 4,549 1.04
oltp_index_scan 7,179 8,903 1.24
select_random_points 20,627 23,922 1.16
select_random_ranges 6,675 7,746 1.16
covering_index_scan 7,442 7,243 0.97
groupby_scan 31,595 34,208 1.08
index_join 8,625 11,095 1.29
index_join_scan 4,696 5,657 1.20
types_table_scan 1,035,569 1,249,922 1.21
table_scan 1,204,327 1,356,214 1.13
oltp_read_only 153,156 174,699 1.14
Average 1.14

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert_ac 22,188 81,584 3.68
oltp_insert_ac 24,804 100,893 4.07
oltp_update_index_ac 27,096 110,733 4.09
oltp_update_non_index_ac 22,654 90,294 3.99
oltp_delete_insert_ac 25,900 103,033 3.98
oltp_write_only_ac 24,859 102,846 4.14
types_delete_insert_ac 21,994 90,352 4.11
oltp_read_write_ac 32,214 107,972 3.35
Average 3.92

100000 rows, median of 5 invocations per test; autocommit writes use 9, workload-only timing via host monotonic clock when available.

Performance Ceiling Check (2.5x individual, 2x average; autocommit writes: 6x / 5x)

All tests within ceilings.

@github-actions

Copy link
Copy Markdown

Sysbench-Style Benchmark (TEXT PK): Doltlite vs SQLite

Companion to the classic Sysbench-Style Benchmark. Every workload here
runs against tables with a 32-char hex TEXT PRIMARY KEY (UUID-shaped).

Individual ratios gated at 2.5×; section averages gated at 2×. Autocommit writes use 6× / 5×.

In-Memory

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 29,090 37,730 1.30
oltp_range_select 13,293 15,064 1.13
oltp_sum_range 12,168 14,474 1.19
oltp_order_range 2,981 3,201 1.07
oltp_distinct_range 4,017 4,251 1.06
oltp_index_scan 4,568 6,374 1.40
select_random_points 17,944 21,397 1.19
select_random_ranges 3,997 5,254 1.31
covering_index_scan 4,501 4,713 1.05
groupby_scan 31,355 34,218 1.09
index_join 6,761 9,364 1.39
index_join_scan 4,612 5,353 1.16
types_table_scan 1,106,777 1,290,994 1.17
table_scan 1,488,199 1,380,278 0.93
oltp_read_only 122,246 138,574 1.13
Average 1.17

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 226,077 355,645 1.57
oltp_insert 21,611 39,019 1.81
oltp_update_index 70,841 133,165 1.88
oltp_update_non_index 50,561 86,649 1.71
oltp_delete_insert 51,681 105,074 2.03
oltp_write_only 31,186 62,934 2.02
types_delete_insert 33,290 56,436 1.70
oltp_read_write 90,777 143,469 1.58
Average 1.79

File-Backed

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 99,435 62,524 0.63
oltp_range_select 21,752 17,981 0.83
oltp_sum_range 20,140 17,317 0.86
oltp_order_range 3,874 3,654 0.94
oltp_distinct_range 5,019 4,690 0.93
oltp_index_scan 11,912 9,171 0.77
select_random_points 27,596 25,625 0.93
select_random_ranges 11,079 7,782 0.70
covering_index_scan 12,157 7,291 0.60
groupby_scan 33,197 34,818 1.05
index_join 11,250 11,151 0.99
index_join_scan 5,577 5,829 1.05
types_table_scan 1,115,313 1,287,484 1.15
table_scan 1,427,902 1,379,599 0.97
oltp_read_only 224,919 175,970 0.78
Average 0.88

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 243,048 380,690 1.57
oltp_insert 48,809 51,432 1.05
oltp_update_index 116,767 166,712 1.43
oltp_update_non_index 95,078 112,327 1.18
oltp_delete_insert 94,652 134,047 1.42
oltp_write_only 95,223 84,309 0.89
types_delete_insert 58,042 73,901 1.27
oltp_read_write 156,112 164,880 1.06
Average 1.23

File-Backed (autocommit)

Each statement runs as its own transaction — exposes per-commit
fixed costs that the wrapped-in-BEGIN/COMMIT tests amortize away.
SQLite uses WAL mode with synchronous=FULL in this section so
the comparison uses SQLite's durable WAL autocommit path.

Reads

Reads have no commit cost; these are the same SQL files as the
File-Backed Reads section, included here for symmetry and to
catch any per-statement overhead doltlite pays on the read path.

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 53,633 62,270 1.16
oltp_range_select 16,817 17,898 1.06
oltp_sum_range 15,838 17,191 1.09
oltp_order_range 3,926 3,770 0.96
oltp_distinct_range 4,683 4,579 0.98
oltp_index_scan 7,361 8,984 1.22
select_random_points 21,180 24,752 1.17
select_random_ranges 6,714 7,767 1.16
covering_index_scan 8,407 7,284 0.87
groupby_scan 32,838 34,679 1.06
index_join 9,365 11,297 1.21
index_join_scan 5,193 6,062 1.17
types_table_scan 1,125,653 1,282,434 1.14
table_scan 1,434,966 1,372,217 0.96
oltp_read_only 163,763 182,338 1.11
Average 1.09

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert_ac 21,057 79,760 3.79
oltp_insert_ac 25,329 92,001 3.63
oltp_update_index_ac 26,743 113,313 4.24
oltp_update_non_index_ac 23,257 95,045 4.09
oltp_delete_insert_ac 24,800 111,685 4.50
oltp_write_only_ac 26,680 102,498 3.84
types_delete_insert_ac 22,081 98,714 4.47
oltp_read_write_ac 30,865 118,246 3.83
Average 4.05

100000 rows, median of 5 invocations per test; autocommit writes use 9, workload-only timing via host monotonic clock when available.

Performance Ceiling Check (2.5x individual, 2x average; autocommit writes: 6x / 5x)

All tests within ceilings.

@timsehn
timsehn merged commit 989926f into master Jul 16, 2026
31 of 32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant