using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Text.RegularExpressions;
///
/// 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,
///
namespace Sdaleo.Internal
{
///
///
///
public enum MySQLCommandType
{
PRE_CREATE,
CREATE,
POST_CREATE,
PRE_ALTER,
ALTER,
POST_ALTER,
PRE_DROP,
DROP,
POST_DROP,
PRE_RENAME,
RENAME,
POST_RENAME,
}
///
/// 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.
///
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;
///
/// Returns true if there are Lines between CommandStart and CommandEnd,
/// this indicates that it contains sql in between
///
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
///
/// Used to Parse a SQLCommand from an SQL Script
///
/// Specifies the line of code where the Command is located --# [Command] [Version] [Arg1] [Arg2] ... [Arg3]
/// The String of Line_CommandStart, that contains all the Command Values the --# [Command] [Version] [Arg1] [Arg2] ... [Arg3] string
/// Specifies the line of code where the Command ends --#
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;
}
///
/// Parses a CommmandStartString and fills the values for this class
///
/// a line that contains a CommandStartString
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 = "--#";
///
/// Returns true if the passed in line is a MySQLCommandStartLine
///
/// line to check
/// true if MySQLCommandStartLine, false otherwise
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;
}
///
/// Returns ture if the passed in line is a MySQLCommandEndLine
///
/// line to check
/// true if MySQLCommandEndLine, false otherwise
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
}
///
///
///
public enum MySQLIdentifierType
{
SCRIPTID,
SCRIPTVERSION,
}
///
/// Custom SQL Identifiers that are part of an SQL Script.
/// They start as follows:
/// --$ [Identifier] [Value]
///
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
///
/// Used to Parse a SQLCommand from an SQL Script
///
/// Specifies the line of code where the Identifier is located --$ [Identifier] [Value]
/// The String of Line_IdentifierString, that contains all the Identifier Values the --$ [Identifier] [Value] string
internal MySQLIdentifier(uint Line_IdentifierStart, string Line_IdentifierString)
{
this.Line_IdentifierStart = Line_IdentifierStart;
ParseLine_IndentifierStartString(Line_IdentifierString);
}
///
/// Parses a LineIdentifierString and fills the values for this class
///
/// a line that contains a LineIdentifierString
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 = "--$";
///
/// Returns true if the passed in line is a MySQLIdentifier
///
/// line to check
/// true if MySQLIdentifier, false otherwise
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
}
///
/// Helper class that parses an SQL Script for Commands and Identifiers,
/// for processing.
///
internal static class SQLParser
{
///
/// Iterates thru the Identifiers and returns the value of the first identifier that matches the identity.
///
/// the value of the first matched identity, String.Empty if not found
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;
}
///
/// Parses an SQLScript for MySQLCommand Objects
///
/// a string array from an sql script
/// all MySQLCommand objects found, if any, otherwise, empty []
/// all MySQLIdentifier objects found, if any, otherwise, empty []
internal static void ParseSQLScriptForMySQLCommandsAndIdentifiers(string[] sqlscript, out MySQLCommand[] commands, out MySQLIdentifier[] identifiers)
{
// Result set
commands = null;
identifiers = null;
List CommandsFound = new List();
List IdentifiersFound = new List();
// 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 sqlScriptBlock = new List();
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 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();
}
///
/// 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.
///
/// an unprocessed Script block from an SQL Script
/// a ADO.Net Friend sql script block that can be executed
internal static string[] GetExecutableSqlTextFromSQLScriptBlock(string[] sqlscriptBlock)
{
bool bIsInCommentMode = false;
StringBuilder sbSQLLines = new StringBuilder();
List sbSQLBrokenOut = new List();
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();
}
}
}