Repository navigation
Lite: refuse a newer data file, re-apply newer columns on every start, and run each due collector once after a sleep (#4727, #4728) - #4753
Merged
Conversation
…than the app's (#4727) An older Lite on the shared data root opened a file whose schema had moved on, and every batch for the 13 tables that gained columns in versions 60 to 65 then failed with only a log line to show for it. The stamp is now read before anything is created, and a newer one throws SchemaVersionTooNewException naming both versions and the file, which the startup handler shows. The file is not written to.
…o a failed or skipped add is healed (#4727) The v60 to v65 steps each caught a failed ALTER, logged a warning and let the version be stamped, so the step never ran again and that table's batches failed on every start. The 15 (table, column, type) entries are now one list, AddedColumns. Each step adds its own version's entries through one shared method, and the same method runs over the whole list on every start of an existing file (existing version above 0), after the table and index statements. It reads which tables and columns exist in one query, runs ADD COLUMN IF NOT EXISTS only for a missing column, skips a table that does not exist yet (the table statements create it with the column), and logs one Error naming the table and column for an add that still fails, so the start continues and the next start retries it. ADD COLUMN goes through on a table that has an index and a v_ view (only DROP COLUMN and ALTER COLUMN hit the Dependency Error), so no index is dropped first. The stamp is not held back. The source pins for the v60 to v64 steps now assert the entries in the list and the step's call to the shared method instead of the literal ALTER text they no longer contain.
… a minute apart (#4728) After a sleep the collection loop's delay returned long after the slot it waited for, and the loop handed the collectors that stale slot, so every collector due then ran, and ran again at the next slot a minute later. The wait now lives in an internal static method, WaitForNextCycleAsync, that takes the clock and the delay. When the delay returns a whole interval or more past its slot, the cycle starts at the latest grid slot at or before now (the same arithmetic as Darling's ServedSlot). A wait that returns on time is unchanged. The test drives that method with a fake clock and a fake delay and mark-and-due over 1, 5 and 15 minute collectors across a simulated 47 minute sleep: each due collector runs once after the resume, and the 1 minute collector still runs at the resume slot and the next one.
erikdarlingdata
marked this pull request as ready for review
September 29, 2026 09:25
…ns entries and the per-version step calls (#4727) Two Darling tests read Lite's DuckDbInitializer as text and still looked for the old three-field column tuples, which #4727 replaced with the four-field entries of DuckDbInitializer.AddedColumns (version first). Each test now pins its columns as entries of that list, and pins that its own migration step calls AddMissingColumnsAsync(connection, AddedColumnsForVersion(N)) for its own version. The step is cut at the next "if (fromVersion <", so a call in a later step cannot satisfy the pin. The schema-version floor, the "twinning Darling's V134" / "V137" anchors, the "Running migration to v64" log line and the golden-file checks are unchanged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4727
Fixes #4728
Why
Three Lite faults, all in how the collection loop and the data file's schema version are handled.
ALTER TABLE, logged a warning and let the version be stamped anyway. The step never ran again, so one transient fault during the one start that ran it left that table's batches failing on every later start. A file stamped without the column had the same problem.What changes
A data file stamped newer than the app is refused (#4727 item 1)
DuckDbInitializer.InitializeCoreAsyncreads the schema stamp before it creates or alters anything (a missingschema_versiontable still reads as version 0, so a fresh file takes the same create path as before).CurrentSchemaVersionthrows the newSchemaVersionTooNewException. Its message names the file and both versions, says the file was not changed, and says what to do.MainWindow_Loadedalready catches an initialization failure and showsex.Messageunder "Initialization Error". The collectors are built after that call, so nothing collects and nothing writes.OpenDatabaseAsync, so it can never be mistaken for the DuckDB storage-format mismatch that triggers the Parquet rebuild.The columns versions 60 to 65 added are re-applied on every start (#4727 item 2)
DuckDbInitializer.AddedColumns, each tagged with its version.AddMissingColumnsAsync, over its own version's entries. The same method runs once over the whole list on every start of an existing file (existing version above 0), after the table and index statements. A fresh file still gets the columns from the table statements.ADD COLUMN IF NOT EXISTSonly for a column that is missing. A start where nothing is missing runs noALTER. A table that does not exist yet (a file older than the table) is skipped quietly, because the table statements that follow create it with the column; before, that case logged a warning from the step.ADD COLUMN IF NOT EXISTSgoes through, as a real add and as a no-op, on a table that has an index and av_view over it.DROP COLUMNis the statement that fails while an index exists (a view does not block it). So the shared method needs no index drop, and the v60 to v65 steps never had one. The new test runs the heal with the indexes and views standing, as a real file has them, so a DuckDB upgrade that starts refusing the add would fail it.DeltaFamilyIntervalColumnTestsx2,PerfmonCounterTypeTests,QsCaptureModeRungTests,TimeHonestyRungTests) asserted the literalALTERtext or the per-step tuples inside each step. They now assert the entries inAddedColumnsand the step's call toAddMissingColumnsAsync. They check the same columns and types, in the new place.After a sleep and resume, each due collector runs once (#4728)
internal static CollectionBackgroundService.WaitForNextCycleAsync(cycleStart, interval, utcNow, delay, ct), whichExecuteAsynccalls. The clock and the delay are passed in, so a test drives the production path.CollectorCadence.NextDue(nextCycle, now, interval) - interval, the same arithmetic as Darling'sServedSlot. A wait that returns on time hands over the same slot as before.Test plan
SchemaColumnHealingTests(3 tests) pass. The main test initializes a file, drops the indexes DuckDB needs in order to drop the columns, drops all 15 columns, puts the indexes back, checks thev_views are standing and the stamp is current, re-initializes, and asserts all 15 columns are back with their types and visible through theirv_views. It failed before the change (first failing assertion:wait_stats.sample_interval_seconds is back).wait_stats.broken_column, and the next entry is still added. A table that does not exist yet logs no warning and no error.CollectionCycleResumeTests(2 tests) pass. A 1-minute loop with a 5-minute and a 15-minute collector, a 47-minute sleep, andScheduleManagermark-and-due: after the resume the 5-minute collector runs once at the resume slot and next 5 minutes later, the 15-minute collector runs once, and the 1-minute collector runs at the resume slot and the next one. With the old shape in the method it failed: at 10:52 the collectors were handed the slot from 10:05, before the sleep. A second test pins that the grid is unchanged when there is no sleep.NewerSchemaFileGuardTests(2 tests) still pass; with the guard neutralised the refusal test fails.AgedDatabaseMigrationTests,PerfmonCounterTypeTests,DeltaFamilyIntervalColumnTests,QsCaptureModeRungTests,TimeHonestyRungTests,EntraCredentialSelectionOrderingTests,AnalysisBudgetTests,FindingsRetentionHorizonPinTestsandSharedBaselineCacheTestspass.Lite.Testsbuilds with 0 warnings.Lite.Testsrun after merging dev: 5604 tests, 1 failed.QueryWindowTruncationTests.SetWindowTruncatedBanner_Truncated_ShowsSinceEffectiveStartexpectedShowing since 2026-01-15 08:30:00and got04:30:00(a 4-hour shift, so a time-zone setting left over from an earlier test in the run). The class is not touched by this PR, and it passes alone (11 of 11).TimeHonestyRungTests.TheLiteTwinIsSchemaV63_AndCarriesBothColumnsandQsCaptureModeRouteKnobToastRungTests.LiteTwinsOnlyTheCaptureModes_AtSchemaV64) now pin the versionedAddedColumnsentries, for example(63, "cpu_utilization_stats", "sample_time_utc", "TIMESTAMP"), and pin that the v63 and v64 steps each callAddMissingColumnsAsync(connection, AddedColumnsForVersion(N))for their own version, with each step cut at the nextif (fromVersion <so a later step's call cannot satisfy it. RED: seeding each defect inDuckDbInitializer.csin turn failed the matching test and left the other class passing. Dropping a v63 entry failed the v63 test, dropping a v64 entry failed the v64 test, and pointing the v63 step at version 62 failed the v63 test (also when a later step called version 63). With the file restored,TimeHonestyRungTests(8 tests),QsCaptureModeRouteKnobToastRungTests(6) andBuiltinAlertPersistenceRungTests(11) pass, and the fullDarling.Testsrun after merging dev has 17186 tests, 0 failed, 0 errors, 1123 skipped (the live tests that need a test server, which was not configured) and 1 not run.Things to check: the every-start pass runs only when the file's stamp was above 0 before the start (a file with no
schema_versionreads as fresh and gets its columns fromCREATE TABLE IF NOT EXISTS);CreateArchiveViewsCoreAsyncis not touched.CHANGELOG
SECTION: Fixed
ENTRY:
REF:
[Lite: an older app stops collecting for 13 tables on a newer data file, and a failed schema step is stamped as done #4727]: Lite: an older app stops collecting for 13 tables on a newer data file, and a failed schema step is stamped as done #4727
[Lite: after sleep and resume, collectors due before the sleep run twice a minute apart #4728]: Lite: after sleep and resume, collectors due before the sleep run twice a minute apart #4728