diff --git a/src/System.Data.Jet/JetCommand.cs b/src/System.Data.Jet/JetCommand.cs index 1169abe..85b780a 100644 --- a/src/System.Data.Jet/JetCommand.cs +++ b/src/System.Data.Jet/JetCommand.cs @@ -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 GetParameterIndices(string sqlFragment) - { - var parameterIndices = new List(); - - // 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[] {'@', '?'}); /// /// Creates a prepared (or compiled) version of the command on the data source diff --git a/src/System.Data.Jet/JetCommandParser.cs b/src/System.Data.Jet/JetCommandParser.cs new file mode 100644 index 0000000..489177d --- /dev/null +++ b/src/System.Data.Jet/JetCommandParser.cs @@ -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 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(); + 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 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(); + 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; + } + } + } +} \ No newline at end of file