From cfd7d6e445b4265b2d45d5e7563f54ed9ee661f1 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Thu, 12 Mar 2026 18:42:42 -0500 Subject: [PATCH] Fix installer dropping database on every upgrade (#538, #539) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 00_uninstall.sql (added in #431 for standalone uninstall) was placed in install/ and picked up by the file glob, making it the first script executed on every install — including upgrades. This silently dropped the PerformanceMonitor database before recreating it empty. Four fixes: 1. Exclude 00_* from the install file list (both CLI and GUI), matching the existing 97_/99_ exclusion pattern. 2. Abort installation when any upgrade script fails instead of falling through to the full install path over a partially-upgraded database. New CLI exit code 8 (UpgradesFailed). 3. Version detection fallback: when the database exists but installation_history has no SUCCESS rows (prior GUI bug), return "1.0.0" so all idempotent upgrades are attempted rather than treating it as a fresh install. 4. Increase upgrade script timeout from 5 minutes to 1 hour for data migrations on large tables (compress_query_stats on 240GB+ DBs). Co-Authored-By: Claude Opus 4.6 --- Installer/Program.cs | 34 +++++++++++++++++--- InstallerGui/MainWindow.xaml.cs | 10 ++++++ InstallerGui/Services/InstallationService.cs | 15 ++++++--- 3 files changed, 51 insertions(+), 8 deletions(-) diff --git a/Installer/Program.cs b/Installer/Program.cs index 74710e41..1acfcbf5 100644 --- a/Installer/Program.cs +++ b/Installer/Program.cs @@ -103,6 +103,7 @@ SQL command timeout constants (in seconds) private const int ShortTimeoutSeconds = 60; // Quick operations (cleanup, queries) private const int MediumTimeoutSeconds = 120; // Dependency installation private const int LongTimeoutSeconds = 300; // SQL file execution (5 minutes) + private const int UpgradeTimeoutSeconds = 3600; // Upgrade data migrations (1 hour, large tables) /* Exit codes for granular error reporting @@ -117,6 +118,7 @@ private static class ExitCodes public const int VersionCheckFailed = 5; public const int SqlFilesNotFound = 6; public const int UninstallFailed = 7; + public const int UpgradesFailed = 8; } static async Task Main(string[] args) @@ -523,8 +525,9 @@ Search current directory and up to 5 parent directories string fileName = Path.GetFileName(f); if (!SqlFileNamePattern.IsMatch(fileName)) return false; - /*Exclude test and troubleshooting scripts from main install*/ - if (fileName.StartsWith("97_", StringComparison.Ordinal) || + /*Exclude uninstall, test, and troubleshooting scripts from main install*/ + if (fileName.StartsWith("00_", StringComparison.Ordinal) || + fileName.StartsWith("97_", StringComparison.Ordinal) || fileName.StartsWith("99_", StringComparison.Ordinal)) return false; return true; @@ -699,6 +702,21 @@ Traces are server-level and persist after database drops Console.WriteLine(); Console.WriteLine($"Upgrades complete: {upgradeSuccessCount} succeeded, {upgradeFailureCount} failed"); + + /*Abort if any upgrade scripts failed — proceeding would reinstall over a partially-upgraded database*/ + if (upgradeFailureCount > 0) + { + Console.WriteLine(); + Console.WriteLine("================================================================================"); + Console.WriteLine("Installation aborted: upgrade scripts must succeed before installation can proceed."); + Console.WriteLine("Fix the errors above and re-run the installer."); + Console.WriteLine("================================================================================"); + if (!automatedMode) + { + WaitForExit(); + } + return ExitCodes.UpgradesFailed; + } } else { @@ -1332,7 +1350,15 @@ FROM PerformanceMonitor.config.installation_history return version.ToString(); } - return null; + /* + Fallback: database and history table exist but no SUCCESS rows. + This can happen if a prior GUI install didn't write history (#538/#539). + Return "1.0.0" so all idempotent upgrade scripts are attempted + rather than treating this as a fresh install (which would drop the database). + */ + Console.WriteLine("Warning: PerformanceMonitor database exists but installation_history has no records."); + Console.WriteLine("Treating as v1.0.0 to apply all available upgrades."); + return "1.0.0"; } } catch (SqlException ex) @@ -1480,7 +1506,7 @@ Execute an upgrade folder using (var cmd = new SqlCommand(trimmedBatch, connection)) { - cmd.CommandTimeout = LongTimeoutSeconds; + cmd.CommandTimeout = UpgradeTimeoutSeconds; try { await cmd.ExecuteNonQueryAsync().ConfigureAwait(false); diff --git a/InstallerGui/MainWindow.xaml.cs b/InstallerGui/MainWindow.xaml.cs index d4ae570e..ee0d48b2 100644 --- a/InstallerGui/MainWindow.xaml.cs +++ b/InstallerGui/MainWindow.xaml.cs @@ -408,6 +408,16 @@ private async void Install_Click(object sender, RoutedEventArgs e) upgradeFailure == 0 ? "Success" : "Warning"); LogMessage("", "Info"); } + + /*Abort if any upgrade scripts failed — proceeding would reinstall over a partially-upgraded database*/ + if (upgradeFailure > 0) + { + LogMessage("", "Info"); + LogMessage("Installation aborted: upgrade scripts must succeed before installation can proceed.", "Error"); + LogMessage("Fix the errors above and re-run the installer.", "Error"); + SetUIState(installing: false); + return; + } } /* diff --git a/InstallerGui/Services/InstallationService.cs b/InstallerGui/Services/InstallationService.cs index e95c07d0..fe4e7c95 100644 --- a/InstallerGui/Services/InstallationService.cs +++ b/InstallerGui/Services/InstallationService.cs @@ -238,8 +238,9 @@ public static (string? SqlDirectory, string? MonitorRootDirectory, List /*Match numbered SQL files but exclude 97 (tests) and 99 (troubleshooting)*/ if (!SqlFilePattern.IsMatch(fileName)) return false; - /*Exclude test and troubleshooting scripts from main install*/ - if (fileName.StartsWith("97_", StringComparison.Ordinal) || + /*Exclude uninstall, test, and troubleshooting scripts from main install*/ + if (fileName.StartsWith("00_", StringComparison.Ordinal) || + fileName.StartsWith("97_", StringComparison.Ordinal) || fileName.StartsWith("99_", StringComparison.Ordinal)) return false; return true; @@ -1113,7 +1114,13 @@ FROM PerformanceMonitor.config.installation_history return version.ToString(); } - return null; + /* + Fallback: database and history table exist but no SUCCESS rows. + This can happen if a prior GUI install didn't write history (#538/#539). + Return "1.0.0" so all idempotent upgrade scripts are attempted + rather than treating this as a fresh install (which would drop the database). + */ + return "1.0.0"; } catch (SqlException) { @@ -1272,7 +1279,7 @@ Parse versions and filter to only applicable upgrades continue; using var cmd = new SqlCommand(trimmedBatch, connection); - cmd.CommandTimeout = 300; + cmd.CommandTimeout = 3600; /*1 hour — upgrade migrations on large tables need extended time*/ try {