using System; using System.Collections.Generic; using System.Data; using System.Text; namespace NzbDrone.Core.Datastore.Migration.Framework { public class SqliteSyntaxReader { public string Buffer { get; private set; } public int Index { get; private set; } private int _previousIndex; public TokenType Type { get; private set; } public string Value { get; private set; } public string ValueToUpper => Value.ToUpperInvariant(); public bool IsEndOfFile => Index >= Buffer.Length; public enum TokenType { Start, Whitespace, End, ListStart, ListSeparator, ListEnd, Identifier, StringToken, StringLiteral, UnknownSymbol } public SqliteSyntaxReader(string sql) { Buffer = sql; } public void TrimBuffer() { Buffer = Buffer.Substring(Index); Index = 0; _previousIndex = 0; } public void SkipWhitespace() { while (!IsEndOfFile && char.IsWhiteSpace(Buffer[Index])) { Index++; } } public void SkipTillToken(TokenType tokenType) { if (IsEndOfFile) { return; } while (Read() != tokenType) { if (Type == TokenType.ListStart) { SkipTillToken(TokenType.ListEnd); } } } public void Rollback() { Index = _previousIndex; Type = TokenType.Whitespace; } public TokenType Read() { if (!IsEndOfFile && char.IsWhiteSpace(Buffer[Index])) { Type = TokenType.Whitespace; SkipWhitespace(); } _previousIndex = Index; if (IsEndOfFile) { Type = TokenType.End; return Type; } if (Buffer[Index] == '(') { Type = TokenType.ListStart; Value = null; Index++; return Type; } if (Buffer[Index] == ',') { Type = TokenType.ListSeparator; Value = null; Index++; return Type; } if (Buffer[Index] == ')') { Type = TokenType.ListEnd; Value = null; Index++; return Type; } if (Buffer[Index] == '\'') { Type = TokenType.StringLiteral; Value = ReadEscapedString('\''); return Type; } if (Buffer[Index] == '\"') { Type = TokenType.Identifier; Value = ReadEscapedString('\"'); return Type; } if (Buffer[Index] == '`') { Type = TokenType.Identifier; Value = ReadEscapedString('`'); return Type; } if (Buffer[Index] == '[') { Type = TokenType.Identifier; Value = ReadTerminatedString(']'); return Type; } if (Type == TokenType.UnknownSymbol) { Value = Buffer[Index].ToString(); Index++; return Type; } if (char.IsLetter(Buffer[Index])) { var start = Index; var end = start + 1; while (end < Buffer.Length && (char.IsLetter(Buffer[end]) || char.IsNumber(Buffer[end]) || Buffer[end] == '_')) { end++; } if (end >= Buffer.Length || Buffer[end] == ',' || Buffer[end] == ')' || Buffer[end] == '(' || char.IsWhiteSpace(Buffer[end])) { Index = end; } else if (Type == TokenType.UnknownSymbol) { Value = Buffer[Index].ToString(); Index++; return Type; } else { throw CreateSyntaxException("Unexpected sequence."); } Type = TokenType.StringToken; Value = Buffer.Substring(start, end - start); return Type; } Type = TokenType.UnknownSymbol; Value = Buffer[Index].ToString(); Index++; return Type; } public List ReadList() { if (Type != TokenType.ListStart) { throw new InvalidOperationException(); } var result = new List(); var start = Index; while (Read() != TokenType.ListEnd) { if (Type == TokenType.End) { throw CreateSyntaxException("Expected ListEnd first"); } if (Type == TokenType.ListStart) { SkipTillToken(TokenType.ListEnd); } else if (Type == TokenType.ListSeparator) { result.Add(new SqliteSyntaxReader(Buffer.Substring(start, Index - start - 1))); start = Index; } } if (Index >= start + 1) { result.Add(new SqliteSyntaxReader(Buffer.Substring(start, Index - start - 1))); } return result; } protected string ReadTerminatedString(char terminator) { var start = Index + 1; var end = Buffer.IndexOf(terminator, Index); if (end == -1) { throw new SyntaxErrorException(); } Index = end + 1; return Buffer.Substring(start, end - start); } protected string ReadEscapedString(char escape) { var identifier = new StringBuilder(); while (true) { var start = Index + 1; var end = Buffer.IndexOf(escape, start); if (end == -1) { throw new SyntaxErrorException(); } Index = end + 1; identifier.Append(Buffer.AsSpan(start, end - start)); if (Buffer[Index] != escape) { break; } identifier.Append(escape); } return identifier.ToString(); } public SyntaxErrorException CreateSyntaxException(string message, params object[] args) { return new SyntaxErrorException(string.Format("{0}. Syntax Error near: {1}", string.Format(message, args), Buffer.Substring(_previousIndex))); } } }