Skip to content

Resolve WORKING/STAGED in dolt_schema_diff - #1786

Merged
timsehn merged 3 commits into
masterfrom
fix/schema-diff-working-ref
Jul 24, 2026
Merged

Resolve WORKING/STAGED in dolt_schema_diff#1786
timsehn merged 3 commits into
masterfrom
fix/schema-diff-working-ref

Conversation

@timsehn

@timsehn timsehn commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Bug (found by Ito QA on #1781, but pre-existing on master)

dolt_schema_diff('HEAD','WORKING') errored with dolt_schema_diff: to_ref 'WORKING' could not be resolved, even though the per-table diff surfaces (dolt_diff_<t>('HEAD','WORKING')) accept those pseudo-refs. Confirmed on clean master (independent of #1781).

Cause

sdResolveOne resolved every endpoint with doltliteResolveRef (commit-refs only) + doltliteCommitCatalogHash. The range (..) path's validation probe did the same. Neither understands WORKING/STAGED.

Fix

Route both the endpoint resolver and the range probe through doltliteResolveCatalogHashForRef — the shared catalog-aware resolver that handles HEAD/WORKING/STAGED and commit refs (AGENTS.md names it as the resolver for these). Also collapses the two-step resolve-then-load-catalog into one call (−14 lines in source).

Testing (fail-before / pass-after)

Added 7 WORKING/STAGED cases to test/doltlite_schema_diff.sh (two-arg, HEAD..WORKING range, WORKING/WORKING self, and HEAD/STAGED):

  • Before (unfixed engine): all 7 fail (49 passed, 7 failed).
  • After: 56 passed, 0 failed.
  • Oracle vs real Dolt unchanged: 60 passed, 0 failed.
  • Clean build under -Werror.

🤖 Generated with Claude Code

dolt_schema_diff resolved every endpoint with doltliteResolveRef, which
only understands commit refs, so dolt_schema_diff('HEAD','WORKING') (and
'HEAD..WORKING', and STAGED) failed with "to_ref 'WORKING' could not be
resolved" even though the per-table diff surfaces accept those pseudo-refs.

Route both the endpoint resolver (sdResolveOne) and the range-validation
probe through doltliteResolveCatalogHashForRef, the shared catalog-aware
resolver that handles HEAD/WORKING/STAGED and commit refs (AGENTS.md names
it as the one resolver for these). This also collapses the two-step
resolve-then-load-catalog into one call.

Adds fail-before/pass-after coverage: 7 WORKING/STAGED cases (two-arg,
range, self, and staged) that error on the old resolver and pass now.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

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 81 200 PASS
status_dirty_many_tables 85 200 PASS
diff_regular_working_one_table 77 150 PASS
diff_regular_working_many_tables 89 200 PASS
diff_stat_working_many_tables 89 200 PASS
diff_schema_working_many_tables 89 200 PASS
branch_list_many_branches 22 100 PASS
branch_create_delete 24 100 PASS
checkout_branch_clean 55 200 PASS
merge_data_no_conflicts 29 150 PASS
merge_schema_no_conflicts 21 100 PASS
merge_data_conflicts 126 250 PASS
merge_data_conflicts_with_resolve 126 250 PASS

Extend vc_oracle_schema_diff_test.sh with five cases exercising the
WORKING and STAGED pseudo-refs against real Dolt: added/dropped table via
HEAD..WORKING, a no-change EXPECT_EMPTY guard, HEAD..STAGED, and
STAGED..WORKING. Confirms doltlite matches Dolt's schema-diff semantics
for these refs, which the prior suite (commit-refs only) never checked.

Fail-before/pass-after: on the pre-fix engine the four non-empty cases
mismatch Dolt (doltlite errored on WORKING/STAGED); with the resolver fix
all five pass. Oracle suite goes 60 -> 65 passing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Sysbench-Style Benchmark: Doltlite vs SQLite

In-Memory

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 23,935 28,385 1.19
oltp_range_select 10,589 12,760 1.21
oltp_sum_range 9,103 12,264 1.35
oltp_order_range 2,655 3,083 1.16
oltp_distinct_range 3,665 4,236 1.16
oltp_index_scan 3,898 4,905 1.26
select_random_points 10,433 11,369 1.09
select_random_ranges 3,015 4,112 1.36
covering_index_scan 4,304 4,238 0.98
groupby_scan 32,101 36,358 1.13
index_join 5,849 8,135 1.39
index_join_scan 3,363 4,877 1.45
types_table_scan 1,106,378 1,323,680 1.20
table_scan 1,270,509 1,445,398 1.14
oltp_read_only 101,781 123,704 1.22
Average 1.22

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 183,222 244,943 1.34
oltp_insert 15,791 28,564 1.81
oltp_update_index 51,786 89,615 1.73
oltp_update_non_index 35,836 58,762 1.64
oltp_delete_insert 44,592 69,514 1.56
oltp_write_only 22,090 44,685 2.02
types_delete_insert 25,293 40,100 1.59
oltp_read_write 64,155 104,853 1.63
Average 1.66

File-Backed

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 106,984 56,504 0.53
oltp_range_select 18,620 15,742 0.85
oltp_sum_range 17,825 15,423 0.87
oltp_order_range 3,456 3,418 0.99
oltp_distinct_range 4,440 4,589 1.03
oltp_index_scan 12,111 8,482 0.70
select_random_points 19,527 14,685 0.75
select_random_ranges 11,243 7,045 0.63
covering_index_scan 12,582 7,435 0.59
groupby_scan 32,222 36,558 1.13
index_join 10,174 9,951 0.98
index_join_scan 4,197 5,258 1.25
types_table_scan 1,104,670 1,320,562 1.20
table_scan 1,257,573 1,437,703 1.14
oltp_read_only 218,397 162,501 0.74
Average 0.89

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 195,617 262,439 1.34
oltp_insert 21,669 35,592 1.64
oltp_update_index 76,811 117,248 1.53
oltp_update_non_index 57,444 80,775 1.41
oltp_delete_insert 65,931 93,998 1.43
oltp_write_only 42,680 65,116 1.53
types_delete_insert 39,717 54,072 1.36
oltp_read_write 86,982 125,233 1.44
Average 1.46

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 51,326 55,842 1.09
oltp_range_select 13,622 15,740 1.16
oltp_sum_range 12,540 15,502 1.24
oltp_order_range 3,035 3,379 1.11
oltp_distinct_range 4,013 4,564 1.14
oltp_index_scan 6,857 8,262 1.20
select_random_points 13,614 14,604 1.07
select_random_ranges 5,805 6,988 1.20
covering_index_scan 7,177 7,318 1.02
groupby_scan 32,010 36,533 1.14
index_join 7,499 9,966 1.33
index_join_scan 3,895 5,215 1.34
types_table_scan 1,099,418 1,321,088 1.20
table_scan 1,255,518 1,434,529 1.14
oltp_read_only 140,483 162,431 1.16
Average 1.17

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert_ac 15,890 60,552 3.81
oltp_insert_ac 17,265 79,372 4.60
oltp_update_index_ac 19,005 93,986 4.95
oltp_update_non_index_ac 15,406 69,638 4.52
oltp_delete_insert_ac 17,685 83,338 4.71
oltp_write_only_ac 17,230 87,561 5.08
types_delete_insert_ac 18,027 77,828 4.32
oltp_read_write_ac 23,828 97,219 4.08
Average 4.51

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: 10x / 5x)

All tests within ceilings.

@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

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 10× / 5×.

In-Memory

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 26,352 30,295 1.15
oltp_range_select 15,613 17,423 1.12
oltp_sum_range 13,624 16,971 1.25
oltp_order_range 2,913 3,214 1.10
oltp_distinct_range 3,718 4,185 1.13
oltp_index_scan 3,697 4,716 1.28
select_random_points 21,965 24,603 1.12
select_random_ranges 5,876 6,816 1.16
covering_index_scan 3,336 3,393 1.02
groupby_scan 29,646 33,543 1.13
index_join 6,281 8,326 1.33
index_join_scan 3,308 4,718 1.43
types_table_scan 887,799 1,040,103 1.17
table_scan 1,051,199 1,147,154 1.09
oltp_read_only 115,153 133,930 1.16
Average 1.17

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 188,216 260,466 1.38
oltp_insert 14,857 27,605 1.86
oltp_update_index 54,850 92,688 1.69
oltp_update_non_index 42,094 64,902 1.54
oltp_delete_insert 41,620 73,895 1.78
oltp_write_only 22,254 45,682 2.05
types_delete_insert 26,314 41,120 1.56
oltp_read_write 77,915 121,288 1.56
Average 1.68

File-Backed

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 91,224 52,307 0.57
oltp_range_select 22,849 19,623 0.86
oltp_sum_range 21,113 19,374 0.92
oltp_order_range 3,672 3,459 0.94
oltp_distinct_range 4,471 4,432 0.99
oltp_index_scan 10,404 7,347 0.71
select_random_points 29,039 27,081 0.93
select_random_ranges 12,681 9,114 0.72
covering_index_scan 10,229 6,003 0.59
groupby_scan 30,807 33,775 1.10
index_join 10,097 10,127 1.00
index_join_scan 4,207 5,046 1.20
types_table_scan 898,914 1,051,710 1.17
table_scan 1,156,672 1,132,002 0.98
oltp_read_only 207,723 166,326 0.80
Average 0.90

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 293,337 331,413 1.13
oltp_insert 29,678 49,545 1.67
oltp_update_index 180,104 183,851 1.02
oltp_update_non_index 139,328 122,822 0.88
oltp_delete_insert 258,793 143,175 0.55
oltp_write_only 98,521 102,045 1.04
types_delete_insert 99,023 92,398 0.93
oltp_read_write 151,493 194,557 1.28
Average 1.06

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 47,217 51,863 1.10
oltp_range_select 18,147 19,695 1.09
oltp_sum_range 16,262 19,323 1.19
oltp_order_range 3,321 3,458 1.04
oltp_distinct_range 4,094 4,419 1.08
oltp_index_scan 6,247 7,299 1.17
select_random_points 24,356 26,918 1.11
select_random_ranges 8,307 9,040 1.09
covering_index_scan 5,956 5,998 1.01
groupby_scan 30,032 33,901 1.13
index_join 8,032 10,076 1.25
index_join_scan 3,869 4,977 1.29
types_table_scan 880,881 1,042,379 1.18
table_scan 1,023,367 1,136,988 1.11
oltp_read_only 147,351 166,647 1.13
Average 1.13

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert_ac 28,807 80,670 2.80
oltp_insert_ac 31,638 93,640 2.96
oltp_update_index_ac 32,762 106,151 3.24
oltp_update_non_index_ac 30,254 164,879 5.45
oltp_delete_insert_ac 30,053 117,482 3.91
oltp_write_only_ac 51,613 280,046 5.43
types_delete_insert_ac 30,092 106,545 3.54
oltp_read_write_ac 34,691 121,702 3.51
Average 3.85

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: 10x / 5x)

