Skip to content

Attribute index changes to their table in the dolt_diff summary#1656

Merged
timsehn merged 1 commit into
masterfrom
bug/index-diff-attribution
Jul 16, 2026
Merged

Attribute index changes to their table in the dolt_diff summary#1656
timsehn merged 1 commit into
masterfrom
bug/index-diff-attribution

Conversation

@timsehn

@timsehn timsehn commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

From the schema-objects-through-add/diff oracle gap survey: the add oracle (516 lines) had zero schema-object coverage and the diff oracle (864 lines) nearly none. Probing the gaps immediately found that the dolt_diff summary is blind to index changes — an index-only commit produces no row at all, where Dolt reports t | 0 | 1:

  • CREATE INDEX and DROP INDEX, committed and working-set, full scans and table_name-filtered scans — all silent
  • view/trigger commits were already correct (dolt_schemas | 1 | 1) — now locked by oracle scenarios

Fix: the summary compares each catalog's index schema rows (name + sql) per table and sets schema_change=1 on the parent when they differ — index changes have no named entry, so attribution must go through schema rows, the same lesson as the staging fixes. Also guards the full scan's unnamed-entry branch to the master entry (index entries were previously taking the dolt_schemas comparison path against non-master roots).

Coverage: nine new scenarios in vc_oracle_diff_test.sh verified against real Dolt, including a dual-setup helper for statements whose syntax diverges (DROP INDEX … ON t, MySQL trigger bodies). Fail-before: master fails exactly the six index scenarios (57/63); fixed: 63/63.

Filed alongside: #1655dolt_status has the same index blindness (sibling of #1651); its fix targets doltlite_status.c and is sequenced after #1653 to avoid conflicts, and the dolt_add oracle scenarios for schema objects (which project dolt_status) queue behind both.

Validation: diff oracle 63/63, diff-stat 49/49, diff-tvf 8/8, status 27/27 vs real Dolt; battery 88/88; matrix 38/38; C regression 309,830/309,830.

🤖 Generated with Claude Code

Index changes carry no named catalog entry, so the diff summary was
blind to them: an index-only commit produced no row at all where Dolt
reports the table with schema_change=1, for CREATE and DROP alike, in
committed and working diffs, full scans and table_name-filtered scans.
The summary now compares each catalog's index schema rows (name and
sql) per table and flags the parent when they differ; the unnamed-entry
branch in the full scan is also guarded to the master entry so index
entries no longer take the dolt_schemas path against garbage roots.

The diff oracle gains nine schema-object scenarios verified against
real Dolt: index create/drop/mixed-with-data commits, working-set index
changes, sibling-noise isolation, filtered scans, view-only and
trigger-only commits (locking the already-correct dolt_schemas rows),
and no-row-diff assertions for index-only commits. Statements whose
syntax diverges between engines run through a new dual-setup 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 80 200 PASS
status_dirty_many_tables 82 200 PASS
diff_regular_working_one_table 76 150 PASS
diff_regular_working_many_tables 87 200 PASS
diff_stat_working_many_tables 88 200 PASS
diff_schema_working_many_tables 88 200 PASS
branch_list_many_branches 21 100 PASS
branch_create_delete 23 100 PASS
checkout_branch_clean 54 200 PASS
merge_data_no_conflicts 28 150 PASS
merge_schema_no_conflicts 20 100 PASS
merge_data_conflicts 124 250 PASS
merge_data_conflicts_with_resolve 124 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 18,134 20,391 1.12
oltp_range_select 8,220 9,945 1.21
oltp_sum_range 7,899 9,153 1.16
oltp_order_range 2,165 2,467 1.14
oltp_distinct_range 2,933 3,273 1.12
oltp_index_scan 3,114 3,830 1.23
select_random_points 8,898 9,447 1.06
select_random_ranges 2,360 3,073 1.30
covering_index_scan 2,968 3,056 1.03
groupby_scan 24,851 26,421 1.06
index_join 4,504 6,346 1.41
index_join_scan 2,514 3,856 1.53
types_table_scan 864,406 1,010,957 1.17
table_scan 988,488 1,106,365 1.12
oltp_read_only 79,375 92,816 1.17
Average 1.19

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 120,710 169,166 1.40
oltp_insert 10,988 19,726 1.80
oltp_update_index 38,397 68,174 1.78
oltp_update_non_index 25,217 43,224 1.71
oltp_delete_insert 35,385 51,669 1.46
oltp_write_only 16,741 32,388 1.93
types_delete_insert 18,179 28,768 1.58
oltp_read_write 48,674 78,834 1.62
Average 1.66

