Files
Umbraco-CMS/umbraco/datalayer/Utility/Installer/DefaultInstallerUtility.cs
Shandem dcdb44975e Fixes upgrade script
[TFS Changeset #65962]
2010-05-05 12:16:00 +00:00

293 lines
9.9 KiB
C#

/************************************************************************************
*
* Umbraco Data Layer
* MIT Licensed work
* ©2008 Ruben Verborgh
*
***********************************************************************************/
using System;
using System.Text.RegularExpressions;
namespace umbraco.DataLayer.Utility.Installer
{
/// <summary>
/// Base class for installers that use an ISqlHelper as data source.
/// </summary>
/// <typeparam name="S">The SQL helper type.</typeparam>
public abstract class DefaultInstallerUtility<S> : BaseUtility<S>, IInstallerUtility where S : ISqlHelper
{
#region Private Fields
/// <summary>The latest available version this installer provides.</summary>
private readonly DatabaseVersion m_LatestVersion;
/// <summary>The currently installed database version, <c>null</c> if undetermined.</summary>
private DatabaseVersion? m_CurrentVersion = null;
/// <summary>Regular expression that finds multiline block comments.</summary>
private static readonly Regex m_findComments = new Regex(@"\/\*.*?\*\/", RegexOptions.Singleline | RegexOptions.Compiled);
#endregion
#region Public Properties
/// <summary>
/// Gets the current data source version.
/// </summary>
/// <value>The current version.</value>
public DatabaseVersion CurrentVersion
{
get
{
if (!m_CurrentVersion.HasValue)
m_CurrentVersion = DetermineCurrentVersion();
return m_CurrentVersion.Value;
}
}
/// <summary>
/// Gets the latest available version.
/// </summary>
/// <value>The latest version.</value>
public DatabaseVersion LatestVersion
{
get { return m_LatestVersion; }
}
/// <summary>
/// Gets a value indicating whether this installer can connect to the data source.
/// </summary>
/// <value>
/// <c>true</c> if the installer can connect; otherwise, <c>false</c>.
/// </value>
public virtual bool CanConnect
{
get { return CurrentVersion != DatabaseVersion.Unavailable; }
}
/// <summary>
/// Gets a value indicating whether the data source is empty and ready for installation.
/// </summary>
/// <value>
/// <c>true</c> if the data source is empty; otherwise, <c>false</c>.
/// </value>
public virtual bool IsEmpty
{
get { return CurrentVersion == DatabaseVersion.None; }
}
/// <summary>
/// Gets a value indicating whether the data source has an up to date version.
/// </summary>
/// <value>
/// <c>true</c> if the data source is up to date; otherwise, <c>false</c>.
/// </value>
public bool IsLatestVersion
{
get { return CurrentVersion == LatestVersion; }
}
/// <summary>
/// Gets a value indicating whether the installer can upgrade the data source.
/// </summary>
/// <value>
/// <c>true</c> if the installer can upgrade the data source; otherwise, <c>false</c>.
/// </value>
/// <remarks>Empty data sources can't be upgraded, just installed.</remarks>
public virtual bool CanUpgrade
{
get { return false; }
}
/// <summary>
/// Gets the version specification for evaluation by DetermineCurrentVersion.
/// Only first matching specification is taken into account.
/// </summary>
/// <value>The version specifications.</value>
protected abstract VersionSpecs[] VersionSpecs { get; }
#endregion
#region Public Constructors
/// <summary>
/// Initializes a new instance of the <see cref="DefaultInstallerUtility&lt;S&gt;"/> class.
/// </summary>
/// <param name="sqlHelper">The SQL helper.</param>
public DefaultInstallerUtility(S sqlHelper)
: this(sqlHelper, DatabaseVersion.Unavailable)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="DefaultInstallerUtility&lt;S&gt;"/> class.
/// </summary>
/// <param name="sqlHelper">The SQL helper.</param>
/// <param name="latestVersion">The latest available version.</param>
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
/// <summary>
/// Installs the latest version into the data source.
/// </summary>
/// <exception cref="System.NotSupportedException">
/// If installing or upgrading is not supported.</exception>
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);
}
/// <summary>
/// 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.
/// </summary>
/// <returns>The current version of the SQL data source</returns>
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;
}
/// <summary>
/// Executes a list of semicolon separated statements.
/// Statements starting with
/// </summary>
/// <param name="statements">The statements.</param>
protected void ExecuteStatements(string statements)
{
if (string.IsNullOrEmpty(statements))
{
throw new ArgumentNullException("statements", "The sql statement to execute is empty. Database version: " + CurrentVersion.ToString());
}
// 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
}
/// <summary>
/// A triple (Field, Table, Version) meaning:
/// if a <c>SELECT</c> statement of <c>Field FROM Table</c> succeeds,
/// the database version is at least <c>Version</c>.
/// </summary>
/// <remarks>
/// This also supports checking for a value in a table.
/// </remarks>
public struct VersionSpecs
{
/// <summary>The name of the field that should exist in order to have at least the specified version.</summary>
public readonly string Field;
/// <summary>The name of the table whose field should exist in order to have at least the specified version.</summary>
public readonly string Table;
/// <summary>
/// The value to look for in the field, if this is left empty it will not be queried.
/// </summary>
public readonly string Value;
/// <summary>The minimum version number of a database that contains the specified field.</summary>
public readonly DatabaseVersion Version;
/// <summary>
/// Initializes a new instance of the <see cref="VersionSpecs"/> struct.
/// </summary>
/// <param name="field">The field.</param>
/// <param name="table">The table.</param>
/// <param name="version">The version.</param>
public VersionSpecs(string field, string table, DatabaseVersion version)
{
Field = field;
Table = table;
Version = version;
Value = "";
}
/// <summary>
/// Initializes a new instance of the <see cref="VersionSpecs"/> struct.
/// </summary>
/// <param name="field">The field.</param>
/// <param name="table">The table.</param>
/// <param name="version">The version.</param>
public VersionSpecs(string field, string table, string value, DatabaseVersion version)
{
Field = field;
Table = table;
Value = value;
Version = version;
}
}
}