All tests within ceilings.

@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

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 10× / 5×.

In-Memory

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 31,316 35,308 1.13
oltp_range_select 13,513 14,562 1.08
oltp_sum_range 12,079 14,104 1.17
oltp_order_range 3,077 3,335 1.08
oltp_distinct_range 4,090 4,457 1.09
oltp_index_scan 4,575 5,929 1.30
select_random_points 17,688 20,179 1.14
select_random_ranges 4,124 5,385 1.31
covering_index_scan 4,593 4,626 1.01
groupby_scan 33,934 36,817 1.08
index_join 6,859 8,831 1.29
index_join_scan 4,269 5,652 1.32
types_table_scan 1,132,452 1,328,622 1.17
table_scan 1,344,561 1,449,802 1.08
oltp_read_only 118,180 134,328 1.14
Average 1.16

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 240,659 334,900 1.39
oltp_insert 20,380 39,227 1.92
oltp_update_index 70,159 131,741 1.88
oltp_update_non_index 49,581 84,437 1.70
oltp_delete_insert 50,009 103,242 2.06
oltp_write_only 28,569 63,236 2.21
types_delete_insert 32,714 52,515 1.61
oltp_read_write 81,144 137,566 1.70
Average 1.81

File-Backed

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 112,803 63,507 0.56
oltp_range_select 21,003 17,172 0.82
oltp_sum_range 20,844 17,073 0.82
oltp_order_range 3,872 3,667 0.95
oltp_distinct_range 4,884 4,798 0.98
oltp_index_scan 13,094 9,087 0.69
select_random_points 25,831 23,084 0.89
select_random_ranges 12,329 8,266 0.67
covering_index_scan 13,215 7,865 0.60
groupby_scan 34,547 37,186 1.08
index_join 11,282 11,337 1.00
index_join_scan 5,309 6,266 1.18
types_table_scan 1,137,937 1,320,539 1.16
table_scan 1,343,179 1,465,895 1.09
oltp_read_only 241,001 175,659 0.73
Average 0.88

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 256,618 359,787 1.40
oltp_insert 30,923 51,193 1.66
oltp_update_index 103,776 163,829 1.58
oltp_update_non_index 77,367 110,258 1.43
oltp_delete_insert 79,754 132,366 1.66
oltp_write_only 55,923 88,626 1.58
types_delete_insert 49,828 71,421 1.43
oltp_read_write 109,652 161,269 1.47
Average 1.53

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 57,436 63,224 1.10
oltp_range_select 16,500 17,258 1.05
oltp_sum_range 15,317 16,832 1.10
oltp_order_range 3,573 3,640 1.02
oltp_distinct_range 4,473 4,808 1.07
oltp_index_scan 7,843 9,062 1.16
select_random_points 21,851 23,304 1.07
select_random_ranges 7,241 8,181 1.13
covering_index_scan 7,988 7,715 0.97
groupby_scan 34,397 37,435 1.09
index_join 8,821 11,273 1.28
index_join_scan 4,796 6,177 1.29
types_table_scan 1,131,698 1,318,865 1.17
table_scan 1,315,119 1,439,567 1.09
oltp_read_only 158,133 175,853 1.11
Average 1.11

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert_ac 15,814 63,920 4.04
oltp_insert_ac 17,440 82,731 4.74
oltp_update_index_ac 19,133 97,423 5.09
oltp_update_non_index_ac 15,452 72,315 4.68
oltp_delete_insert_ac 17,064 84,763 4.97
oltp_write_only_ac 17,081 83,656 4.90
types_delete_insert_ac 14,821 72,615 4.90
oltp_read_write_ac 23,177 92,731 4.00
Average 4.67

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: 10x / 5x)

