You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
272 lines
7.1 KiB
272 lines
7.1 KiB
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<SqliteSyntaxReader> ReadList()
|
|
{
|
|
if (Type != TokenType.ListStart)
|
|
{
|
|
throw new InvalidOperationException();
|
|
}
|
|
|
|
var result = new List<SqliteSyntaxReader>();
|
|
|
|
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)));
|
|
}
|
|
}
|
|
}
|