254 lines
10 KiB
C#
254 lines
10 KiB
C#
/************************************************************************************
|
|
*
|
|
* Umbraco Data Layer
|
|
* MIT Licensed work
|
|
* ©2008 Ruben Verborgh
|
|
*
|
|
***********************************************************************************/
|
|
|
|
using System.Text;
|
|
using System;
|
|
|
|
namespace umbraco.DataLayer
|
|
{
|
|
/// <summary>
|
|
/// Object that performs parsing tasks on an SQL command.
|
|
/// </summary>
|
|
public class SqlParser
|
|
{
|
|
/// <summary>Original tokens for query token replacement.</summary>
|
|
private char[] m_SrcTokens;
|
|
/// <summary>Destination tokens for query token replacement.</summary>
|
|
private char[] m_DestTokens;
|
|
|
|
#region Public Properties
|
|
|
|
/// <summary>
|
|
/// Gets the character strings are surrounded with.
|
|
/// </summary>
|
|
/// <value>The string delimiter.</value>
|
|
public virtual char StringDelimiter
|
|
{
|
|
get { return '\''; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the param character parameters start with.
|
|
/// </summary>
|
|
/// <value>The parameter starting token.</value>
|
|
public virtual char ParamToken
|
|
{
|
|
get { return '@'; }
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="SqlParser"/> class.
|
|
/// </summary>
|
|
public SqlParser()
|
|
: this(null, null)
|
|
{ }
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="SqlParser"/> class
|
|
/// that performs the specified token translation.
|
|
/// </summary>
|
|
/// <param name="srcTokens">The original tokens.</param>
|
|
/// <param name="destTokens">The new tokens, each token corresponds with the item of
|
|
/// <c>srcTokens</c> at the same index.</param>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Parses the query, performing provider specific changes.
|
|
/// </summary>
|
|
/// <param name="query">The query.</param>
|
|
/// <returns>The modified query.</returns>
|
|
/// <remarks>
|
|
/// The default implementation returns the original query.
|
|
/// Overriding classes can change this behavior.
|
|
/// </remarks>
|
|
public virtual string Parse(string query)
|
|
{
|
|
return query;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Replaces parameters in a query by their values.
|
|
/// </summary>
|
|
/// <param name="commandText">The command text.</param>
|
|
/// <param name="parameters">The parameters.</param>
|
|
/// <returns>The query, with parameter placeholders replaced by their values.</returns>
|
|
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();
|
|
}
|
|
|
|
/// <summary>Replaces tokens before or after identifiers in a query string.</summary>
|
|
/// <param name="query">The original query.</param>
|
|
/// <returns>The query with replaced identifier tokens.</returns>
|
|
/// <remarks>Assumes a correct query.</remarks>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Uppercases the identifiers in the query, leaving strings untouched.
|
|
/// </summary>
|
|
/// <param name="query">The query.</param>
|
|
/// <returns>The query with uppercased identifiers.</returns>
|
|
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
|
|
|
|
/// <summary>Searches the position in a query where a string is terminated.</summary>
|
|
/// <param name="query">The query to search.</param>
|
|
/// <param name="startPos">The position of the opening quote of the string.</param>
|
|
/// <returns>The position of termination quote of the string.</returns>
|
|
/// <remarks>
|
|
/// Assumes a correct query, 0<startPos<query.Length-1 and query[startPos]=='\''.
|
|
/// (because the function is optimized for speed)
|
|
/// </remarks>
|
|
/// <exception cref="System.IndexOutOfRangeException">If the query is incorrect,
|
|
/// startPos not in ]0,query.Length-1[ or query[startPos]!='\''. </exception>
|
|
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
|
|
}
|
|
} |