All tests within ceilings.

@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

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 10× / 5×.

In-Memory

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 29,265 38,304 1.31
oltp_range_select 13,369 15,199 1.14
oltp_sum_range 11,971 14,714 1.23
oltp_order_range 2,872 3,207 1.12
oltp_distinct_range 3,887 4,364 1.12
oltp_index_scan 4,445 6,524 1.47
select_random_points 17,796 21,379 1.20
select_random_ranges 4,028 5,329 1.32
covering_index_scan 4,428 4,498 1.02
groupby_scan 31,417 35,487 1.13
index_join 6,949 9,083 1.31
index_join_scan 4,687 5,465 1.17
types_table_scan 1,059,463 1,343,334 1.27
table_scan 1,216,425 1,450,019 1.19
oltp_read_only 118,380 141,129 1.19
Average 1.21

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 231,340 358,964 1.55
oltp_insert 21,398 39,593 1.85
oltp_update_index 69,652 130,885 1.88
oltp_update_non_index 48,145 85,904 1.78
oltp_delete_insert 50,539 103,865 2.06
oltp_write_only 28,554 61,410 2.15
types_delete_insert 32,527 55,727 1.71
oltp_read_write 85,977 142,325 1.66
Average 1.83

File-Backed

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 99,397 63,215 0.64
oltp_range_select 20,526 17,870 0.87
oltp_sum_range 19,279 17,663 0.92
oltp_order_range 3,897 3,672 0.94
oltp_distinct_range 4,979 4,760 0.96
oltp_index_scan 11,921 9,229 0.77
select_random_points 25,673 25,332 0.99
select_random_ranges 11,282 7,876 0.70
covering_index_scan 12,668 7,314 0.58
groupby_scan 32,920 36,284 1.10
index_join 11,780 11,541 0.98
index_join_scan 5,624 6,053 1.08
types_table_scan 1,045,747 1,353,859 1.29
table_scan 1,453,205 1,465,611 1.01
oltp_read_only 228,443 180,041 0.79
Average 0.91

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 250,679 388,476 1.55
oltp_insert 51,661 51,808 1.00
oltp_update_index 111,514 168,454 1.51
oltp_update_non_index 99,533 113,036 1.14
oltp_delete_insert 88,980 134,148 1.51
oltp_write_only 65,339 85,653 1.31
types_delete_insert 54,496 74,428 1.37
oltp_read_write 146,752 166,486 1.13
Average 1.31

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,516 63,909 1.19
oltp_range_select 17,112 17,972 1.05
oltp_sum_range 15,473 17,629 1.14
oltp_order_range 3,573 3,658 1.02
oltp_distinct_range 4,434 4,888 1.10
oltp_index_scan 7,182 9,481 1.32
select_random_points 21,591 25,654 1.19
select_random_ranges 6,810 7,795 1.14
covering_index_scan 8,291 7,370 0.89
groupby_scan 32,468 36,140 1.11
index_join 9,303 11,541 1.24
index_join_scan 5,060 5,983 1.18
types_table_scan 1,050,486 1,347,516 1.28
table_scan 1,256,276 1,451,511 1.16
oltp_read_only 151,245 177,060 1.17
Average 1.15

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert_ac 22,818 82,674 3.62
oltp_insert_ac 26,897 97,985 3.64
oltp_update_index_ac 28,076 113,490 4.04
oltp_update_non_index_ac 23,014 94,754 4.12
oltp_delete_insert_ac 25,555 103,982 4.07
oltp_write_only_ac 24,755 113,371 4.58
types_delete_insert_ac 22,730 103,693 4.56
oltp_read_write_ac 32,173 111,964 3.48
Average 4.01

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: 10x / 5x)

