463 lines
18 KiB
C#
463 lines
18 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Diagnostics;
|
|
using System.Text.RegularExpressions;
|
|
|
|
/// <summary>
|
|
/// MySQLCommands are embedded in SQL Scripts as follows:
|
|
///--# [Command] [Version] [Arg1] [Arg2] ... [Arg3]
|
|
///--#
|
|
///~ Serves as the Command End Marker!
|
|
///-- Possible Commands are, PRE_CREATE,CREATE,POST_CREATE,PRE_ALTER,ALTER,POST_ALTER,PRE_DROP,DROP,POST_DROP,RENAME
|
|
///-- Command Execution is as follows.
|
|
///
|
|
///-- CREATE must exists.
|
|
///-- For New Install: *Script ID does not exist* -- WILL ALWAYS TAKE ONLY LATEST VERSION for CREATE, runs PRE-CREATEs and POST-CREATEs accordingly
|
|
///-- PRE-CREATE, CREATE, POST-CREATE (If you want nothing done, leave command blank)
|
|
///-- Allow Versions to use *, so that we can say for all [0.*.*] call this PRE-CREATE, or POST-CREATE
|
|
///
|
|
///-- For Update Install: *Script ID exists* - Will check db where to start and execute until to highest version of CREATE was reached
|
|
///-- PRE-DROP, DROP, POST-DROP, PRE-RENAME, RENAME, POST-RENAME, PRE-ALTER, ALTER, POST-ALTER, for each respective version
|
|
///
|
|
///-- Versioning is done as follows:
|
|
/// (Major version).(Minor version).(Revision number)
|
|
///
|
|
/// MySQLIdentifiers are embedded in SQL Scripts as follows:
|
|
///--$ [Identifier] [Value], Identifier structure
|
|
///
|
|
///--$ [ScriptId] [Guid]!
|
|
///-- Each script is versioned on it's own, so that is what we store in the db.
|
|
///-- Give each script a UNIQUE GUID.
|
|
///-- We will then store the ScriptID guid in the versioning table, along side the highest version of the script that most recently ran!,
|
|
///-- ~this way we can always know if something has to be done for each database object,
|
|
/// </summary>
|
|
namespace Sdaleo.Internal
|
|
{
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public enum MySQLCommandType
|
|
{
|
|
PRE_CREATE,
|
|
CREATE,
|
|
POST_CREATE,
|
|
PRE_ALTER,
|
|
ALTER,
|
|
POST_ALTER,
|
|
PRE_DROP,
|
|
DROP,
|
|
POST_DROP,
|
|
PRE_RENAME,
|
|
RENAME,
|
|
POST_RENAME,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Custom SQL Commands that are part of an SQL Script.
|
|
/// They start as follows:
|
|
/// --# [Command] [Version] [Arg1] [Arg2] ... [Arg3]
|
|
///
|
|
/// and end as follows:
|
|
/// --#
|
|
/// ~Serves as the Command End Marker!
|
|
/// Everything in between is SQL.
|
|
/// </summary>
|
|
internal class MySQLCommand
|
|
{
|
|
#region Members N' Properties
|
|
|
|
// Line Specifics
|
|
internal uint Line_CommandStart { get; private set; }
|
|
internal uint Line_CommandEnd { get; private set; }
|
|
internal string[] SQLScriptBlock { get; private set; }
|
|
|
|
// Command Specifics
|
|
internal MySQLCommandType Command { get; private set; }
|
|
internal string Version
|
|
{
|
|
get
|
|
{
|
|
if (_Versioning != null)
|
|
return _Versioning.Version;
|
|
else
|
|
return String.Empty;
|
|
}
|
|
}
|
|
internal Versioning _Versioning = null;
|
|
internal string[] Arguments = null;
|
|
|
|
/// <summary>
|
|
/// Returns true if there are Lines between CommandStart and CommandEnd,
|
|
/// this indicates that it contains sql in between
|
|
/// </summary>
|
|
internal bool ContainsSQLScript
|
|
{
|
|
get
|
|
{
|
|
if (Line_CommandStart >= Line_CommandEnd)
|
|
return false;
|
|
else if ((Line_CommandStart + 1) == Line_CommandEnd)
|
|
return false;
|
|
else if ((SQLScriptBlock != null) && (SQLScriptBlock.Length > 0))
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Internal Execute
|
|
|
|
internal DBError ExecuteScriptBlock(IConnectDb credential)
|
|
{
|
|
DBError dbError = DBError.Create("No Script Block to Execute");
|
|
if ((SQLScriptBlock != null) && (SQLScriptBlock.Length > 0))
|
|
{
|
|
foreach (string sqlLine in SQLScriptBlock)
|
|
{
|
|
DB db = DB.Create(credential);
|
|
dbError = db.ExecuteNonQuery(sqlLine);
|
|
if (dbError.ErrorOccured)
|
|
break;
|
|
}
|
|
}
|
|
return dbError;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Construction
|
|
|
|
/// <summary>
|
|
/// Used to Parse a SQLCommand from an SQL Script
|
|
/// </summary>
|
|
/// <param name="Line_CommandStart">Specifies the line of code where the Command is located --# [Command] [Version] [Arg1] [Arg2] ... [Arg3] </param>
|
|
/// <param name="Line_CommandStartString">The String of Line_CommandStart, that contains all the Command Values the --# [Command] [Version] [Arg1] [Arg2] ... [Arg3] string</param>
|
|
/// <param name="Line_CommandEnd">Specifies the line of code where the Command ends --#</param>
|
|
internal MySQLCommand(uint Line_CommandStart, string Line_CommandStartString, uint Line_CommandEnd, string[] sqlScriptBlock)
|
|
{
|
|
this.Line_CommandStart = Line_CommandStart;
|
|
this.Line_CommandEnd = Line_CommandEnd;
|
|
ParseLine_CommandStartString(Line_CommandStartString);
|
|
this.SQLScriptBlock = sqlScriptBlock;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses a CommmandStartString and fills the values for this class
|
|
/// </summary>
|
|
/// <param name="LineCommandStartString">a line that contains a CommandStartString</param>
|
|
private void ParseLine_CommandStartString(string LineCommandStartString)
|
|
{
|
|
/// --# [Command] [Version] [Arg1] [Arg2] ... [Arg3]
|
|
/// --# [Create] [1.0.0] [Arg1] [Arg2]
|
|
Regex rx = new Regex(@"\[([\w\a-\?\.\*\+]+)\]");
|
|
MatchCollection matches = rx.Matches(LineCommandStartString);
|
|
if (matches.Count < 2)
|
|
throw new ArgumentException("LineIdentifierString is Invalid");
|
|
|
|
// Get the Command and Command Version
|
|
string m1 = matches[0].ToString().Trim(new char[] { '[', ']' });
|
|
string m2 = matches[1].ToString().Trim(new char[] { '[', ']' });
|
|
this.Command = (MySQLCommandType)Enum.Parse(typeof(MySQLCommandType), m1.ToUpper());
|
|
this._Versioning = new Versioning(m2);
|
|
|
|
// Get Optional Arguments
|
|
int nArguments = matches.Count - 2;
|
|
if (nArguments > 0)
|
|
{
|
|
Arguments = new string[nArguments];
|
|
for (int i = 0; i < nArguments; ++i)
|
|
Arguments[i] = matches[2 + i].ToString().Trim(new char[] { '[', ']' });
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Internal Statics
|
|
|
|
internal const string IDENTIFY_SQLSCRIPT_COMMAND = "--#";
|
|
|
|
/// <summary>
|
|
/// Returns true if the passed in line is a MySQLCommandStartLine
|
|
/// </summary>
|
|
/// <param name="line">line to check</param>
|
|
/// <returns>true if MySQLCommandStartLine, false otherwise</returns>
|
|
internal static bool IsMySQLCommandStartLine(string line)
|
|
{
|
|
string lineTrimed = line.Trim();
|
|
if (lineTrimed.StartsWith(IDENTIFY_SQLSCRIPT_COMMAND) && lineTrimed.Length != IDENTIFY_SQLSCRIPT_COMMAND.Length)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns ture if the passed in line is a MySQLCommandEndLine
|
|
/// </summary>
|
|
/// <param name="line">line to check</param>
|
|
/// <returns>true if MySQLCommandEndLine, false otherwise</returns>
|
|
internal static bool IsMySQLCommandEndLine(string line)
|
|
{
|
|
string lineTrimed = line.Trim();
|
|
if (lineTrimed.StartsWith(IDENTIFY_SQLSCRIPT_COMMAND) && lineTrimed.Length == IDENTIFY_SQLSCRIPT_COMMAND.Length)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public enum MySQLIdentifierType
|
|
{
|
|
SCRIPTID,
|
|
SCRIPTVERSION,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Custom SQL Identifiers that are part of an SQL Script.
|
|
/// They start as follows:
|
|
/// --$ [Identifier] [Value]
|
|
/// </summary>
|
|
public class MySQLIdentifier
|
|
{
|
|
#region Members N' Properties
|
|
|
|
// Line Specifics
|
|
internal uint Line_IdentifierStart { get; private set; }
|
|
|
|
// Identifier Specifics
|
|
internal MySQLIdentifierType Identity { get; private set; }
|
|
internal string Value { get; private set; }
|
|
|
|
#endregion
|
|
|
|
#region Construction
|
|
|
|
/// <summary>
|
|
/// Used to Parse a SQLCommand from an SQL Script
|
|
/// </summary>
|
|
/// <param name="Line_Identifier">Specifies the line of code where the Identifier is located --$ [Identifier] [Value] </param>
|
|
/// <param name="Line_IdentifierString">The String of Line_IdentifierString, that contains all the Identifier Values the --$ [Identifier] [Value] string</param>
|
|
internal MySQLIdentifier(uint Line_IdentifierStart, string Line_IdentifierString)
|
|
{
|
|
this.Line_IdentifierStart = Line_IdentifierStart;
|
|
ParseLine_IndentifierStartString(Line_IdentifierString);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses a LineIdentifierString and fills the values for this class
|
|
/// </summary>
|
|
/// <param name="LineIdentifierString">a line that contains a LineIdentifierString</param>
|
|
private void ParseLine_IndentifierStartString(string LineIdentifierString)
|
|
{
|
|
/// --$ [Identifier] [Value]
|
|
/// --$ [ScriptID] [12312-312321-3213]
|
|
Regex rx = new Regex(@"\[([\w\a-\?\.\*\+]+)\]");
|
|
MatchCollection matches = rx.Matches(LineIdentifierString);
|
|
if (matches.Count < 2)
|
|
throw new ArgumentException("LineIdentifierString is Invalid");
|
|
|
|
// Get the Identity and Value
|
|
string m1 = matches[0].ToString().Trim(new char[] { '[', ']' });
|
|
string m2 = matches[1].ToString().Trim(new char[] { '[', ']' });
|
|
this.Identity = (MySQLIdentifierType)Enum.Parse(typeof(MySQLIdentifierType), m1.ToUpper());
|
|
this.Value = m2;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Internal Statics
|
|
|
|
internal const string IDENTIFY_SQLSCRIPT_IDENTIFIER = "--$";
|
|
|
|
/// <summary>
|
|
/// Returns true if the passed in line is a MySQLIdentifier
|
|
/// </summary>
|
|
/// <param name="line">line to check</param>
|
|
/// <returns>true if MySQLIdentifier, false otherwise</returns>
|
|
internal static bool IsMySQLIdentifier(string line)
|
|
{
|
|
string lineTrimed = line.Trim();
|
|
if (lineTrimed.StartsWith(IDENTIFY_SQLSCRIPT_IDENTIFIER) && lineTrimed.Length != IDENTIFY_SQLSCRIPT_IDENTIFIER.Length)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper class that parses an SQL Script for Commands and Identifiers,
|
|
/// for processing.
|
|
/// </summary>
|
|
internal static class SQLParser
|
|
{
|
|
/// <summary>
|
|
/// Iterates thru the Identifiers and returns the value of the first identifier that matches the identity.
|
|
/// </summary>
|
|
/// <returns>the value of the first matched identity, String.Empty if not found</returns>
|
|
internal static string GetValueForFirstFoundMySQLIdentifierType(MySQLIdentifierType Identity, MySQLIdentifier[] identifiers)
|
|
{
|
|
if (identifiers != null && identifiers.Length > 0)
|
|
{
|
|
foreach (MySQLIdentifier identifier in identifiers)
|
|
{
|
|
if (identifier.Identity == Identity)
|
|
return identifier.Value;
|
|
}
|
|
}
|
|
return String.Empty;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses an SQLScript for MySQLCommand Objects
|
|
/// </summary>
|
|
/// <param name="sqlscript">a string array from an sql script</param>
|
|
/// <param name="commands">all MySQLCommand objects found, if any, otherwise, empty []</param>
|
|
/// <param name="identifiers">all MySQLIdentifier objects found, if any, otherwise, empty []</param>
|
|
internal static void ParseSQLScriptForMySQLCommandsAndIdentifiers(string[] sqlscript, out MySQLCommand[] commands, out MySQLIdentifier[] identifiers)
|
|
{
|
|
// Result set
|
|
commands = null;
|
|
identifiers = null;
|
|
List<MySQLCommand> CommandsFound = new List<MySQLCommand>();
|
|
List<MySQLIdentifier> IdentifiersFound = new List<MySQLIdentifier>();
|
|
|
|
// intermediary variables
|
|
int nStartLine = -1;
|
|
string strStartLine = String.Empty;
|
|
|
|
// Iterate thru all lines
|
|
for (int i = 0; i < sqlscript.Length; ++i)
|
|
{
|
|
string curLine = sqlscript[i];
|
|
|
|
/// Parse for
|
|
/// --$ [Identifier] [Value]
|
|
if (MySQLIdentifier.IsMySQLIdentifier(curLine))
|
|
{
|
|
IdentifiersFound.Add(new MySQLIdentifier((uint)i, curLine));
|
|
continue;
|
|
}
|
|
|
|
/// Parse for
|
|
/// --# [Command] [Version] [Arg1] [Arg2] ... [Arg3]
|
|
/// ...
|
|
/// --#
|
|
if (MySQLCommand.IsMySQLCommandStartLine(curLine))
|
|
{
|
|
nStartLine = i;
|
|
strStartLine = curLine;
|
|
continue;
|
|
}
|
|
else if (MySQLCommand.IsMySQLCommandEndLine(curLine))
|
|
{
|
|
// Something is wrong with the script!
|
|
if ((nStartLine == -1) || String.IsNullOrEmpty(strStartLine))
|
|
{
|
|
Debug.Assert(false);
|
|
}
|
|
else
|
|
{
|
|
if (nStartLine == i || nStartLine == i + 1)
|
|
{
|
|
Debug.Assert(false); // There is no SQLCodeBlock! = something is wrong with the script
|
|
nStartLine = -1;
|
|
strStartLine = String.Empty;
|
|
continue;
|
|
}
|
|
|
|
// Create the SQLScriptBlock
|
|
List<String> sqlScriptBlock = new List<String>();
|
|
for (int j = nStartLine; j < i; ++j)
|
|
sqlScriptBlock.Add(sqlscript[j]);
|
|
|
|
// Convert the SQLScriptBlock into Executable Code
|
|
string[] executableSQLCodeBlock = GetExecutableSqlTextFromSQLScriptBlock(sqlScriptBlock.ToArray());
|
|
|
|
// Create the Commands Object that now contains the Executable SQL Code
|
|
CommandsFound.Add(new MySQLCommand((uint)nStartLine, strStartLine, (uint)i, executableSQLCodeBlock));
|
|
nStartLine = -1;
|
|
strStartLine = String.Empty;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Delegate to Propertly Sort the MySQLCommands, First, by Command, then by Version
|
|
System.Comparison<MySQLCommand> comparer = delegate(MySQLCommand x, MySQLCommand y)
|
|
{
|
|
// First Compare the Command,
|
|
int nCompare = x.Command.CompareTo(y.Command);
|
|
|
|
// If equal, compare the versioning
|
|
if(nCompare == 0)
|
|
nCompare = x._Versioning.CompareTo(y._Versioning);
|
|
|
|
return nCompare;
|
|
};
|
|
CommandsFound.Sort(comparer);
|
|
|
|
// Pass out Result set
|
|
commands = CommandsFound.ToArray();
|
|
identifiers = IdentifiersFound.ToArray();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Use this to Parse an SQL Script Block (a block of code, contained inside a MySQLCommand.
|
|
/// It Ignores Comment Lines, and also breaks out the SQL Script by GO Statements.
|
|
/// </summary>
|
|
/// <param name="sqlscriptBlock">an unprocessed Script block from an SQL Script</param>
|
|
/// <returns>a ADO.Net Friend sql script block that can be executed</returns>
|
|
internal static string[] GetExecutableSqlTextFromSQLScriptBlock(string[] sqlscriptBlock)
|
|
{
|
|
|
|
bool bIsInCommentMode = false;
|
|
StringBuilder sbSQLLines = new StringBuilder();
|
|
List<String> sbSQLBrokenOut = new List<String>();
|
|
foreach (string line in sqlscriptBlock)
|
|
{
|
|
// Let's deal with comments, which allows us to put comments in our SQL Scripts
|
|
if (bIsInCommentMode)
|
|
{
|
|
if (line.StartsWith("*/") || line.EndsWith("*/"))
|
|
bIsInCommentMode = false;
|
|
continue;
|
|
}
|
|
else if (line.StartsWith("/*") && !line.EndsWith("*/"))
|
|
{
|
|
bIsInCommentMode = true; // skip lines until end of comment
|
|
continue;
|
|
}
|
|
else if (line.StartsWith("/*") && line.EndsWith("*/"))
|
|
continue; // skip single comment line
|
|
else if (line.StartsWith("--"))
|
|
continue; // skip single comment line
|
|
|
|
// We break out GO stmts to allow execution seperatly
|
|
if ((line.Length == 2) && line.ToUpper().StartsWith("GO"))
|
|
{
|
|
if (sbSQLLines.Length > 0)
|
|
sbSQLBrokenOut.Add(sbSQLLines.ToString());
|
|
sbSQLLines.Remove(0, sbSQLLines.Length); // clear sbSQLLines
|
|
continue;
|
|
}
|
|
|
|
// Ready to use the SQL Line
|
|
sbSQLLines.Append((line + " "));
|
|
}
|
|
|
|
if (sbSQLLines.Length > 0)
|
|
sbSQLBrokenOut.Add(sbSQLLines.ToString());
|
|
|
|
// Return the 'GO' Broken out SQL Script Block
|
|
return sbSQLBrokenOut.ToArray();
|
|
}
|
|
}
|
|
}
|