Generated: 2026-06-21
Commits 823c9dd..HEAD (10 commits), verified against DB-1 and DB-2 goals.
Commits inspected:
- 670293e DB-1 Stage 0: Surface write failures with shutdown/restart dialog
- ee98942 DB-1 Stage 1: FileDatabaseUpdate.cs
- aac849d DB-1 Stage 2: non-detection FileDatabase.cs call sites
- 38303a7 DB-1 Stage 3: detection/recognition FileDatabase.cs call sites
- 07ac2b9 DB-1 Stage 4: CommonDatabase.cs
- a84b417 DB-1 Stage 5: retry BUSY/LOCKED in ExecuteNonQueryWithRollbackCore
- 5f9899b DB-1 Stage 6: [MustUseReturnValue] safety net on SQLiteWrapper write methods
- ebad1fa Resharper fixes
- a295ce0 DB-2 Pre-stage: SqlErrorState and OnReadError scaffolding
- 7d7a234 DB-2 Stage A: retry loops in all 7 read methods
- e1727fd DB-2 Stage B: ResetAllReadErrorState()
- 1acd154 DB-2 Stage C+D: read-error notice dialog, checkpoints, Debug.Fail cleanup
DB-1 (write failure handling) is substantially complete. All primary write call sites in FileDatabaseUpdate.cs, FileDatabase.cs, and CommonDatabase.cs correctly check .Success and call TimelapseNeedsToShutDownDataWriteErrorDialog. The [MustUseReturnValue] attribute is on all 14 public write method overloads in SQLiteWrapper.cs. No old-style new SqlOperationResult { } object initializers remain.
DB-2 (read failure handling) is substantially complete. All 7 read methods have the 3-attempt retry loop with _errorFired gate. OnReadError is wired in TimelapseWindow.xaml.cs and calls both SqlErrorState.TryRecord and TimelapseReadErrorNoticeDialog followed by ResetAllReadErrorState(). No #if !DEBUG guards remain. Stage C checkpoints are in place.
However, several genuine issues were found:
- Medium:
SetColumnToACommonValueis called inCommonDatabase.cstwice without checking the returnedSqlOperationResult, and has no[MustUseReturnValue]attribute. - Medium: Multiple
SchemaDeleteColumn,SchemaAddColumnToEndOfTable,SchemaRenameColumn, andSchemaAlterTableWithNewColumnDefinitionscalls inFileDatabase.csdiscard theirSqlOperationResultreturn values silently. - Medium: Multiple
DropTablecalls inFileDatabase.csdiscard theirSqlOperationResultwithout error handling. - Low:
SyncControlsToDatabase()inCommonDatabase.csreturnsfalseon write failure but does not call the shutdown dialog. - Low:
TimelapseMenuFile.csline 127 has a danglingif (SqlErrorState.HasError)statement with the body commented out — syntactically valid but semantically inert (equivalent to a no-op). - Low: The
RecognitionSelector.xaml.cscomment on_ =lines says "DB-2: fails on read error; handled in DB-2 plan" but these are write operations being discarded, which is technically a DB-1 issue (temp-table writes treated as survivable). The comment is conceptually misleading. - Info:
OnReadErroris assigned with=(not+=), which is intentional but means any secondary window that reassigns it would silently replace the handler.
Severity: Medium
File: src\Timelapse\Database\CommonDatabase.cs:1704 and :1724
Finding: Database.SetColumnToACommonValue(...) is called twice but the returned SqlOperationResult is completely discarded (no _ =, no .Success check, no error dialog). The method returns an SqlOperationResult from ExecuteNonQueryWithRollback, so a write failure goes silently undetected. Additionally, SetColumnToACommonValue on SQLiteWrapper.cs at line 458 has no [MustUseReturnValue] attribute, so the compiler/ReSharper does not warn at call sites.
// CommonDatabase.cs:1704
Database.SetColumnToACommonValue(DBTables.TemplateInfo, DatabaseColumn.VersionCompatibility, versionNumber);
// CommonDatabase.cs:1724
Database.SetColumnToACommonValue(DBTables.TemplateInfo, DatabaseColumn.Standard, standard);Recommendation: Either (a) add [MustUseReturnValue] to SetColumnToACommonValue and check .Success at both call sites with the shutdown dialog, or (b) document these as survivable writes and add _ = with a comment. These update the template info table (TemplateInfo), so failures affect .tdb files — isDDBfile should be false at these sites (or computed from FilePath).
Severity: Medium
File: src\Timelapse\Database\FileDatabase.cs (multiple lines)
Finding: The following schema-altering calls discard their SqlOperationResult entirely — no _ =, no .Success check, no error dialog, no comment:
- Line 400:
Database.SchemaDeleteColumn(DBTables.FileData, dataLabel); - Line 407:
Database.SchemaDeleteColumn(DBTables.Markers, dataLabel); - Line 451:
Database.SchemaDeleteColumn(tableName, dataLabel); - Line 476:
Database.SchemaAddColumnToEndOfTable(DBTables.FileData, columnDefinition); - Line 481:
Database.SchemaAddColumnToEndOfTable(DBTables.Markers, markerColumnDefinition); - Line 495-497:
Database.SchemaAddColumnToEndOfTable(MetadataComposeTableNameFromLevel(level), columnDefinition); - Line 526:
Database.SchemaRenameColumn(DBTables.FileData, dataLabelToRename.Key, dataLabelToRename.Value); - Line 533:
Database.SchemaRenameColumn(DBTables.Markers, ...); - Line 545-547:
Database.SchemaRenameColumn(MetadataComposeTableNameFromLevel(level), ...); - Line 786:
Database.SchemaAlterTableWithNewColumnDefinitions(DBTables.FileData, columnDefinitions); - Line 3284:
this.Database.SchemaAddColumnToEndOfTable(DBTables.Detections, ...); - Line 3289:
this.Database.SchemaAddColumnToEndOfTable(DBTables.Detections, ...); - Line 3295:
this.Database.SchemaAddColumnToEndOfTable(DBTables.ClassificationCategories, ...);
And in CommonDatabase.cs:
- Line 1874:
database.SchemaAddColumnToEndOfTable(DBTables.Template, scd); - Line 1899:
database.SchemaRenameColumn(DBTables.TemplateInfo, "VersionCompatability", ...); - Line 1930:
database.SchemaAddColumnToEndOfTable(DBTables.TemplateInfo, scd); - Line 1951:
database.SchemaAddColumnToEndOfTable(DBTables.TemplateInfo, scd); - Line 1972:
database.SchemaAddColumnToEndOfTable(DBTables.ImageSet, scd); - Line 1993:
database.SchemaAddColumnToEndOfTable(DBTables.ImageSet, scd);
Evidence: These methods all return SqlOperationResult from SQLiteWrapper.cs. The methods SchemaDeleteColumn, SchemaAddColumnToEndOfTable, SchemaRenameColumn, and SchemaAlterTableWithNewColumnDefinitions all have try/catch blocks that return SqlOperationResult.Fail(...) on exception — so failures exist but are silently ignored by callers.
Recommendation: Decide whether schema alteration failures are fatal (requiring shutdown dialog) or survivable. If fatal, add .Success checks and shutdown dialogs. If survivable, add _ = with a comment and consider whether [MustUseReturnValue] should be absent on these methods. Note: none of these schema methods carry [MustUseReturnValue], which is consistent with them not being in the original 14-method list, but the gap leaves DB-1 incomplete for this class of write.
Severity: Medium
File: src\Timelapse\Database\FileDatabase.cs (multiple lines)
Finding: DropTable returns SqlOperationResult and is called in at least nine places, none of which check .Success:
- Line 339:
Database.DropTable(DBTables.Template); - Line 344:
Database.DropTable(DBTables.MetadataTemplate); - Line 345:
Database.DropTable(DBTables.MetadataInfo); - Line 443:
Database.DropTable(tableName); - Lines 2790-2794: Five detection-table drops
- Line 3309:
this.Database.DropTable(DBTables.Classifications); - Line 3350:
this.Database.DropTable(DBTables.Classifications);
Vacuum() is also called without result checks at FileDatabase.cs:3351, DeleteImages.xaml.cs:404, and FileDatabaseResetIdAndVacuum.cs:67.
Evidence: DropTable(string tableName) is a public method at SQLiteWrapper.cs:1690 that returns SqlOperationResult. Vacuum() at line 1725 also returns SqlOperationResult. Neither has [MustUseReturnValue].
Recommendation: DropTable and Vacuum failures are arguably recoverable (the table still exists; the vacuum can be retried), so discarding the result may be intentional. Add _ = Database.DropTable(...) with a comment at each call site to make the intentional discard explicit and prevent future confusion. If a drop failure during schema migration (e.g., DBTables.Template) is actually fatal, add the shutdown dialog.
Severity: Low
File: src\Timelapse\Database\CommonDatabase.cs:831-834
Finding: SyncControlsToDatabase() is a private method called from ReorderControlsInDatabase(). On write failure it returns false instead of calling TimelapseNeedsToShutDownDataWriteErrorDialog. The caller at line 976 simply propagates the bool return to its own caller but no shutdown dialog is shown for what is a template/database write error.
if (!Database.Update(DBTables.Template, columnsTuplesWithWhereList).Success)
{
return false; // ← no dialog shown
}Recommendation: Either propagate to a caller that shows the shutdown dialog, or call TimelapseNeedsToShutDownDataWriteErrorDialog here. Check isDDBfile from this.FilePath?.EndsWith(".ddb", ...).
Severity: Low
File: src\Timelapse\TimelapseMenuCallbacks\TimelapseMenuFile.cs:127-134
Finding: Lines 127–134 contain:
if (SqlErrorState.HasError)
//{
// SqlOperationResult.GenerateExceptionDialog(SqlErrorState.SqlOperationResult, "MenuItemLoadImages_ClickAsync");
// SqlErrorState.Reset();
//}
// Add it to the list, as its originally invalid, but the user was asked to update it
// So its likely ok now.
return;The if has its body commented out. The return; on line 134 is not inside the if block — it is the next statement, unconditionally executed whenever Dialogs.DialogIsFileValid returns false. This is syntactically valid C# (an if-statement with a single empty no-op statement), but:
- The Stage C checkpoint pattern (
if (SqlErrorState.HasError) { SQLiteWrapper.ResetAllReadErrorState(); }) is NOT present here. - If the intent was to add a Stage C checkpoint, the implementation is incomplete — the commented-out code uses
GenerateExceptionDialog(the wrong approach for Stage C), andResetAllReadErrorState()is never called at this location.
Recommendation: Either (a) add a proper Stage C checkpoint — if (SqlErrorState.HasError) { SQLiteWrapper.ResetAllReadErrorState(); } — before the return;, or (b) remove the dead if statement and the stale comment entirely.
Severity: Low
File: src\Timelapse\Controls\RecognitionSelector.xaml.cs:419,422,437,449
Finding: The four _ = Database.Database.ExecuteNonQueryWithRollback(queryN); calls carry the comment // DB-2: fails on read error; handled in DB-2 plan. These are write operations (creating temp tables and indexes). DB-2 is the read-failure plan; DB-1 is the write-failure plan. The comments misidentify the category.
Additionally, if a temp-table create fails, the subsequent queries that depend on those tables will silently produce wrong results (e.g., empty or incorrect recognition counts). Whether this is survivable is a design question, but the comment gives a false impression that DB-2 handles the failure, when DB-2 only applies to read operations.
Recommendation: Correct the comments. If the intent is that these writes are survivable (temp tables can fail gracefully), document that explicitly: // Intentional: temp-table create failure is survivable; downstream queries return empty results.
Severity: Low / Design note
File: src\Timelapse\TimelapseWindow.xaml.cs:165-170
Finding: The OnReadError handler calls ResetAllReadErrorState() immediately after showing the dialog. This means _errorFired is reset to 0 as soon as the dialog is dismissed. If a second read error fires while the user is reading the first dialog, the _errorFired gate has already allowed the first dialog through; since the gate is still 1 during the dialog, the second error would be gated out. But once the user clicks OK and ResetAllReadErrorState() runs, a third subsequent error would fire a new dialog immediately, which is the intended behavior.
However, there is a subtle interaction: SqlErrorState.TryRecord and ResetAllReadErrorState() are called within the same lambda, but SqlErrorState.TryRecord records the first error only, while ResetAllReadErrorState() clears both _errorFired and SqlErrorState. If TryRecord returns false (a second error arrived on a race), the dialog is still shown (because _errorFired was already 0 from the first clear) with the second result. This is acceptable behavior but worth noting.
Recommendation: No change required — the current behavior is correct. Document the intentional order (TryRecord → show dialog → reset) with a comment noting the race is harmless.
Severity: Info
File: src\Timelapse\TimelapseWindow.xaml.cs:165
Finding: SQLiteWrapper.OnReadError = (context, sqlOperationResult) => { ... }; is an assignment, not a += subscription. This is intentional for a single-subscriber pattern. If a second window or component assigned OnReadError = ..., the first handler would be silently replaced. Since OnReadError is static, it is shared across all SQLiteWrapper instances.
Recommendation: Consider renaming OnReadError to make its single-subscriber nature explicit, or add a guard in the setter. Currently acceptable because only TimelapseWindow's constructor sets it.
Severity: Info
File: src\Timelapse\Database\SQLiteWrapper.cs
Finding: The following public methods return SqlOperationResult but are not decorated with [MustUseReturnValue], meaning ReSharper and the compiler will not warn when their return values are discarded:
SetColumnToACommonValue(line 458)TrimWhitespace(line 469) — unused currentlyUpdateParticularColumnValuesWithNewValues(line 490) — used inFileDatabase.cs, checked thereChangeNullToEmptyString(line 506) — unused currentlySchemaRenameTable(line 1200)SchemaAlterTableWithNewColumnDefinitions(line 1223)SchemaAddColumnToTable(line 1371)SchemaDeleteColumn(line 1432)SchemaRenameColumn(line 1482)SchemaAlterColumn(line 1494)SchemaAddColumnToEndOfTable(line 1358)DropTable(line 1690)Vacuum(line 1725)CreateTable(line 104)
The 14 primary write methods (Insert, Update, Delete, UpsertRow, etc.) all have [MustUseReturnValue]. The schema-manipulation and utility write methods do not.
Recommendation: Add [MustUseReturnValue] to at minimum SetColumnToACommonValue and SchemaAddColumnToEndOfTable since they are actively called without result checks. The others (DropTable, Vacuum, schema-alter) could be added too for completeness, with intentional discards marked _ =.
Severity: Info
File: src\Timelapse\TimelapsePartialClasses\TimelapseImageSetLoading.cs:398-401
Finding: The Stage C checkpoint at line 398 is correctly placed immediately before return new(true, string.Empty) and runs after OnFolderLoadingCompleteAsync. This covers the image-set-load pipeline as intended.
However, the checkpoint occurs after TryBeginImageFolderLoad (line 389-392) only in the else branch. The if (importImagesAsNewDDBFile) path calls TryBeginImageFolderLoad and returns early (line 391: return new(false, fileDatabaseFilePath)) on failure without a Stage C checkpoint. Read errors during initial image folder load are therefore not reset in that path.
Recommendation: Add if (SqlErrorState.HasError) { SQLiteWrapper.ResetAllReadErrorState(); } before the return new(false, fileDatabaseFilePath) at line 391, so the checkpoint fires regardless of which branch exits.
Severity: Info
File: src\Timelapse\Database\SQLiteWrapper.cs:1160-1190
Finding: The private methods CopyAllValuesFromTable and CopyAllValuesBetweenTables (lines 1160-1190) execute command.ExecuteNonQuery() directly on a raw SQLiteCommand without any error handling or retry. These are called inside SchemaAlterTableWithNewColumnDefinitions, SchemaAddColumnToTable, SchemaDeleteColumn, and SchemaAlterColumn. A write failure here bubbles up through the outer try/catch in those methods and is caught, so the outer SqlOperationResult.Fail is returned — but see Finding-02, those results are discarded by callers.
Similarly, SchemaRenameTable (private, line 1678), SchemaAddColumnToEndOfTable (private overload, line 1628), DropTable (private overload, line 1705), and PragmaSetForeignKeys (line 1821) all call command.ExecuteNonQuery() without try/catch. These are only called from within methods that have their own outer try/catch, so failures propagate to the outer handler.
Recommendation: No change to these private helpers is required — they correctly rely on their callers' outer try/catch. This is documented for completeness.
Severity: Low
File: src\Timelapse\Database\MergeDatabases.cs:121-127
Finding:
if (!destinationDdb.ExecuteNonQueryWithRollback(query).Success)
{
Dialogs.TimelapseNeedsToShutDownDataWriteErrorDialog(GlobalReferences.MainWindow,
destinationDdb.FilePath?.EndsWith(".ddb", StringComparison.OrdinalIgnoreCase),
"The problem occurred in CheckoutDatabaseWithRelativePath", destinationDdb.FilePath);
}There is no return after the dialog call. Since TimelapseNeedsToShutDownDataWriteErrorDialog calls Application.Current.Shutdown(), the application will terminate before any following code runs — so the missing return is not a practical bug. However, execution will continue past the if block after the dialog is shown but before Shutdown() fully completes on the dispatcher (Shutdown is asynchronous). This mirrors the same pattern in a few other sites (e.g., FileDatabase.cs:1134, FileDatabase.cs:1657).
Recommendation: Add return; after every TimelapseNeedsToShutDownDataWriteErrorDialog call for clarity and to prevent any post-failure code from executing. The compiler does not know that Shutdown terminates execution, so it will not warn about unreachable code. This is a defensive coding quality issue rather than a runtime bug.
| Call Site | File:Line | Method | isDDBfile | Return checked? | Dialog shown? | Status |
|---|---|---|---|---|---|---|
UpdateFile |
FileDatabaseUpdate.cs:43 | Database.Update |
true (hardcoded) |
✅ | ✅ | ✅ Handled |
UpdateFileAsync |
FileDatabaseUpdate.cs:68 | Database.Update |
true |
✅ | ✅ | ✅ Handled |
UpdateFiles(List) |
FileDatabaseUpdate.cs:152 | Database.Update |
true |
✅ | ✅ | ✅ Handled |
UpdateFiles(ColumnTuplesWithWhere) |
FileDatabaseUpdate.cs:161 | Database.Update |
true |
✅ | ✅ | ✅ Handled |
UpdateFiles(ColumnTuple) |
FileDatabaseUpdate.cs:169 | Database.Update |
true |
✅ | ✅ | ✅ Handled |
UpdateFilesCore |
FileDatabaseUpdate.cs:291 | Database.Update |
true |
✅ | ✅ | ✅ Handled |
UpdateSyncImageSetToDatabase |
FileDatabaseUpdate.cs:314 | Database.Update |
true |
✅ | ✅ | ✅ Handled |
UpdateSyncMarkerToDatabase |
FileDatabaseUpdate.cs:326 | Database.Update |
true |
✅ | ✅ | ✅ Handled |
UpdateMarkers |
FileDatabaseUpdate.cs:341 | Database.Update |
true |
✅ | ✅ | ✅ Handled |
UpdateAdjustedFileTimes |
FileDatabaseUpdate.cs:437 | Database.Update |
true |
✅ | ✅ | ✅ Handled |
UpdateExchangeDayAndMonth |
FileDatabaseUpdate.cs:484 | Database.Update |
true |
✅ | ✅ | ✅ Handled |
UpdateRelativePathByReplacingPrefix |
FileDatabaseUpdate.cs:531 | ExecuteNonQueryWithRollback |
true |
✅ | ✅ | ✅ Handled |
OnDatabaseCreatedAsync (ImageSet) |
FileDatabase.cs:278 | Database.Insert |
true |
✅ | ✅ | ✅ Handled |
OnExistingDatabaseOpenedAsync (MetadataInfo) |
FileDatabase.cs:363 | Database.Update |
true |
✅ | ✅ | ✅ Handled |
OnExistingDatabaseOpenedAsync (Markers) |
FileDatabase.cs:422 | Database.DeleteRows |
true |
✅ | ✅ | ✅ Handled |
OnExistingDatabaseOpenedAsync (MetadataInfo all rows) |
FileDatabase.cs:507 | Database.DeleteAllRowsInTables |
true |
✅ | ✅ | ✅ Handled |
OnExistingDatabaseOpenedAsync (SearchTerms) |
FileDatabase.cs:573 | Database.Update |
true |
✅ | ✅ | ✅ Handled |
RepairClassificationCategoriesIfNeeded |
FileDatabase.cs:636 | ExecuteNonQueryWithRollback |
true |
✅ | ✅ | ✅ Handled |
AddFiles |
FileDatabase.cs:1132 | ExecuteNonQueryWithRollback |
true |
✅ | ✅ | ✅ Handled |
DeleteFilesAndMarkers |
FileDatabase.cs:1351 | ExecuteNonQueryWithRollback |
true |
✅ | ✅ | ✅ Handled |
InsertRows |
FileDatabase.cs:1655 | Database.Insert |
true |
✅ | ✅ | ✅ Handled |
MarkersTryInsertNewMarkerRow |
FileDatabase.cs:1727 | Database.Insert |
true |
✅ | ✅ | ✅ Handled |
MarkersRemoveMarkerRow |
FileDatabase.cs:1778 | Database.Delete |
true |
✅ | ✅ | ✅ Handled |
MetadataTablesAndDatabaseUpsertRow (insert) |
FileDatabase.cs:1886 | Database.Insert |
true |
✅ | ✅ | ✅ Handled |
MetadataTablesAndDatabaseUpsertRow (update) |
FileDatabase.cs:1899 | Database.Update |
true |
✅ | ✅ | ✅ Handled |
MetadataUpdateFolderDataPath |
FileDatabase.cs:1916 | UpdateParticularColumnValues |
true |
✅ | ✅ | ✅ Handled |
InsertDetection |
FileDatabase.cs:2124 | Database.Insert |
true |
✅ | ✅ | ✅ Handled |
InsertDetectionsVideo |
FileDatabase.cs:2132 | Database.Insert |
true |
✅ | ✅ | ✅ Handled |
recognizer import (remove unneeded) |
FileDatabase.cs:2436 | ExecuteNonQueryWithRollback |
true |
✅ | ✅ | ✅ Handled |
TrySetBoundingBoxDisplayThreshold |
FileDatabase.cs:3035 | Database.Update |
true |
✅ | ✅ | ✅ Handled |
UpdateOldStyleRecognitionTables |
FileDatabase.cs:3341 | Database.Update |
true |
✅ | ✅ | ✅ Handled |
AddControlToDataTableAndDatabase |
CommonDatabase.cs:518 | Database.Insert |
FilePath?.EndsWith | ✅ | ✅ | ✅ Handled |
RemoveControlFromDataTableAndDatabase (delete) |
CommonDatabase.cs:565 | Database.DeleteRows |
FilePath?.EndsWith | ✅ | ✅ | ✅ Handled |
RemoveControlFromDataTableAndDatabase (update) |
CommonDatabase.cs:593 | Database.Update |
FilePath?.EndsWith | ✅ | ✅ | ✅ Handled |
SyncControlToDatabase |
CommonDatabase.cs:808 | Database.Update |
FilePath?.EndsWith | ✅ | ✅ | ✅ Handled |
SyncControlsToDatabase (reorder) |
CommonDatabase.cs:831 | Database.Update |
— | ✅ | ❌ | |
SyncControlsToEmptyDatabase |
CommonDatabase.cs:853 | Database.Insert |
FilePath?.EndsWith | ✅ | ✅ | ✅ Handled |
SyncMetadataControlsToEmptyDatabase |
CommonDatabase.cs:879 | Database.Insert |
FilePath?.EndsWith | ✅ | ✅ | ✅ Handled |
SyncMetadataInfoToEmptyDatabase |
CommonDatabase.cs:904 | Database.Insert |
FilePath?.EndsWith | ✅ | ✅ | ✅ Handled |
MetadataAddControlToDataTableAndDatabase |
CommonDatabase.cs:1262 | Database.Insert |
FilePath?.EndsWith | ✅ | ✅ | ✅ Handled |
RemoveMetadataControlFromDataTableAndDatabase (delete) |
CommonDatabase.cs:1306 | Database.DeleteRows |
FilePath?.EndsWith | ✅ | ✅ | ✅ Handled |
RemoveMetadataControlFromDataTableAndDatabase (update) |
CommonDatabase.cs:1342 | Database.Update |
FilePath?.EndsWith | ✅ | ✅ | ✅ Handled |
MetadataDeleteLevelFromDatabase |
CommonDatabase.cs:1369 | ExecuteNonQueryWithRollback |
FilePath?.EndsWith | ✅ | ✅ | ✅ Handled |
MetadataMoveLevelForwards/Backwards |
CommonDatabase.cs:1420 | ExecuteNonQueryWithRollback |
FilePath?.EndsWith | ✅ | ✅ | ✅ Handled |
SyncMetadataControlsToDatabase (single) |
CommonDatabase.cs:1512 | Database.Update |
FilePath?.EndsWith | ✅ | ✅ | ✅ Handled |
SyncMetadataControlsToDatabase (list) |
CommonDatabase.cs:1538 | Database.Update |
FilePath?.EndsWith | ✅ | ✅ | ✅ Handled |
CreateAndPopulateTemplateInfoTable |
CommonDatabase.cs:1680 | database.Insert |
FilePath?.EndsWith | ✅ | ✅ | ✅ Handled |
SetTemplateVersionCompatibility |
CommonDatabase.cs:1704 | SetColumnToACommonValue |
— | ❌ | ❌ | ❌ Missing |
SetTemplateStandard |
CommonDatabase.cs:1724 | SetColumnToACommonValue |
— | ❌ | ❌ | ❌ Missing |
UpsertMetadataInfoTableRow |
CommonDatabase.cs:1748 | Database.UpsertRow |
FilePath?.EndsWith | ✅ | ✅ | ✅ Handled |
PopulateTemplateTableWithStandardControls |
CommonDatabase.cs:1858 | database.Insert |
FilePath?.EndsWith | ✅ | ✅ | ✅ Handled |
AddExportToCSVColumnIfNeeded (Update) |
CommonDatabase.cs:1879 | database.Update |
FilePath?.EndsWith | ✅ | ✅ | ✅ Handled |
AddTemplateInfoTableOrRowIfNeeded |
CommonDatabase.cs:1913 | database.Insert |
FilePath?.EndsWith | ✅ | ✅ | ✅ Handled |
AddStandardToTemplateInfoColumnIfNeeded |
CommonDatabase.cs:1934 | database.Update |
FilePath?.EndsWith | ✅ | ✅ | ✅ Handled |
AddBackwardsCompatibilityToTemplateInfoColumnIfNeeded |
CommonDatabase.cs:1955 | database.Update |
FilePath?.EndsWith | ✅ | ✅ | ✅ Handled |
AddStandardToImageSetColumnIfNeeded |
CommonDatabase.cs:1976 | database.Update |
FilePath?.EndsWith | ✅ | ✅ | ✅ Handled |
AddBackwardsCompatibilityToImageSetColumnIfNeeded |
CommonDatabase.cs:1997 | database.Update |
FilePath?.EndsWith | ✅ | ✅ | ✅ Handled |
CheckoutDatabaseWithRelativePath |
MergeDatabases.cs:121 | ExecuteNonQueryWithRollback |
FilePath?.EndsWith | ✅ | ✅ | ✅ Handled (no return) |
UpdateMetadataTableAndMetadataDatabase |
MetadataDataEntryHandler.cs:710 | Database.Update |
FilePath?.EndsWith | ✅ | ✅ | ✅ Handled |
ResetIDsAndVacuum |
FileDatabaseResetIdAndVacuum.cs:52 | ExecuteNonQueryWithRollback |
— | ✅ | ✅ (GenerateExceptionDialog) | ✅ Handled |
RecognitionSelector temp tables (4×) |
RecognitionSelector.xaml.cs:419,422,437,449 | ExecuteNonQueryWithRollback |
— | _ = (intentional) |
❌ | ℹ️ Intentional-skip (misleading comment) |
IndexCreateIfNotExists (single) |
SQLiteWrapper.cs:155 | ExecuteNonQueryWithRollback |
— | _ = (intentional) |
❌ | ℹ️ Intentional-skip |
IndexCreateIfNotExists (list) |
SQLiteWrapper.cs:168 | ExecuteNonQueryWithRollback |
— | _ = (intentional) |
❌ | ℹ️ Intentional-skip |
IndexDropIfExists |
SQLiteWrapper.cs:142 | ExecuteNonQueryWithRollback |
— | _ = (intentional) |
❌ | ℹ️ Intentional-skip |
SchemaDeleteColumn (FileData) |
FileDatabase.cs:400 | SchemaDeleteColumn |
— | ❌ | ❌ | ❌ Missing |
SchemaDeleteColumn (Markers) |
FileDatabase.cs:407 | SchemaDeleteColumn |
— | ❌ | ❌ | ❌ Missing |
SchemaDeleteColumn (level table) |
FileDatabase.cs:451 | SchemaDeleteColumn |
— | ❌ | ❌ | ❌ Missing |
SchemaAddColumnToEndOfTable (FileData) |
FileDatabase.cs:476 | SchemaAddColumnToEndOfTable |
— | ❌ | ❌ | ❌ Missing |
SchemaAddColumnToEndOfTable (Markers) |
FileDatabase.cs:481 | SchemaAddColumnToEndOfTable |
— | ❌ | ❌ | ❌ Missing |
SchemaAddColumnToEndOfTable (metadata level) |
FileDatabase.cs:495 | SchemaAddColumnToEndOfTable |
— | ❌ | ❌ | ❌ Missing |
SchemaRenameColumn (FileData) |
FileDatabase.cs:526 | SchemaRenameColumn |
— | ❌ | ❌ | ❌ Missing |
SchemaRenameColumn (Markers) |
FileDatabase.cs:533 | SchemaRenameColumn |
— | ❌ | ❌ | ❌ Missing |
SchemaRenameColumn (metadata level) |
FileDatabase.cs:545 | SchemaRenameColumn |
— | ❌ | ❌ | ❌ Missing |
SchemaAlterTableWithNewColumnDefinitions |
FileDatabase.cs:786 | SchemaAlterTableWithNewColumnDefinitions |
— | ❌ | ❌ | ❌ Missing |
SchemaAddColumnToEndOfTable (Detections ×2) |
FileDatabase.cs:3284,3289 | SchemaAddColumnToEndOfTable |
— | ❌ | ❌ | ❌ Missing |
SchemaAddColumnToEndOfTable (ClassificationCat) |
FileDatabase.cs:3295 | SchemaAddColumnToEndOfTable |
— | ❌ | ❌ | ❌ Missing |
DropTable (Template) |
FileDatabase.cs:339 | DropTable |
— | ❌ | ❌ | |
DropTable (MetadataTemplate/Info ×2) |
FileDatabase.cs:344-345 | DropTable |
— | ❌ | ❌ | |
DropTable (level table) |
FileDatabase.cs:443 | DropTable |
— | ❌ | ❌ | |
DropTable (5× detection tables) |
FileDatabase.cs:2790-2794 | DropTable |
— | ❌ | ❌ | |
DropTable (Classifications ×2) |
FileDatabase.cs:3309,3350 | DropTable |
— | ❌ | ❌ | |
Vacuum (×3) |
FileDatabase.cs:3351, DeleteImages.xaml.cs:404, FileDatabaseResetIdAndVacuum.cs:67 | Vacuum |
— | ❌ | ❌ | |
SchemaAddColumnToEndOfTable (×4 in CommonDatabase) |
CommonDatabase.cs:1874,1930,1951,1972,1993 | SchemaAddColumnToEndOfTable |
— | ❌ | ❌ | ❌ Missing |
SchemaRenameColumn |
CommonDatabase.cs:1899 | SchemaRenameColumn |
— | ❌ | ❌ | ❌ Missing |
SetColumnToACommonValue (×2) |
CommonDatabase.cs:1704,1724 | SetColumnToACommonValue |
— | ❌ | ❌ | ❌ Missing |
The 7 wrapped read methods in SQLiteWrapper.cs that have retry + OnReadError coverage:
GetDataTableFromSelectGetDataTableFromSelectAsyncGetDistinctValuesInColumnGetScalarFromSelect(covers all 5 scalar wrapper methods:ScalarGetScalarFromSelectAsInt,ScalarGetScalarFromSelectAsLong,ScalarBoolFromOneOrZero,ScalarGetMaxValueAsLong,ScalarGetFloatValue)SchemaGetColumnsSchemaGetColumnsAndDefaultValuesSchemaIsColumnInTable
Read paths that bypass the 7 methods and have no retry or OnReadError:
-
TableExists/TableExistsAndNotEmpty/TableHasContent(SQLiteWrapper.cs:1755-1784): These callGetDataTableFromSelectorScalarGetScalarFromSelectAsInt, so they do go through the covered read path. No gap here. -
PragmaGetQuickCheck(SQLiteWrapper.cs:1791-1816): Opens its own connection, callsExecuteReader, and catches all exceptions with barecatch { return false; }. It has no retry, noOnReadErrorinvocation, and returns a silentfalseon any failure. This is intentional (it's an integrity-check probe), but it does suppress all errors. -
GetSchema(private) (SQLiteWrapper.cs:1067-1072): Called from private helpersGetSchemaColumnNamesAsList,GetSchemaColumnNamesAsString,GetSchemaColumnDefinitions. These are only called from within methods that have outer try/catch blocks (SchemaGetColumns,SchemaGetColumnsAndDefaultValues,SchemaAlterColumn, etc.), so failures propagate to those outer handlers. No direct gap. -
SchemaRenameTable,SchemaAlterTableWithNewColumnDefinitions,SchemaAlterColumn,SchemaAddColumnToTable,SchemaDeleteColumn: These open their own connections and callGetSchemaColumnNamesAsListinside their try blocks. Any read failure there is caught by the outercatch (Exception exception)and returned asSqlOperationResult.Fail(...). However, callers discard those results (see Finding-02), so failures are silently lost. -
FileDatabaseCountOrSelectFiles.cs: Does not contain any direct write calls. It delegates toGetDataTableFromSelectand scalar methods which are all covered.
Present on all 14 primary public write method overloads:
Insert(×2 overloads) ✅UpsertRow✅Update(×4 overloads) ✅DeleteRows✅Delete✅DeleteAllRowsInTables✅ExecuteNonQueryWithRollback(×4 overloads) ✅
Absent on schema/utility write methods:
SetColumnToACommonValue❌ (called without result check)SchemaAddColumnToEndOfTable❌ (called without result check)SchemaDeleteColumn❌ (called without result check)SchemaRenameColumn❌ (called without result check)SchemaAlterColumn❌ (called without result check)SchemaAlterTableWithNewColumnDefinitions❌ (called without result check)DropTable(public overload) ❌ (called without result check)Vacuum❌ (called without result check)TrimWhitespace❌ (currently unused externally)ChangeNullToEmptyString❌ (currently unused externally)
DB-1: Substantially complete, with gaps. The primary write-failure coverage across FileDatabaseUpdate.cs, FileDatabase.cs, and CommonDatabase.cs is thorough and correct. However, the schema-manipulation methods (SchemaDeleteColumn, SchemaAddColumnToEndOfTable, SchemaRenameColumn, SchemaAlterTableWithNewColumnDefinitions), DropTable, Vacuum, and SetColumnToACommonValue are not covered — their results are discarded silently with no error dialog and no intentional-skip comments. Whether these are considered survivable is a design decision that should be explicitly documented.
DB-2: Complete. All 7 read methods have retry loops, _errorFired gates, and OnReadError invocations using SqlOperationResult.Fail(...). No #if !DEBUG guards remain. ResetAllReadErrorState() is wired correctly. Stage C checkpoints are present at the 5 specified locations, with one gap (Finding-10): the new-image-folder-load path in TimelapseImageSetLoading.cs exits without resetting error state.
Recommended follow-up actions (priority order):
- Decide policy for schema-alter and DropTable/Vacuum failures — either add shutdown dialogs or explicit
_ =discards with comments (Finding-02, Finding-03). - Fix
SetColumnToACommonValuecall sites inCommonDatabase.cs— add[MustUseReturnValue]and check result (Finding-01). - Fix Stage C checkpoint gap in
TimelapseImageSetLoading.cs(Finding-10). - Fix dead-code
if (SqlErrorState.HasError)inTimelapseMenuFile.cs(Finding-05). - Fix missing
returnafter shutdown dialog calls atMergeDatabases.cs:126and other sites that currently fall through (Finding-12). - Correct misleading DB-2 comment in
RecognitionSelector.xaml.cs(Finding-06). - Add shutdown dialog or document intent for
SyncControlsToDatabase()failure (Finding-04).