All tests within ceilings.

@timsehn timsehn closed this Jul 24, 2026
@timsehn timsehn reopened this Jul 24, 2026
@itoqa

itoqa Bot commented Jul 24, 2026

Copy link
Copy Markdown

Ito QA test results
Commit: d5dcc02: 11 test cases ran, 1 failed ❌, 10 passed ✅.

Summary

Coverage spans schema comparisons across committed, staged, and working states, including additions, ranges, branches, recovery after errors, repeated scans, and committed-history regression behavior. It also exercises invalid-reference edge cases; the broader behavior is healthy, with one minor error-reporting gap for malformed range inputs.

Safe to merge — the only PR-attributable issue is minor: invalid range references are rejected but reported with an unhelpful generic error, while no schema data is returned incorrectly. This is a diagnostic usability caveat rather than a merge-blocking behavior failure.

Tests run by Ito

View full run

Result Severity Type Description
Minor severity Range Both invalid ranges correctly returned no schema rows and failed validation, but each emitted only ‘unknown operation’ instead of an endpoint-resolution error naming ‘does-not-exist’.
Catalog DoltLite successfully compared HEAD with WORKING and returned exactly one addition row for table w, including its expected CREATE TABLE statement.
Catalog After adding the uncommitted table w to the staging area, dolt_schema_diff compared HEAD with STAGED and returned exactly one addition with the expected CREATE TABLE statement.
Catalog With table t committed and table w left unstaged, comparing STAGED with HEAD returned no schema-diff rows, confirming that an empty staged catalog falls back to HEAD and excludes working-only changes.
Range The one-argument HEAD..WORKING range query succeeded and returned the same single added table w as the explicit HEAD, WORKING query.
Schema The local committed-reference schema-diff regression suite passed all 56 tests, covering additions, drops, modifications, renames, SQL text, ranges, branches, merges, and replay scenarios.
Schema Missing refs, an invalid from_ref, and an invalid to_ref each produced a controlled SQL error. A subsequent valid HEAD-to-HEAD query on the same database returned 0 rows, confirming no stale result data remained.
Schema Two consecutive HEAD-to-WORKING scans returned exactly one addition row named w each time, with no duplicate or stale rows.
Schema An invalid to_ref produced a controlled resolution error, and the subsequent valid HEAD-to-WORKING scan on the same connection returned only w without crashing.
Working DoltLite reported the uncommitted table w exactly once in both HEAD-to-WORKING and HEAD-to-STAGED comparisons, with matching CREATE TABLE text.
Working The STAGED-to-WORKING schema diff reported only the unstaged working_table addition; the previously staged staged_table was not duplicated.

