X Tutup
using System; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; using System.Text; namespace NpgsqlTypes { /// /// Represents a PostgreSQL tsquery. This is the base class for lexeme, not, and and or nodes. /// public abstract class NpgsqlTsQuery { /// /// Node kind /// public NodeKind Kind { get; protected set; } /// /// NodeKind /// public enum NodeKind : byte { /// /// Lexeme /// Lexeme, /// /// Not operator /// Not, /// /// And operator /// And, /// /// Or operator /// Or, /// /// Represents the empty tsquery. Should only be used at top level. /// Empty } internal abstract void Write(StringBuilder sb, bool first = false); /// /// Writes the tsquery in PostgreSQL's text format. /// /// public override string ToString() { StringBuilder sb = new StringBuilder(); Write(sb, true); return sb.ToString(); } /// /// Parses a tsquery in PostgreSQL's text format. /// /// /// public static NpgsqlTsQuery Parse(string value) { if (value == null) throw new ArgumentNullException("value"); Contract.EndContractBlock(); Stack valStack = new Stack(); Stack opStack = new Stack(); StringBuilder sb = new StringBuilder(); int pos = 0; char ch; bool expectingBinOp = false; NextToken: if (pos >= value.Length) goto Finish; ch = value[pos++]; if (ch == '\'') { goto WaitEndComplex; } if ((ch == ')' || ch == '|' || ch == '&') && !expectingBinOp || (ch == '(' || ch == '!') && expectingBinOp) { throw new FormatException("Syntax error in tsquery. Unexpected token."); } if (ch == '(' || ch == '!' || ch == '&') { opStack.Push(ch); expectingBinOp = false; goto NextToken; } if (ch == '|') { if (opStack.Count > 0 && opStack.Peek() == '|') { if (valStack.Count < 2) throw new FormatException("Syntax error in tsquery"); var right = valStack.Pop(); var left = valStack.Pop(); valStack.Push(new NpgsqlTsQueryOr(left, right)); // Implicit pop and repush | } else { opStack.Push('|'); } expectingBinOp = false; goto NextToken; } if (ch == ')') { while (opStack.Count > 0 && opStack.Peek() != '(') { if (valStack.Count < 2 || opStack.Peek() == '!') throw new FormatException("Syntax error in tsquery"); var right = valStack.Pop(); var left = valStack.Pop(); valStack.Push(opStack.Pop() == '&' ? (NpgsqlTsQuery)new NpgsqlTsQueryAnd(left, right) : new NpgsqlTsQueryOr(left, right)); } if (opStack.Count == 0) throw new FormatException("Syntax error in tsquery: closing parenthesis without an opening parenthesis"); opStack.Pop(); goto PushedVal; } if (ch == ':') { throw new FormatException("Unexpected : while parsing tsquery"); } if (Char.IsWhiteSpace(ch)) { goto NextToken; } pos--; if (expectingBinOp) throw new FormatException("Unexpected lexeme while parsing tsquery"); goto WaitEnd; WaitEnd: if (pos >= value.Length || Char.IsWhiteSpace(ch = value[pos]) || ch == '!' || ch == '&' || ch == '|' || ch == '(' || ch == ')') { valStack.Push(new NpgsqlTsQueryLexeme(sb.ToString())); goto PushedVal; } pos++; if (ch == ':') { valStack.Push(new NpgsqlTsQueryLexeme(sb.ToString())); sb.Clear(); goto InWeightInfo; } if (ch == '\\') { if (pos >= value.Length) throw new FormatException(@"Unexpected \ in end of value"); ch = value[pos++]; } sb.Append(ch); goto WaitEnd; WaitEndComplex: if (pos >= value.Length) throw new FormatException("Missing terminating ' in string literal"); ch = value[pos++]; if (ch == '\'') { if (pos < value.Length && value[pos] == '\'') { ch = '\''; pos++; } else { valStack.Push(new NpgsqlTsQueryLexeme(sb.ToString())); if (pos < value.Length && value[pos] == ':') { pos++; goto InWeightInfo; } else { goto PushedVal; } } } if (ch == '\\') { if (pos >= value.Length) throw new FormatException(@"Unexpected \ in end of value"); ch = value[pos++]; } sb.Append(ch); goto WaitEndComplex; InWeightInfo: if (pos >= value.Length) goto Finish; ch = value[pos]; if (ch == '*') ((NpgsqlTsQueryLexeme)valStack.Peek()).IsPrefixSearch = true; else if (ch == 'a' || ch == 'A') ((NpgsqlTsQueryLexeme)valStack.Peek()).Weights |= NpgsqlTsQueryLexeme.Weight.A; else if (ch == 'b' || ch == 'B') ((NpgsqlTsQueryLexeme)valStack.Peek()).Weights |= NpgsqlTsQueryLexeme.Weight.B; else if (ch == 'c' || ch == 'C') ((NpgsqlTsQueryLexeme)valStack.Peek()).Weights |= NpgsqlTsQueryLexeme.Weight.C; else if (ch == 'd' || ch == 'D') ((NpgsqlTsQueryLexeme)valStack.Peek()).Weights |= NpgsqlTsQueryLexeme.Weight.D; else goto PushedVal; pos++; goto InWeightInfo; PushedVal: sb.Clear(); while (opStack.Count > 0 && (opStack.Peek() == '&' || opStack.Peek() == '!')) { if (opStack.Peek() == '&') { if (valStack.Count < 2) throw new FormatException("Syntax error in tsquery"); var right = valStack.Pop(); var left = valStack.Pop(); valStack.Push(new NpgsqlTsQueryAnd(left, right)); } else if (opStack.Peek() == '!') { if (valStack.Count == 0) throw new FormatException("Syntax error in tsquery"); valStack.Push(new NpgsqlTsQueryNot(valStack.Pop())); } opStack.Pop(); } expectingBinOp = true; goto NextToken; Finish: while (opStack.Count > 0) { if (valStack.Count < 2 || (opStack.Peek() != '|' && opStack.Peek() != '&')) throw new FormatException("Syntax error in tsquery"); var right = valStack.Pop(); var left = valStack.Pop(); valStack.Push(opStack.Pop() == '&' ? (NpgsqlTsQuery)new NpgsqlTsQueryAnd(left, right) : new NpgsqlTsQueryOr(left, right)); } if (valStack.Count != 1) throw new FormatException("Syntax error in tsquery"); return valStack.Pop(); } } /// /// TsQuery Lexeme node. /// public sealed class NpgsqlTsQueryLexeme : NpgsqlTsQuery { string _text; /// /// Lexeme text. /// public string Text { get { return _text; } set { if (string.IsNullOrEmpty(value)) throw new ArgumentException("Text is null or empty string", "value"); Contract.EndContractBlock(); _text = value; } } Weight _weights; /// /// Weights is a bitmask of the Weight enum. /// public Weight Weights { get { return _weights; } set { if (((byte)value >> 4) != 0) throw new ArgumentOutOfRangeException("value", "Illegal weights"); Contract.EndContractBlock(); _weights = value; } } /// /// Prefix search. /// public bool IsPrefixSearch { get; set; } /// /// Creates a tsquery lexeme with only lexeme text. /// /// Lexeme text. public NpgsqlTsQueryLexeme(string text) : this(text, Weight.None, false) { } /// /// Creates a tsquery lexeme with lexeme text and weights. /// /// Lexeme text. /// Bitmask of enum Weight. public NpgsqlTsQueryLexeme(string text, Weight weights) : this(text, weights, false) { } /// /// Creates a tsquery lexeme with lexeme text, weights and prefix search flag. /// /// Lexeme text. /// Bitmask of enum Weight. /// Is prefix search? public NpgsqlTsQueryLexeme(string text, Weight weights, bool isPrefixSearch) { Kind = NodeKind.Lexeme; Text = text; Weights = weights; IsPrefixSearch = isPrefixSearch; } /// /// Weight enum, can be OR'ed together. /// [Flags] public enum Weight { /// /// None /// None = 0, /// /// D /// D = 1, /// /// C /// C = 2, /// /// B /// B = 4, /// /// A /// A = 8 } internal override void Write(StringBuilder sb, bool first = false) { sb.Append('\'').Append(Text.Replace(@"\", @"\\").Replace("'", "''")).Append('\''); if (IsPrefixSearch || Weights != Weight.None) sb.Append(':'); if (IsPrefixSearch) sb.Append('*'); if ((Weights & Weight.A) != Weight.None) sb.Append('A'); if ((Weights & Weight.B) != Weight.None) sb.Append('B'); if ((Weights & Weight.C) != Weight.None) sb.Append('C'); if ((Weights & Weight.D) != Weight.None) sb.Append('D'); } } /// /// TsQuery Not node. /// public sealed class NpgsqlTsQueryNot : NpgsqlTsQuery { /// /// Child node /// public NpgsqlTsQuery Child { get; set; } /// /// Creates a not operator, with a given child node. /// /// public NpgsqlTsQueryNot(NpgsqlTsQuery child) { Kind = NodeKind.Not; Child = child; } internal override void Write(StringBuilder sb, bool first = false) { sb.Append('!'); if (Child == null) { sb.Append("''"); } else { if (Child.Kind != NodeKind.Lexeme) sb.Append("( "); Child.Write(sb, true); if (Child.Kind != NodeKind.Lexeme) sb.Append(" )"); } } } /// /// Base class for TsQuery binary operators (& and |). /// public abstract class NpgsqlTsQueryBinOp : NpgsqlTsQuery { /// /// Left child /// public NpgsqlTsQuery Left { get; set; } /// /// Right child /// public NpgsqlTsQuery Right { get; set; } } /// /// TsQuery And node. /// public sealed class NpgsqlTsQueryAnd : NpgsqlTsQueryBinOp { /// /// Creates an and operator, with two given child nodes. /// /// /// public NpgsqlTsQueryAnd(NpgsqlTsQuery left, NpgsqlTsQuery right) { Kind = NodeKind.And; Left = left; Right = right; } internal override void Write(StringBuilder sb, bool first = false) { Left.Write(sb); sb.Append(" & "); Right.Write(sb); } } /// /// TsQuery Or Node. /// public sealed class NpgsqlTsQueryOr : NpgsqlTsQueryBinOp { /// /// Creates an or operator, with two given child nodes. /// /// /// public NpgsqlTsQueryOr(NpgsqlTsQuery left, NpgsqlTsQuery right) { Kind = NodeKind.Or; Left = left; Right = right; } internal override void Write(StringBuilder sb, bool first = false) { if (!first) sb.Append("( "); Left.Write(sb); sb.Append(" | "); Right.Write(sb); if (!first) sb.Append(" )"); } } /// /// Represents an empty tsquery. Shold only be used as top node. /// public sealed class NpgsqlTsQueryEmpty : NpgsqlTsQuery { /// /// Creates a tsquery that represents an empty query. Should not be used as child node. /// public NpgsqlTsQueryEmpty() { Kind = NodeKind.Empty; } internal override void Write(StringBuilder sb, bool first = false) { } } }
X Tutup