/************************************************************************************ * * Umbraco Data Layer * MIT Licensed work * ©2008 Ruben Verborgh * ***********************************************************************************/ using System; using System.Text.RegularExpressions; namespace umbraco.DataLayer.Utility.Installer { /// /// Base class for installers that use an ISqlHelper as data source. /// /// The SQL helper type. public abstract class DefaultInstallerUtility : BaseUtility, IInstallerUtility where S : ISqlHelper { #region Private Fields /// The latest available version this installer provides. private readonly DatabaseVersion m_LatestVersion; /// The currently installed database version, null if undetermined. private DatabaseVersion? m_CurrentVersion = null; /// Regular expression that finds multiline block comments. private static readonly Regex m_findComments = new Regex(@"\/\*.*?\*\/", RegexOptions.Singleline | RegexOptions.Compiled); #endregion #region Public Properties /// /// Gets the current data source version. /// /// The current version. public DatabaseVersion CurrentVersion { get { if (!m_CurrentVersion.HasValue) m_CurrentVersion = DetermineCurrentVersion(); return m_CurrentVersion.Value; } } /// /// Gets the latest available version. /// /// The latest version. public DatabaseVersion LatestVersion { get { return m_LatestVersion; } } /// /// Gets a value indicating whether this installer can connect to the data source. /// /// /// true if the installer can connect; otherwise, false. /// public virtual bool CanConnect { get { return CurrentVersion != DatabaseVersion.Unavailable; } } /// /// Gets a value indicating whether the data source is empty and ready for installation. /// /// /// true if the data source is empty; otherwise, false. /// public virtual bool IsEmpty { get { return CurrentVersion == DatabaseVersion.None; } } /// /// Gets a value indicating whether the data source has an up to date version. /// /// /// true if the data source is up to date; otherwise, false. /// public bool IsLatestVersion { get { return CurrentVersion == LatestVersion; } } /// /// Gets a value indicating whether the installer can upgrade the data source. /// /// /// true if the installer can upgrade the data source; otherwise, false. /// /// Empty data sources can't be upgraded, just installed. public virtual bool CanUpgrade { get { return false; } } /// /// Gets the version specification for evaluation by DetermineCurrentVersion. /// Only first matching specification is taken into account. /// /// The version specifications. protected abstract VersionSpecs[] VersionSpecs { get; } #endregion #region Public Constructors /// /// Initializes a new instance of the class. /// /// The SQL helper. public DefaultInstallerUtility(S sqlHelper) : this(sqlHelper, DatabaseVersion.Unavailable) { } /// /// Initializes a new instance of the class. /// /// The SQL helper. /// The latest available version. public DefaultInstallerUtility(S sqlHelper, DatabaseVersion latestVersion) : base(sqlHelper) { m_LatestVersion = latestVersion; } #endregion #region Protected Properties protected abstract string FullInstallSql { get; } protected abstract string UpgradeSql { get; } #endregion #region IUmbracoInstaller Members /// /// Installs the latest version into the data source. /// /// /// If installing or upgrading is not supported. public void Install() { if (IsLatestVersion) return; // installation on empty database if (IsEmpty) { NewInstall(FullInstallSql); } else // upgrade from older version { if (!CanUpgrade) throw new NotSupportedException("Upgrading from this version is not supported."); // execute version specific upgrade set Upgrade(UpgradeSql); } } #endregion #region Protected Methods protected virtual void NewInstall(string sql) { ExecuteStatements(sql); } protected virtual void Upgrade(string sql) { ExecuteStatements(sql); } /// /// Determines the current version of the SQL data source, /// by attempting to select a certain field from a certain table. /// The specifications are retrieved using the VersionSpecs property. /// /// The current version of the SQL data source protected virtual DatabaseVersion DetermineCurrentVersion() { foreach (VersionSpecs v in VersionSpecs) { try { if (!String.IsNullOrEmpty(v.Table) && !String.IsNullOrEmpty(v.Field) && !String.IsNullOrEmpty(v.Value)) { IRecordsReader reader = SqlHelper.ExecuteReader(string.Format("SELECT {0} FROM {1} WHERE {0}={2}", v.Field, v.Table, v.Value)); if (!reader.HasRecords) continue; } else if (String.IsNullOrEmpty(v.Table)) SqlHelper.ExecuteNonQuery(string.Format("SELECT {0}", v.Field)); else SqlHelper.ExecuteNonQuery(string.Format("SELECT {0} FROM {1}", v.Field, v.Table)); return v.Version; } catch { } } return DatabaseVersion.Unavailable; } /// /// Executes a list of semicolon separated statements. /// Statements starting with /// /// The statements. protected void ExecuteStatements(string statements) { // replace block comments by whitespace statements = m_findComments.Replace(statements, " "); // execute all non-empty statements foreach (string statement in statements.Split(";".ToCharArray())) { string rawStatement = statement.Trim(); if (rawStatement.Length > 0) SqlHelper.ExecuteNonQuery(rawStatement); } } #endregion } /// /// A triple (Field, Table, Version) meaning: /// if a SELECT statement of Field FROM Table succeeds, /// the database version is at least Version. /// /// /// This also supports checking for a value in a table. /// public struct VersionSpecs { /// The name of the field that should exist in order to have at least the specified version. public readonly string Field; /// The name of the table whose field should exist in order to have at least the specified version. public readonly string Table; /// /// The value to look for in the field, if this is left empty it will not be queried. /// public readonly string Value; /// The minimum version number of a database that contains the specified field. public readonly DatabaseVersion Version; /// /// Initializes a new instance of the struct. /// /// The field. /// The table. /// The version. public VersionSpecs(string field, string table, DatabaseVersion version) { Field = field; Table = table; Version = version; Value = ""; } /// /// Initializes a new instance of the struct. /// /// The field. /// The table. /// The version. public VersionSpecs(string field, string table, string value, DatabaseVersion version) { Field = field; Table = table; Value = value; Version = version; } } }