diff --git a/Duplicati/Library/SQLiteHelper/DBSchemaUpgrades/DbUpgradesRegistry.cs b/Duplicati/Library/SQLiteHelper/DBSchemaUpgrades/DbUpgradesRegistry.cs new file mode 100644 index 000000000..a0eecc779 --- /dev/null +++ b/Duplicati/Library/SQLiteHelper/DBSchemaUpgrades/DbUpgradesRegistry.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using Duplicati.Library.SQLiteHelper.DBUpdates; + +namespace Duplicati.Library.SQLiteHelper.DBSchemaUpgrades +{ + class DbUpgradesRegistry + { + /// + /// Registry of custom code to be executed along a SQL schema upgrade. The key of the map + /// represents a 1-based version the upgrade code applies to (after performing the update + /// the database will be at that version), the value is an instance of upgrader that + /// implements the hooks code. + /// + public static readonly IDictionary CodeChanges = + new Dictionary() + { + }; + } +} diff --git a/Duplicati/Library/SQLiteHelper/DBSchemaUpgrades/IDbSchemaUpgrade.cs b/Duplicati/Library/SQLiteHelper/DBSchemaUpgrades/IDbSchemaUpgrade.cs new file mode 100644 index 000000000..4033d9242 --- /dev/null +++ b/Duplicati/Library/SQLiteHelper/DBSchemaUpgrades/IDbSchemaUpgrade.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Duplicati.Library.SQLiteHelper.DBUpdates +{ + + // TODO: Consider passing in a transaction and performing everything in one go. + + public interface IDbSchemaUpgrade + { + /// + /// Executed before the textual SQL command is executed. + /// + /// + void BeforeSql(System.Data.IDbConnection connection); + + /// + /// Executed after the textual SQL command is executed. + /// + /// + void AfterSql(System.Data.IDbConnection connection); + } +} diff --git a/Duplicati/Library/SQLiteHelper/DatabaseUpgrader.cs b/Duplicati/Library/SQLiteHelper/DatabaseUpgrader.cs index 18d44788f..9bf4fab50 100644 --- a/Duplicati/Library/SQLiteHelper/DatabaseUpgrader.cs +++ b/Duplicati/Library/SQLiteHelper/DatabaseUpgrader.cs @@ -23,13 +23,24 @@ using System.Text; using System.Data; using System.Text.RegularExpressions; using System.Linq; +using Duplicati.Library.SQLiteHelper.DBUpdates; +using Duplicati.Library.SQLiteHelper.DBSchemaUpgrades; namespace Duplicati.Library.SQLiteHelper { /// /// This class will read embedded files from the given folder. /// Updates should have the form "1.Sample upgrade.sql". - /// When the database schema changes, simply put a new file into the folder. + /// When the database schema changes, simply put a new file into the folder + /// and set it to be emnbedded in the binary. + /// + /// Additionally, it's possible to execute custom code before and after + /// the SQL is executed. To set up a custom upgrade stage, add your + /// code to DbUpgradesRegistry along with the DB version to apply it with. + /// + /// Even if all the DB upgrade code is handled in C#, you still have to add + /// a dummy SQL file to indicate the version ID is already taken. + /// /// Each upgrade file should ONLY upgrade from the previous version. /// If done correctly, a user may be upgrade from the very first version /// to the very latest. @@ -235,7 +246,9 @@ namespace Duplicati.Library.SQLiteHelper } else if (versions.Count > dbversion) { - string backupfile = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(sourcefile), Strings.DatabaseUpgrader.BackupFilenamePrefix + " " + DateTime.Now.ToString("yyyyMMddhhmmss", System.Globalization.CultureInfo.InvariantCulture) + ".sqlite"); + string backupfile = System.IO.Path.Combine( + System.IO.Path.GetDirectoryName(sourcefile), + Strings.DatabaseUpgrader.BackupFilenamePrefix + " " + DateTime.Now.ToString("yyyyMMddhhmmss", System.Globalization.CultureInfo.InvariantCulture) + ".sqlite"); try { @@ -247,6 +260,16 @@ namespace Duplicati.Library.SQLiteHelper for (int i = dbversion; i < versions.Count; i++) { + IDbSchemaUpgrade dbCodeUpgrade; + + // The versions in the registry are 1-based, the loop index is zero based. + bool hookFound = DbUpgradesRegistry.CodeChanges.TryGetValue(i + 1, out dbCodeUpgrade); + + if (hookFound) + { + dbCodeUpgrade.BeforeSql(connection); + } + //TODO: Find a better way to split SQL statements, as there may be embedded semicolons //in the SQL, like "UPDATE x WHERE y = ';';" @@ -261,6 +284,11 @@ namespace Duplicati.Library.SQLiteHelper cmd.ExecuteNonQuery(); } + if (hookFound) + { + dbCodeUpgrade.AfterSql(connection); + } + // after upgrade, db_version should have changed to i + 1. If logic changes, just requery. preparserVars["db_version"] = i + 1; } diff --git a/Duplicati/Library/SQLiteHelper/Duplicati.Library.SQLiteHelper.csproj b/Duplicati/Library/SQLiteHelper/Duplicati.Library.SQLiteHelper.csproj index 9cf37e422..06d9deab3 100644 --- a/Duplicati/Library/SQLiteHelper/Duplicati.Library.SQLiteHelper.csproj +++ b/Duplicati/Library/SQLiteHelper/Duplicati.Library.SQLiteHelper.csproj @@ -38,6 +38,8 @@ + +