Tip

Reply with @itoqa to send us feedback on this test run.

int rc;

rc = doltliteResolveRef(db, zRef, &commitHash);
int rc = doltliteResolveCatalogHashForRef(db, zRef, pCatHash);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

View All Evidence

Minor severity Invalid range endpoints hide the failing reference

What failed: Both invalid ranges correctly returned no schema rows and failed validation, but each emitted only ‘unknown operation’ instead of an endpoint-resolution error naming ‘does-not-exist’.

Impact · Steps · Stub / mock · Analysis · Why this is likely a bug
  • Severity: Minor Minor severity
  • Impact: Users who provide a nonexistent range endpoint receive a generic error instead of being told which endpoint could not be resolved, making the query failure harder to diagnose. The invalid query is still rejected and no schema rows are returned.
  • Steps to Reproduce:
    1. Create a database with a committed schema and run dolt_schema_diff with HEAD..does-not-exist.
    2. Run the inverse range does-not-exist..HEAD.
    3. Inspect the errors returned for both queries.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: The explicit two-reference path calls sdResolveOne at src/doltlite_schema_diff.c:764-766, which formats failures as ‘dolt_schema_diff: '' could not be resolved’. The PR-changed range path at src/doltlite_schema_diff.c:821-826 calls doltliteResolveCatalogHashForRef for each endpoint and returns the raw rc, so it bypasses that formatter; the smallest fix is to route each parsed endpoint through the existing endpoint-aware error handling while retaining the catalog-aware resolver.
  • Why this is likely a bug: The local execution reproduced the same generic error for both invalid endpoint directions, and the source shows a deterministic production path that discards the endpoint context. This is not caused by browser setup or test data; preserving the resolver error while assigning the same endpoint-specific message used by explicit references would correct the behavior.
