/************************************************************************************
*
* 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;
}
}
}