File-Backed

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 42,845 28,537 0.67
oltp_range_select 11,063 11,066 1.00
oltp_sum_range 10,453 10,359 0.99
oltp_order_range 2,556 2,645 1.03
oltp_distinct_range 3,290 3,441 1.05
oltp_index_scan 5,801 5,184 0.89
select_random_points 11,931 10,704 0.90
select_random_ranges 4,987 3,926 0.79
covering_index_scan 5,669 4,173 0.74
groupby_scan 25,201 26,607 1.06
index_join 6,101 7,395 1.21
index_join_scan 2,916 3,994 1.37
types_table_scan 861,945 1,007,833 1.17
table_scan 991,825 1,108,115 1.12
oltp_read_only 115,039 105,090 0.91
Average 0.99

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 165,865 233,132 1.41
oltp_insert 22,725 35,031 1.54
oltp_update_index 131,696 130,430 0.99
oltp_update_non_index 105,303 95,098 0.90
oltp_delete_insert 129,525 106,757 0.82
oltp_write_only 84,897 78,260 0.92
types_delete_insert 72,322 61,945 0.86
oltp_read_write 116,316 125,312 1.08
Average 1.07

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 26,297 28,722 1.09
oltp_range_select 9,509 11,098 1.17
oltp_sum_range 9,201 10,406 1.13
oltp_order_range 2,390 2,666 1.12
oltp_distinct_range 3,156 3,421 1.08
oltp_index_scan 4,293 5,186 1.21
select_random_points 10,319 10,860 1.05
select_random_ranges 3,360 3,926 1.17
covering_index_scan 4,002 4,326 1.08
groupby_scan 25,007 26,606 1.06
index_join 5,311 7,274 1.37
index_join_scan 2,763 4,016 1.45
types_table_scan 863,366 1,007,696 1.17
table_scan 993,926 1,104,296 1.11
oltp_read_only 90,877 104,577 1.15
Average 1.16

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert_ac 31,396 90,828 2.89
oltp_insert_ac 36,757 99,681 2.71
oltp_update_index_ac 37,385 124,990 3.34
oltp_update_non_index_ac 33,763 99,420 2.94
oltp_delete_insert_ac 35,932 109,778 3.06
oltp_write_only_ac 36,103 109,526 3.03
types_delete_insert_ac 33,374 101,339 3.04
oltp_read_write_ac 39,532 128,093 3.24
Average 3.03

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 30,989 36,981 1.19
oltp_range_select 13,310 15,624 1.17
oltp_sum_range 12,179 13,826 1.14
oltp_order_range 2,869 3,134 1.09
oltp_distinct_range 4,061 4,223 1.04
oltp_index_scan 4,466 6,292 1.41
select_random_points 18,702 20,977 1.12
select_random_ranges 4,151 5,308 1.28
covering_index_scan 4,301 4,566 1.06
groupby_scan 31,570 33,554 1.06
index_join 6,851 10,070 1.47
index_join_scan 4,470 5,672 1.27
types_table_scan 1,045,504 1,251,981 1.20
table_scan 1,221,612 1,353,190 1.11
oltp_read_only 118,561 135,779 1.15
Average 1.18

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 235,449 346,398 1.47
oltp_insert 20,079 38,789 1.93
oltp_update_index 68,303 127,589 1.87
oltp_update_non_index 49,675 83,783 1.69
oltp_delete_insert 49,137 103,913 2.11
oltp_write_only 28,456 60,705 2.13
types_delete_insert 33,523 52,792 1.57
oltp_read_write 82,567 137,427 1.66
Average 1.81