Relevant code

src/doltlite_schema_diff.c:736-742

if( rc!=SQLITE_OK ){
  sqlite3_free(pVtab->zErrMsg);
  pVtab->zErrMsg = sqlite3_mprintf(
    "dolt_schema_diff: %s '%s' could not be resolved", zWhich, zRef);
  return SQLITE_ERROR;
}

src/doltlite_schema_diff.c:821-826

rc = doltliteResolveCatalogHashForRef(db, zRangeFrom, &probe);
if( rc==SQLITE_OK ) rc = doltliteResolveCatalogHashForRef(db, zRangeTo, &probe);
if( rc!=SQLITE_OK ){
  sqlite3_free(zRangeFrom);
  sqlite3_free(zRangeTo);
  return rc;
}
Evidence Package
Copy prompt for an agent
Ito QA identified the following failure during automated PR testing. Please investigate and propose a fix.

**Minor severity — Invalid range endpoints hide the failing reference**

**What failed:** Both invalid ranges correctly returned no schema rows and failed validation, but each emitted only ‘unknown operation’ instead of an endpoint-resolution error naming ‘does-not-exist’.

- **Impact:** Users who provide a nonexistent range endpoint receive a generic error instead of being told which endpoint could not be resolved, making the query failure harder to diagnose. The invalid query is still rejected and no schema rows are returned.
- **Steps to reproduce:**
  1. Create a database with a committed schema and run dolt_schema_diff with HEAD..does-not-exist.
  2. Run the inverse range does-not-exist..HEAD.
  3. Inspect the errors returned for both queries.
- **Stub / mock content:** No stubs, mocks, or bypasses were applied for this test in the recorded run.
- **Code analysis:** The explicit two-reference path calls sdResolveOne at src/doltlite_schema_diff.c:764-766, which formats failures as ‘dolt_schema_diff: <which> '<ref>' could not be resolved’. The PR-changed range path at src/doltlite_schema_diff.c:821-826 calls doltliteResolveCatalogHashForRef for each endpoint and returns the raw rc, so it bypasses that formatter; the smallest fix is to route each parsed endpoint through the existing endpoint-aware error handling while retaining the catalog-aware resolver.
- **Why this is likely a bug:** The local execution reproduced the same generic error for both invalid endpoint directions, and the source shows a deterministic production path that discards the endpoint context. This is not caused by browser setup or test data; preserving the resolver error while assigning the same endpoint-specific message used by explicit references would correct the behavior.

**Relevant code:**

`src/doltlite_schema_diff.c:736-742`

~~~c
if( rc!=SQLITE_OK ){
  sqlite3_free(pVtab->zErrMsg);
  pVtab->zErrMsg = sqlite3_mprintf(
    "dolt_schema_diff: %s '%s' could not be resolved", zWhich, zRef);
  return SQLITE_ERROR;
}
~~~

`src/doltlite_schema_diff.c:821-826`

~~~c
rc = doltliteResolveCatalogHashForRef(db, zRangeFrom, &probe);
if( rc==SQLITE_OK ) rc = doltliteResolveCatalogHashForRef(db, zRangeTo, &probe);
if( rc!=SQLITE_OK ){
  sqlite3_free(zRangeFrom);
  sqlite3_free(zRangeTo);
  return rc;
}
~~~

Ito QA flagged that a malformed range endpoint (e.g. 'x..HEAD') failed
validation with a generic "unknown operation" instead of naming the bad
ref, unlike the two-arg form which reports
"dolt_schema_diff: to_ref 'x' could not be resolved".

