Generalize and fix command parsing.

pull/48/head
Lau 6 years ago
parent 293286a767
commit 1781ce29df

@ -210,6 +210,7 @@ namespace System.Data.Jet
{
if (_rowCount == null)
throw new InvalidOperationException("Invalid " + commandText + ". Run a DataReader before.");
var dataTable = new DataTable("Rowcount");
dataTable.Columns.Add("ROWCOUNT", typeof(int));
dataTable.Rows.Add(_rowCount.Value);
@ -540,108 +541,8 @@ namespace System.Data.Jet
}
protected virtual IReadOnlyList<int> GetParameterIndices(string sqlFragment)
{
var parameterIndices = new List<int>();
// We use '\0' as the default state and char.
var state = '\0';
var lastChar = '\0';
// State machine to count ODBC parameter occurrences.
for (var i = 0; i < sqlFragment.Length; i++)
{
var c = sqlFragment[i];
if (state == '\'')
{
// We are currently inside a string, or closed the string in the last iteration but didn't
// know that at the time, because it still could have been the beginning of an escape sequence.
if (c == '\'')
{
// We either end the string, begin an escape sequence or end an escape sequence.
if (lastChar == '\'')
{
// This is the end of an escape sequence.
// We continue being in a string.
lastChar = '\0';
}
else
{
// This is either the beginning of an escape sequence, or the end of the string.
// We will know the in the next iteration.
lastChar = '\'';
}
}
else if (lastChar == '\'')
{
// The last iteration was the end of as string.
// Reset the current state and continue processing the current char.
state = '\0';
lastChar = '\0';
}
}
if (state == '"')
{
// We are currently inside a string, or closed the string in the last iteration but didn't
// know that at the time, because it still could have been the beginning of an escape sequence.
if (c == '"')
{
// We either end the string, begin an escape sequence or end an escape sequence.
if (lastChar == '"')
{
// This is the end of an escape sequence.
// We continue being in a string.
lastChar = '\0';
}
else
{
// This is either the beginning of an escape sequence, or the end of the string.
// We will know the in the next iteration.
lastChar = '"';
}
}
else if (lastChar == '"')
{
// The last iteration was the end of as string.
// Reset the current state and continue processing the current char.
state = '\0';
lastChar = '\0';
}
}
if (state == '\0')
{
if (c == '"')
{
state = '"';
}
else if (c == '\'')
{
state = '\'';
}
else if (c == '`')
{
state = '`';
}
else if (c == '?' ||
c == '@')
{
parameterIndices.Add(i);
}
}
if (state == '`' &&
c == '`')
{
state = '\0';
}
}
return parameterIndices.AsReadOnly();
}
=> new JetCommandParser(sqlFragment)
.GetStateIndices(new[] {'@', '?'});
/// <summary>
/// Creates a prepared (or compiled) version of the command on the data source

@ -0,0 +1,233 @@
using System.Collections.Generic;
using System.Linq;
namespace System.Data.Jet
{
public class JetCommandParser
{
public string SqlFragment { get; }
public char[] States { get; }
public JetCommandParser(string sqlFragment)
{
SqlFragment = sqlFragment ?? throw new ArgumentNullException(nameof(sqlFragment));
States = new char[sqlFragment.Length];
Parse();
}
public IReadOnlyList<int> GetStateIndices(char state, int start = 0, int length = -1)
{
if (start < 0 ||
start >= States.Length)
{
throw new ArgumentOutOfRangeException(nameof(start));
}
if (length < 0 ||
length > 0 && start + length > States.Length)
{
length = States.Length - start;
}
var stateIndices = new List<int>();
char? lastState = null;
for (var i = start; i < length; i++)
{
var currentState = States[i];
if (currentState == state &&
currentState != lastState)
{
stateIndices.Add(i);
}
lastState = currentState;
}
return stateIndices.AsReadOnly();
}
public IReadOnlyList<int> GetStateIndices(char[] states, int start = 0, int length = -1)
{
if (states == null)
{
throw new ArgumentNullException(nameof(states));
}
if (states.Length <= 0)
{
throw new ArgumentOutOfRangeException(nameof(states));
}
if (start < 0 ||
start >= States.Length)
{
throw new ArgumentOutOfRangeException(nameof(start));
}
if (length < 0 ||
length > 0 && start + length > States.Length)
{
length = States.Length - start;
}
var stateIndices = new List<int>();
char? lastState = null;
for (var i = start; i < length; i++)
{
var currentState = States[i];
if (currentState != lastState &&
states.Contains(currentState))
{
stateIndices.Add(i);
}
lastState = currentState;
}
return stateIndices.AsReadOnly();
}
protected virtual void Parse()
{
// We use '\0' as the default state and char.
var state = '\0';
var lastChar = '\0';
// State machine to parse ODBC and OleDB SQL.
for (var i = 0; i < SqlFragment.Length; i++)
{
var c = SqlFragment[i];
if (state == '\'')
{
// We are currently inside a string, or closed the string in the last iteration but didn't
// know that at the time, because it still could have been the beginning of an escape sequence.
if (c == '\'')
{
// We either end the string, begin an escape sequence or end an escape sequence.
if (lastChar == '\'')
{
// This is the end of an escape sequence.
// We continue being in a string.
lastChar = '\0';
}
else
{
// This is either the beginning of an escape sequence, or the end of the string.
// We will know the in the next iteration.
lastChar = '\'';
}
}
else if (lastChar == '\'')
{
// The last iteration was the end of a string.
// Reset the current state and continue processing the current char.
state = '\0';
lastChar = '\0';
States[i - 1] = state;
}
}
if (state == '"')
{
// We are currently inside a string, or closed the string in the last iteration but didn't
// know that at the time, because it still could have been the beginning of an escape sequence.
if (c == '"')
{
// We either end the string, begin an escape sequence or end an escape sequence.
if (lastChar == '"')
{
// This is the end of an escape sequence.
// We continue being in a string.
lastChar = '\0';
}
else
{
// This is either the beginning of an escape sequence, or the end of the string.
// We will know the in the next iteration.
lastChar = '"';
}
}
else if (lastChar == '"')
{
// The last iteration was the end of a string.
// Reset the current state and continue processing the current char.
state = '\0';
lastChar = '\0';
States[i - 1] = state;
}
}
if (state == '\0')
{
if (c == '"')
{
state = '"';
}
else if (c == '\'')
{
state = '\'';
}
else if (c == '`')
{
state = '`';
}
else if (c == '?')
{
States[i] = '?';
}
else if (c == '@')
{
// This is either the beginning of a named parameter, or a global variable like @@identity.
state = '@';
}
else if (c == ';')
{
States[i] = ';';
}
}
else if (state == '`' &&
c == '`')
{
state = '\0';
}
else if (state == '@')
{
if (c == '@')
{
// This has not been a named parameter, but a global variable.
// We use '$' to signal a global variable like @@identity.
States[i - 1] = '$';
}
state = '\0';
}
if (state != '\0')
{
States[i] = state;
}
}
//
// Handle still pending states:
//
if (state == '\'' && lastChar == '\'' ||
state == '"' && lastChar == '"')
{
// The last iteration was the end of a string.
state = '\0';
lastChar = '\0';
States[States.Length - 1] = state;
}
}
}
}
Loading…
Cancel
Save