/************************************************************************************
*
* Umbraco Data Layer
* MIT Licensed work
* ©2008 Ruben Verborgh
*
***********************************************************************************/
using System.Text;
using System;
namespace umbraco.DataLayer
{
///
/// Object that performs parsing tasks on an SQL command.
///
public class SqlParser
{
/// Original tokens for query token replacement.
private char[] m_SrcTokens;
/// Destination tokens for query token replacement.
private char[] m_DestTokens;
#region Public Properties
///
/// Gets the character strings are surrounded with.
///
/// The string delimiter.
public virtual char StringDelimiter
{
get { return '\''; }
}
///
/// Gets the param character parameters start with.
///
/// The parameter starting token.
public virtual char ParamToken
{
get { return '@'; }
}
#endregion
///
/// Initializes a new instance of the class.
///
public SqlParser()
: this(null, null)
{ }
///
/// Initializes a new instance of the class
/// that performs the specified token translation.
///
/// The original tokens.
/// The new tokens, each token corresponds with the item of
/// srcTokens at the same index.
public SqlParser(char[] srcTokens, char[] destTokens)
{
if ((srcTokens == null && destTokens != null)
|| (srcTokens != null && destTokens == null)
|| (srcTokens != null && destTokens != null && srcTokens.Length != destTokens.Length))
throw new ArgumentException("srcTokens and destTokens dimensions do not match");
m_SrcTokens = srcTokens;
m_DestTokens = destTokens;
}
#region Public Methods
///
/// Parses the query, performing provider specific changes.
///
/// The query.
/// The modified query.
///
/// The default implementation returns the original query.
/// Overriding classes can change this behavior.
///
public virtual string Parse(string query)
{
return query;
}
///
/// Replaces parameters in a query by their values.
///
/// The command text.
/// The parameters.
/// The query, with parameter placeholders replaced by their values.
public virtual string ApplyParameters(string commandText, IParameter[] parameters)
{
// no parameters, just return commandText
if (parameters.Length == 0)
return commandText;
// build string that includes parameters
StringBuilder result = new StringBuilder(commandText.Length);
for (int i = 0; i < commandText.Length; i++)
{
// Is this the start of a parameter name?
if (commandText[i] == ParamToken)
{
/* Read parameter name, from ParamToken till next space or end */
int paramNameStart = i;
// move i past end of parameter name
while (++i < commandText.Length && char.IsLetterOrDigit(commandText[i])) ;
// store parameter name in string
string paramName = commandText.Substring(paramNameStart, i - paramNameStart).ToLower();
/* Get the parameter value */
string paramValue = string.Empty;
// find a parameter with identical name
for (int p = 0; p < parameters.Length && paramValue.Length == 0; p++)
if (parameters[p].ParameterName.ToLower() == paramName)
paramValue = parameters[p].Value.ToString();
/* Append parameter value */
result.Append('\'').Append(paramValue.Replace("'", "''")).Append('\'');
// check we're not at the end of the string
if (i >= commandText.Length)
break;
}
// append the character
result.Append(commandText[i]);
}
// append terminating semicolon
result.Append(';');
return result.ToString();
}
/// Replaces tokens before or after identifiers in a query string.
/// The original query.
/// The query with replaced identifier tokens.
/// Assumes a correct query.
public virtual string ReplaceIdentifierTokens(string query)
{
if (m_SrcTokens == null || m_DestTokens == null || m_SrcTokens.Length == 0 || m_DestTokens.Length == 0)
return query;
// find first occurrence of an originalToken
bool noMatch = true;
int pos;
for (pos = 0; pos < query.Length && noMatch; pos++)
foreach (char srcToken in m_SrcTokens)
if (query[pos] == srcToken)
noMatch = false;
// if no occurrence found before end of string, return original query
if (noMatch)
return query;
// put pos back at the match of the source token
pos--;
// if there's a quote before the token, it could be inside of a string
for (int i = 0; i < pos; i++)
if (query[i] == StringDelimiter)
// start at the beginning of the string
pos = i;
// loop through all string characters
char[] queryTokens = query.ToCharArray();
do
{
char token = queryTokens[pos];
// if begin of string, advance to end so we don't replace inside string literals
if (token == StringDelimiter)
pos = FindStringEndPosition(query, pos);
else
{
// if we recognize a source token, replace it by a new one
for (int i = 0; i < m_SrcTokens.Length; i++)
if (token == m_SrcTokens[i])
{
queryTokens[pos] = m_DestTokens[i];
break;
}
}
} while (++pos < query.Length);
// return new query
return new string(queryTokens);
}
///
/// Uppercases the identifiers in the query, leaving strings untouched.
///
/// The query.
/// The query with uppercased identifiers.
public virtual string UppercaseIdentifiers(string query)
{
if (string.IsNullOrEmpty(query))
return string.Empty;
StringBuilder replacedQuery = new StringBuilder(query.Length);
// parse the query in SQL parts and string literal parts
int partStartPos = 0;
for (int currentPos = 0; currentPos < query.Length; currentPos++)
{
// String start?
if (query[currentPos] == StringDelimiter)
{
// append part before string, uppercased
replacedQuery.Append(query.Substring(partStartPos, currentPos - partStartPos).ToUpper());
// append string, case unchanged
int endStringPos = FindStringEndPosition(query, currentPos);
replacedQuery.Append(query.Substring(currentPos, endStringPos - currentPos));
// advance string
currentPos = partStartPos = endStringPos;
}
}
// append remainder of the query, uppercased
replacedQuery.Append(query.Substring(partStartPos).ToUpper());
return replacedQuery.ToString();
}
#endregion
#region Protected Methods
/// Searches the position in a query where a string is terminated.
/// The query to search.
/// The position of the opening quote of the string.
/// The position of termination quote of the string.
///
/// Assumes a correct query, 0<startPos<query.Length-1 and query[startPos]=='\''.
/// (because the function is optimized for speed)
///
/// If the query is incorrect,
/// startPos not in ]0,query.Length-1[ or query[startPos]!='\''.
protected virtual int FindStringEndPosition(string query, int startPos)
{
// move to first character of string
startPos++;
// keep searching, function will eventually return with correct query
// or encounter an IndexOutOfRangeException if incorrect
while (true)
{
// move to next quote
while (query[startPos++] != StringDelimiter) ;
// if the quote is at the end of the string, we're done
if (startPos == query.Length)
return startPos - 1;
// if the next character isn't a quote, we're done also (quotes are escaped by doubling)
if (query[startPos++] != StringDelimiter)
return startPos - 2;
}
}
#endregion
}
}