File-Backed

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 98,733 60,615 0.61
oltp_range_select 21,010 18,126 0.86
oltp_sum_range 18,935 16,376 0.86
oltp_order_range 3,773 3,483 0.92
oltp_distinct_range 4,714 4,626 0.98
oltp_index_scan 11,464 8,984 0.78
select_random_points 26,378 24,087 0.91
select_random_ranges 11,131 7,750 0.70
covering_index_scan 11,299 7,186 0.64
groupby_scan 32,902 33,979 1.03
index_join 10,549 11,469 1.09
index_join_scan 5,325 5,968 1.12
types_table_scan 1,042,393 1,267,789 1.22
table_scan 1,221,350 1,354,218 1.11
oltp_read_only 217,331 171,465 0.79
Average 0.91

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 252,001 368,803 1.46
oltp_insert 37,756 51,441 1.36
oltp_update_index 104,161 159,789 1.53
oltp_update_non_index 84,246 107,010 1.27
oltp_delete_insert 84,229 127,954 1.52
oltp_write_only 62,065 83,009 1.34
types_delete_insert 54,055 70,857 1.31
oltp_read_write 121,184 160,736 1.33
Average 1.39

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 52,996 61,238 1.16
oltp_range_select 16,020 18,337 1.14
oltp_sum_range 14,618 16,635 1.14
oltp_order_range 3,492 3,509 1.00
oltp_distinct_range 4,413 4,712 1.07
oltp_index_scan 7,242 9,238 1.28
select_random_points 22,027 24,505 1.11
select_random_ranges 6,693 7,740 1.16
covering_index_scan 7,276 7,186 0.99
groupby_scan 31,989 34,050 1.06
index_join 8,725 11,440 1.31
index_join_scan 4,882 5,920 1.21
types_table_scan 1,041,361 1,263,457 1.21
table_scan 1,229,310 1,350,630 1.10
oltp_read_only 152,407 171,482 1.13
Average 1.14

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert_ac 25,878 111,212 4.30
oltp_insert_ac 29,640 118,899 4.01
oltp_update_index_ac 35,941 128,458 3.57
oltp_update_non_index_ac 28,978 114,895 3.96
oltp_delete_insert_ac 31,324 122,882 3.92
oltp_write_only_ac 34,228 124,437 3.64
types_delete_insert_ac 27,254 106,224 3.90
oltp_read_write_ac 36,368 131,595 3.62
Average 3.87

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 30,166 37,528 1.24
oltp_range_select 13,117 15,717 1.20
oltp_sum_range 11,864 13,932 1.17
oltp_order_range 2,854 3,157 1.11
oltp_distinct_range 3,939 4,166 1.06
oltp_index_scan 4,454 6,155 1.38
select_random_points 17,738 21,116 1.19
select_random_ranges 3,938 5,235 1.33
covering_index_scan 4,397 4,495 1.02
groupby_scan 31,870 33,154 1.04
index_join 6,859 8,997 1.31
index_join_scan 4,689 5,349 1.14
types_table_scan 1,056,097 1,256,369 1.19
table_scan 1,228,172 1,350,562 1.10
oltp_read_only 120,376 137,706 1.14
Average 1.18

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 226,851 349,541 1.54
oltp_insert 21,191 38,705 1.83
oltp_update_index 70,089 131,418 1.88
oltp_update_non_index 47,104 86,022 1.83
oltp_delete_insert 50,561 101,756 2.01
oltp_write_only 28,324 60,248 2.13
types_delete_insert 33,247 54,637 1.64
oltp_read_write 85,880 139,812 1.63
Average 1.81

