Skip to content

Take a table its upgrade record calls current as unchanged without reading the database [all-db] - #518

Merged
jeff377 merged 1 commit into
mainfrom
claude/fast-schema-compare
Nov 15, 2026
Merged

jeff377 merged 1 commit into
mainfrom
claude/fast-schema-compare

Conversation

@jeff377

@jeff377 jeff377 commented Nov 15, 2026

Copy link
Copy Markdown
Contributor

Part of #512. This is the second step. It uses the records added in #517 to skip comparing tables that have not changed.

What changes

  • UpgradeOptions.CompareMode is Fast by default.
    • A table is taken as unchanged without reading the database catalog when its record in st_table_upgrade meets three conditions:
      • the status is a success (Succeeded or Verified);
      • the record was written under this SchemaFormat;
      • the record's hash equals the hash of the definition's current schema.
    • Any other table is compared in full.
    • Full always compares. It is the only way to find a change made to a table by hand.
    • db upgrade --full-compare selects Full.
  • The store reads its records once per instance. It reads only table_name, status, format_version and schema_hash, row by row with a reader. schema_xml is never read for this, so a database with thousands of tables stays small in memory.
  • A compare that finds nothing to change records the table. The next fast compare can then rely on it, and a record left Upgrading or Failed gets corrected.
    • The new record is Verified unless this format wrote the existing record.
    • The format check ignores Verified records. Without this, a deployment that changed no table would block rolling back to an older tool.
    • Dry runs write nothing.
    • If the records cannot be read, the table is compared in full instead.
  • Where it applies:
    • TableSchemaBuilder.Execute and DatabaseInitializer use the fast path.
    • GetCommandText and CompareToDiff always compare in full.
    • SharedDatabaseState uses Full, because a persistent local test database may be changed by hand.
  • SchemaFormat remarks now say the version must also be raised when what a comparison detects changes, since fast compare trusts records written under the current version.
  • Documentation: db upgrade usage text, the DbManager help, and the CLI table in database-settings-guide (en and zh-TW).

Measured

These numbers were measured while designing this change, against local containers. The setup was 26 framework tables in 3 databases, averaged over 5 rounds. The last column reads all records of a database in one query, which is the approach this PR implements.

Database Full compare One record query per table One query per database
Oracle 599 ms 84 ms 1.5 ms
SQL Server 288 ms 43 ms 1.5 ms
PostgreSQL 56 ms 12 ms 0.9 ms
MySQL 48 ms 14 ms 1.1 ms
SQLite 1.2 ms 0.5 ms 0.2 ms

Compatibility

The public API changes are additions only:

  • UpgradeCompareMode
  • UpgradeOptions.CompareMode
  • TableUpgradeStatus.Verified, added last so existing values keep their numbers
  • TableUpgradeRecordStore.IsRecordedAsCurrent and TryRecordUnchanged

Behaviour change: with the default options, TableSchemaBuilder.Execute and db upgrade no longer notice a table changed by hand while its record is current. Use Full for that.

Verification

  • dotnet build Polhem.slnx -c Release: 0 warnings.
  • ./test.sh locally with all five databases. Everything passes except Generate_iOS_Builds, which fails because the local Xcode does not match the iOS workload; it is unrelated to this change.
  • New tests:
    • record summaries and TryRecordUnchanged on all five databases;
    • TableSchemaBuilder recording a current table;
    • DatabaseInitializer:
      • fast versus full when a table was dropped by hand;
      • falling back to a full compare for Failed, Upgrading, an old format, and a stale hash;
      • dry runs writing nothing;
      • a deployment with no changes leaving no record that blocks an older tool;
    • --full-compare on the command line.
  • Adversarial review of the diff before opening, with fixes:
    • Rollback (P2): verification wrote this format, so a no-op deployment blocked rolling back to an older tool. This led to the Verified status.
    • Unreadable records: these now fall back to a full compare instead of throwing.
    • Docs: the SchemaFormat remarks were incomplete; they are updated as described above.
    • Left as is:
      • A verification can overwrite a Failed record that another process wrote at the same moment. Only the diagnostic text is lost, and the next run compares in full again.
      • DatabaseInitializer keeps its own record cache. It is not updated when a table is upgraded, which costs at most one extra compare.

…ading the database [all-db]

UpgradeOptions.CompareMode is Fast by default: a table whose record in st_table_upgrade is a success of
this SchemaFormat with the definition's current schema hash is taken as unchanged without reading the
database catalog. The store reads only the table name, status, format and hash of every record, once per
instance and row by row; the recorded schemas are not read. Anything else compares in full, and
UpgradeCompareMode.Full (db upgrade --full-compare) always does, which finds changes made by hand.

A full compare that finds nothing to change records the table, so the next fast compare can rely on it
and a record left Upgrading or Failed is corrected. That record is Verified unless this format wrote it:
a deployment that changed no table must not refuse a rollback to an older tool, so the format check
leaves Verified records out. Dry runs write nothing, and records that cannot be read only cost the
shortcut.

TableSchemaBuilder.Execute and DatabaseInitializer use it; GetCommandText and CompareToDiff stay full.
The test fixture compares in full, because a persistent local database may be changed by hand.

PublicAPI: additions only (UpgradeCompareMode, UpgradeOptions.CompareMode, TableUpgradeStatus.Verified,
TableUpgradeRecordStore.IsRecordedAsCurrent and TryRecordUnchanged). TableUpgradeStatus gains a member
at the end, so existing values keep their numbers.

Part of #512
@jeff377 jeff377 added enhancement New feature or request area: database Polhem.Db, database providers, repositories labels Nov 15, 2026
@jeff377
jeff377 enabled auto-merge November 15, 2026 08:26
@sonarqubecloud

Copy link
Copy Markdown

@jeff377
jeff377 added this pull request to the merge queue Nov 15, 2026
Merged via the queue into main with commit 49c842f Nov 15, 2026
5 checks passed
@jeff377
jeff377 deleted the claude/fast-schema-compare branch November 15, 2026 08:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: database Polhem.Db, database providers, repositories enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant