Skip to content

Commit

Permalink
Add NO_BACKSLASH_ESCAPES support to StatementPreparer, SqlParser and …
Browse files Browse the repository at this point in the history
…derived classes and MySqlParameter.

Single Quotes are now always escaped with two single quotes instead of a leading backspace.
This addresses everything except triggering/detecting the NO_BACKSLASH_ESCAPES from mysql-net#701 and PomeloFoundation/Pomelo.EntityFrameworkCore.MySql#827
  • Loading branch information
lauxjpn committed Sep 21, 2019
1 parent 9d30e1c commit 569cb82
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 20 deletions.
9 changes: 7 additions & 2 deletions src/MySqlConnector/Core/SqlParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,16 @@ namespace MySqlConnector.Core
{
internal abstract class SqlParser
{
protected StatementPreparer Preparer { get; }

protected SqlParser(StatementPreparer preparer) => Preparer = preparer;

public void Parse(string sql)
{
OnBeforeParse(sql ?? throw new ArgumentNullException(nameof(sql)));

int parameterStartIndex = -1;
var noBackslashEscapes = (Preparer.Options & StatementPreparerOptions.NoBackslashEscapes) == StatementPreparerOptions.NoBackslashEscapes;

var state = State.Beginning;
var beforeCommentState = State.Beginning;
Expand All @@ -35,7 +40,7 @@ public void Parse(string sql)
{
if (ch == '\'')
state = State.SingleQuotedStringSingleQuote;
else if (ch == '\\')
else if (ch == '\\' && !noBackslashEscapes)
state = State.SingleQuotedStringBackslash;
}
else if (state == State.SingleQuotedStringBackslash)
Expand All @@ -46,7 +51,7 @@ public void Parse(string sql)
{
if (ch == '"')
state = State.DoubleQuotedStringDoubleQuote;
else if (ch == '\\')
else if (ch == '\\' && !noBackslashEscapes)
state = State.DoubleQuotedStringBackslash;
}
else if (state == State.DoubleQuotedStringBackslash)
Expand Down
31 changes: 15 additions & 16 deletions src/MySqlConnector/Core/StatementPreparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ namespace MySqlConnector.Core
{
internal sealed class StatementPreparer
{
public StatementPreparerOptions Options { get; }

public StatementPreparer(string commandText, MySqlParameterCollection? parameters, StatementPreparerOptions options)
{
m_commandText = commandText;
m_parameters = parameters;
m_options = options;
Options = options | StatementPreparerOptions.NoBackslashEscapes;
}

public ParsedStatements SplitStatements()
Expand Down Expand Up @@ -42,7 +44,7 @@ public bool ParseAndBindParameters(ByteBufferWriter writer)
private int GetParameterIndex(string name)
{
var index = m_parameters?.NormalizedIndexOf(name) ?? -1;
if (index == -1 && (m_options & StatementPreparerOptions.AllowUserVariables) == 0)
if (index == -1 && (Options & StatementPreparerOptions.AllowUserVariables) == 0)
throw new MySqlException("Parameter '{0}' must be defined. To use this as a variable, set 'Allow User Variables=true' in the connection string.".FormatInvariant(name));
return index;
}
Expand All @@ -52,24 +54,24 @@ private MySqlParameter GetInputParameter(int index)
if (index >= (m_parameters?.Count ?? 0))
throw new MySqlException("Parameter index {0} is invalid when only {1} parameter{2} defined.".FormatInvariant(index, m_parameters?.Count ?? 0, m_parameters?.Count == 1 ? " is" : "s are"));
var parameter = m_parameters![index];
if (parameter.Direction != ParameterDirection.Input && (m_options & StatementPreparerOptions.AllowOutputParameters) == 0)
if (parameter.Direction != ParameterDirection.Input && (Options & StatementPreparerOptions.AllowOutputParameters) == 0)
throw new MySqlException("Only ParameterDirection.Input is supported when CommandType is Text (parameter name: {0})".FormatInvariant(parameter.ParameterName));
return parameter;
}

private sealed class ParameterSqlParser : SqlParser
{
public ParameterSqlParser(StatementPreparer preparer, ByteBufferWriter writer)
: base(preparer)
{
m_preparer = preparer;
m_writer = writer;
}

public bool IsComplete { get; private set; }

protected override void OnNamedParameter(int index, int length)
{
var parameterIndex = m_preparer.GetParameterIndex(m_preparer.m_commandText.Substring(index, length));
var parameterIndex = Preparer.GetParameterIndex(Preparer.m_commandText.Substring(index, length));
if (parameterIndex != -1)
DoAppendParameter(parameterIndex, index, length);
}
Expand All @@ -82,23 +84,22 @@ protected override void OnPositionalParameter(int index)

private void DoAppendParameter(int parameterIndex, int textIndex, int textLength)
{
m_writer.Write(m_preparer.m_commandText, m_lastIndex, textIndex - m_lastIndex);
var parameter = m_preparer.GetInputParameter(parameterIndex);
parameter.AppendSqlString(m_writer, m_preparer.m_options);
m_writer.Write(Preparer.m_commandText, m_lastIndex, textIndex - m_lastIndex);
var parameter = Preparer.GetInputParameter(parameterIndex);
parameter.AppendSqlString(m_writer, Preparer.Options);
m_lastIndex = textIndex + textLength;
}

protected override void OnParsed(FinalParseStates states)
{
m_writer.Write(m_preparer.m_commandText, m_lastIndex, m_preparer.m_commandText.Length - m_lastIndex);
m_writer.Write(Preparer.m_commandText, m_lastIndex, Preparer.m_commandText.Length - m_lastIndex);
if ((states & FinalParseStates.NeedsNewline) == FinalParseStates.NeedsNewline)
m_writer.Write((byte) '\n');
if ((states & FinalParseStates.NeedsSemicolon) == FinalParseStates.NeedsSemicolon)
m_writer.Write((byte) ';');
IsComplete = (states & FinalParseStates.Complete) == FinalParseStates.Complete;
}

readonly StatementPreparer m_preparer;
readonly ByteBufferWriter m_writer;
int m_currentParameterIndex;
int m_lastIndex;
Expand All @@ -107,8 +108,8 @@ protected override void OnParsed(FinalParseStates states)
private sealed class PreparedCommandSqlParser : SqlParser
{
public PreparedCommandSqlParser(StatementPreparer preparer, List<ParsedStatement> statements, List<int> statementStartEndIndexes, ByteBufferWriter writer)
: base(preparer)
{
m_preparer = preparer;
m_statements = statements;
m_statementStartEndIndexes = statementStartEndIndexes;
m_writer = writer;
Expand All @@ -124,7 +125,7 @@ protected override void OnStatementBegin(int index)

protected override void OnNamedParameter(int index, int length)
{
var parameterName = m_preparer.m_commandText.Substring(index, length);
var parameterName = Preparer.m_commandText.Substring(index, length);
DoAppendParameter(parameterName, -1, index, length);
}

Expand All @@ -137,7 +138,7 @@ protected override void OnPositionalParameter(int index)
private void DoAppendParameter(string? parameterName, int parameterIndex, int textIndex, int textLength)
{
// write all SQL up to the parameter
m_writer.Write(m_preparer.m_commandText, m_lastIndex, textIndex - m_lastIndex);
m_writer.Write(Preparer.m_commandText, m_lastIndex, textIndex - m_lastIndex);
m_lastIndex = textIndex + textLength;

// replace the parameter with a ? placeholder
Expand All @@ -150,12 +151,11 @@ private void DoAppendParameter(string? parameterName, int parameterIndex, int te

protected override void OnStatementEnd(int index)
{
m_writer.Write(m_preparer.m_commandText, m_lastIndex, index - m_lastIndex);
m_writer.Write(Preparer.m_commandText, m_lastIndex, index - m_lastIndex);
m_lastIndex = index;
m_statementStartEndIndexes.Add(m_writer.Position);
}

readonly StatementPreparer m_preparer;
readonly List<ParsedStatement> m_statements;
readonly List<int> m_statementStartEndIndexes;
readonly ByteBufferWriter m_writer;
Expand All @@ -166,6 +166,5 @@ protected override void OnStatementEnd(int index)

readonly string m_commandText;
readonly MySqlParameterCollection? m_parameters;
readonly StatementPreparerOptions m_options;
}
}
1 change: 1 addition & 0 deletions src/MySqlConnector/Core/StatementPreparerOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ internal enum StatementPreparerOptions
GuidFormatTimeSwapBinary16 = 0x80,
GuidFormatLittleEndianBinary16 = 0xA0,
GuidFormatMask = 0xE0,
NoBackslashEscapes = 0x100,
}
}
17 changes: 15 additions & 2 deletions src/MySqlConnector/MySql.Data.MySqlClient/MySqlParameter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -207,14 +207,21 @@ private MySqlParameter(MySqlParameter other, string parameterName)

internal void AppendSqlString(ByteBufferWriter writer, StatementPreparerOptions options)
{
var noBackslashEscapes = (options & StatementPreparerOptions.NoBackslashEscapes) == StatementPreparerOptions.NoBackslashEscapes;

if (Value is null || Value == DBNull.Value)
{
writer.Write(s_nullBytes);
}
else if (Value is string stringValue)
{
writer.Write((byte) '\'');
writer.Write(stringValue.Replace("\\", "\\\\").Replace("'", "\\'"));

if (noBackslashEscapes)
writer.Write(stringValue.Replace("'", "''"));
else
writer.Write(stringValue.Replace("\\", "\\\\").Replace("'", "''"));

writer.Write((byte) '\'');
}
else if (Value is char charValue)
Expand All @@ -223,8 +230,14 @@ internal void AppendSqlString(ByteBufferWriter writer, StatementPreparerOptions
switch (charValue)
{
case '\'':
writer.Write((byte) '\'');
writer.Write((byte) charValue);
break;

case '\\':
writer.Write((byte) '\\');
if (!noBackslashEscapes)
writer.Write((byte) '\\');

writer.Write((byte) charValue);
break;

Expand Down

0 comments on commit 569cb82

Please sign in to comment.