File-Backed

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 96,781 61,987 0.64
oltp_range_select 20,479 18,189 0.89
oltp_sum_range 19,698 16,705 0.85
oltp_order_range 3,706 3,514 0.95
oltp_distinct_range 4,726 4,517 0.96
oltp_index_scan 11,514 8,979 0.78
select_random_points 25,910 24,833 0.96
select_random_ranges 10,941 7,626 0.70
covering_index_scan 12,390 7,169 0.58
groupby_scan 32,587 33,992 1.04
index_join 11,887 11,257 0.95
index_join_scan 5,790 5,875 1.01
types_table_scan 1,050,188 1,265,734 1.21
table_scan 1,229,223 1,344,802 1.09
oltp_read_only 220,684 175,323 0.79
Average 0.89

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 244,586 377,170 1.54
oltp_insert 47,937 50,957 1.06
oltp_update_index 111,483 168,897 1.52
oltp_update_non_index 97,182 111,452 1.15
oltp_delete_insert 93,440 129,843 1.39
oltp_write_only 81,974 82,796 1.01
types_delete_insert 57,596 73,126 1.27
oltp_read_write 147,977 161,389 1.09
Average 1.25

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 52,805 62,399 1.18
oltp_range_select 16,170 18,181 1.12
oltp_sum_range 14,716 16,684 1.13
oltp_order_range 3,250 3,473 1.07
oltp_distinct_range 4,271 4,509 1.06
oltp_index_scan 7,235 9,103 1.26
select_random_points 20,862 24,487 1.17
select_random_ranges 6,459 7,730 1.20
covering_index_scan 7,648 7,269 0.95
groupby_scan 31,943 33,975 1.06
index_join 9,078 11,658 1.28
index_join_scan 4,936 5,966 1.21
types_table_scan 1,049,377 1,267,048 1.21
table_scan 1,239,852 1,358,677 1.10
oltp_read_only 153,158 175,547 1.15
Average 1.14

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert_ac 25,612 85,836 3.35
oltp_insert_ac 26,238 103,586 3.95
oltp_update_index_ac 29,502 124,942 4.24
oltp_update_non_index_ac 24,957 97,683 3.91
oltp_delete_insert_ac 28,578 109,794 3.84
oltp_write_only_ac 27,234 108,361 3.98
types_delete_insert_ac 24,644 104,642 4.25
oltp_read_write_ac 33,674 118,757 3.53
Average 3.88

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,795 42,463 1.22
oltp_range_select 20,073 22,235 1.11
oltp_sum_range 18,542 20,837 1.12
oltp_order_range 3,565 3,899 1.09
oltp_distinct_range 4,677 4,902 1.05
oltp_index_scan 4,801 6,572 1.37
select_random_points 30,496 33,837 1.11
select_random_ranges 7,882 9,090 1.15
covering_index_scan 4,242 4,674 1.10
groupby_scan 36,496 38,761 1.06
index_join 8,433 11,277 1.34
index_join_scan 4,208 5,730 1.36
types_table_scan 1,159,526 1,289,351 1.11
table_scan 1,411,874 1,401,559 0.99
oltp_read_only 154,905 178,506 1.15
Average 1.16

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 249,766 364,388 1.46
oltp_insert 19,592 37,909 1.93
oltp_update_index 74,201 129,465 1.74
oltp_update_non_index 53,968 85,780 1.59
oltp_delete_insert 52,321 101,186 1.93
oltp_write_only 28,263 62,222 2.20
types_delete_insert 35,244 58,123 1.65
oltp_read_write 114,208 166,004 1.45
Average 1.75

File-Backed

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 105,770 67,180 0.64
oltp_range_select 27,616 25,576 0.93
oltp_sum_range 27,267 24,333 0.89
oltp_order_range 4,608 4,363 0.95
oltp_distinct_range 5,558 5,443 0.98
oltp_index_scan 12,263 9,279 0.76
select_random_points 38,921 37,907 0.97
select_random_ranges 15,400 12,123 0.79
covering_index_scan 11,483 7,126 0.62
groupby_scan 36,546 39,783 1.09
index_join 12,304 13,703 1.11
index_join_scan 5,243 6,163 1.18
types_table_scan 1,047,488 1,293,672 1.24
table_scan 1,391,500 1,366,168 0.98
oltp_read_only 251,546 214,835 0.85
Average 0.93

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 265,070 383,260 1.45
oltp_insert 26,413 46,675 1.77
oltp_update_index 95,260 152,305 1.60
oltp_update_non_index 81,254 111,850 1.38
oltp_delete_insert 80,111 125,518 1.57
oltp_write_only 50,797 80,021 1.58
types_delete_insert 51,089 72,912 1.43
oltp_read_write 140,322 183,119 1.30
Average 1.51

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,899 66,621 1.17
oltp_range_select 22,775 25,320 1.11
oltp_sum_range 21,549 24,652 1.14
oltp_order_range 4,583 4,850 1.06
oltp_distinct_range 5,458 5,690 1.04
oltp_index_scan 7,222 9,226 1.28
select_random_points 34,618 39,269 1.13
select_random_ranges 10,825 12,035 1.11
covering_index_scan 7,342 7,266 0.99
groupby_scan 36,704 39,190 1.07
index_join 10,039 13,353 1.33
index_join_scan 4,693 6,129 1.31
types_table_scan 1,142,558 1,292,861 1.13
table_scan 1,420,026 1,386,047 0.98
oltp_read_only 195,163 214,382 1.10
Average 1.13

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert_ac 24,169 87,668 3.63
oltp_insert_ac 28,561 107,761 3.77
oltp_update_index_ac 28,942 120,780 4.17
oltp_update_non_index_ac 24,225 95,459 3.94
oltp_delete_insert_ac 26,885 106,917 3.98
oltp_write_only_ac 26,264 109,591 4.17
types_delete_insert_ac 23,799 95,785 4.02
oltp_read_write_ac 33,992 115,910 3.41
Average 3.89

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 4785004 into master Jul 16, 2026
27 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