The range probe returned the raw resolver rc without setting zErrMsg, so
SQLite surfaced a default message. Set a from_ref/to_ref-specific message
naming the failing endpoint, matching sdResolveOne.

Fail-before/pass-after: two range cases asserting the named-endpoint text
(fail on the generic message, pass now). Suite 56 -> 58.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@itoqa

itoqa Bot commented Jul 24, 2026

Copy link
Copy Markdown

Ito QA test results
Ito Diff Reportd5dcc02099ca9e: 6 test cases ran, 1 fixed ✅, 5 passing ✅.

Diff Summary

The run covered schema comparison across committed, working, staged, and range-based states, including normal additions and recovery after invalid endpoints. It exercised business logic and adversarial edge cases around endpoint validation, error attribution, filtering, repeated queries, and clean recovery without stale results or crashes.

Safe to merge — all exercised behaviors passed, including the broader schema-diff regression coverage, with no PR-attributable regressions, new failures, or previously flagged failures. Several previously passing areas were not exercised in this run, but they are coverage caveats rather than merge blockers.

Tests run by Ito

View full run

Result State Severity Type Description
❌->✅ Fixed Range Both invalid range directions failed cleanly with endpoint-specific resolution errors and produced no schema rows.
Passing Range The missing-left..HEAD query failed before producing schema rows and explicitly identified missing-left as the unresolved from_ref.
Passing Range The corrected three-argument filtered query failed before producing schema rows and identified missing-left as the unresolved from_ref. The earlier failure was caused by using the two-argument form, which treated t as to_ref, and by running a stale native binary; a clean rebuild and valid filtered control completed successfully.
Passing Range The supported unfiltered and filtered range queries reported the missing right endpoint as to_ref 'missing-right' before producing rows. The valid filtered HEAD..WORKING retry completed successfully with no rows for unchanged table t; the earlier failure used two arguments, which are parsed as from_ref and to_ref rather than a range plus filter.
Passing Range Invalid right endpoints retained their own to_ref errors, while the valid HEAD..WORKING retry returned the expected one-row schema diff without stale state or a crash.
Passing Range The native DoltLite CLI preserved the correct from_ref and to_ref names for invalid range endpoints, returned exit status 1 with no rows, and the complete schema-diff regression suite passed 58 of 58 tests.
⏸️ Skipped Catalog DoltLite successfully compared HEAD with WORKING and returned exactly one addition row for table w, including its expected CREATE TABLE statement.
⏸️ Skipped Catalog After adding the uncommitted table w to the staging area, dolt_schema_diff compared HEAD with STAGED and returned exactly one addition with the expected CREATE TABLE statement.
⏸️ Skipped Catalog With table t committed and table w left unstaged, comparing STAGED with HEAD returned no schema-diff rows, confirming that an empty staged catalog falls back to HEAD and excludes working-only changes.
⏸️ Skipped Range The one-argument HEAD..WORKING range query succeeded and returned the same single added table w as the explicit HEAD, WORKING query.
⏸️ Skipped Schema The local committed-reference schema-diff regression suite passed all 56 tests, covering additions, drops, modifications, renames, SQL text, ranges, branches, merges, and replay scenarios.
⏸️ Skipped Schema Missing refs, an invalid from_ref, and an invalid to_ref each produced a controlled SQL error. A subsequent valid HEAD-to-HEAD query on the same database returned 0 rows, confirming no stale result data remained.
⏸️ Skipped Schema Two consecutive HEAD-to-WORKING scans returned exactly one addition row named w each time, with no duplicate or stale rows.
⏸️ Skipped Schema An invalid to_ref produced a controlled resolution error, and the subsequent valid HEAD-to-WORKING scan on the same connection returned only w without crashing.
⏸️ Skipped Working DoltLite reported the uncommitted table w exactly once in both HEAD-to-WORKING and HEAD-to-STAGED comparisons, with matching CREATE TABLE text.
⏸️ Skipped Working The STAGED-to-WORKING schema diff reported only the unstaged working_table addition; the previously staged staged_table was not duplicated.

Tip

Reply with @itoqa to send us feedback on this test run.

@timsehn
timsehn merged commit eafe6fe into master Jul 24, 2026
30 checks passed
@timsehn
timsehn deleted the fix/schema-diff-working-ref branch July 24, 2026 23:52
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