public void TsQuery() { NpgsqlTsQuery query; query = new NpgsqlTsQueryLexeme("a", NpgsqlTsQueryLexeme.Weight.A | NpgsqlTsQueryLexeme.Weight.B); query = new NpgsqlTsQueryOr(query, query); query = new NpgsqlTsQueryOr(query, query); var str = query.ToString(); query = NpgsqlTsQuery.Parse("a & b | c"); Assert.AreEqual("'a' & 'b' | 'c'", query.ToString()); query = NpgsqlTsQuery.Parse("'a''':*ab&d:d&!c"); Assert.AreEqual("'a''':*AB & 'd':D & !'c'", query.ToString()); query = NpgsqlTsQuery.Parse("(a & !(c | d)) & (!!a&b) | c | d | e"); Assert.AreEqual("( ( 'a' & !( 'c' | 'd' ) & !( !'a' ) & 'b' | 'c' ) | 'd' ) | 'e'", query.ToString()); Assert.AreEqual(query.ToString(), NpgsqlTsQuery.Parse(query.ToString()).ToString()); query = NpgsqlTsQuery.Parse("(((a:*)))"); Assert.AreEqual("'a':*", query.ToString()); query = NpgsqlTsQuery.Parse(@"'a\\b''cde'"); Assert.AreEqual(@"a\b'cde", ((NpgsqlTsQueryLexeme)query).Text); Assert.AreEqual(@"'a\\b''cde'", query.ToString()); Assert.Throws(typeof(FormatException), () => NpgsqlTsQuery.Parse("a b c & &")); Assert.Throws(typeof(FormatException), () => NpgsqlTsQuery.Parse("&")); Assert.Throws(typeof(FormatException), () => NpgsqlTsQuery.Parse("|")); Assert.Throws(typeof(FormatException), () => NpgsqlTsQuery.Parse("!")); Assert.Throws(typeof(FormatException), () => NpgsqlTsQuery.Parse("(")); Assert.Throws(typeof(FormatException), () => NpgsqlTsQuery.Parse(")")); Assert.Throws(typeof(FormatException), () => NpgsqlTsQuery.Parse("()")); }
/// <summary> /// Parses a tsquery in PostgreSQL's text format. /// </summary> /// <param name="value"></param> /// <returns></returns> public static NpgsqlTsQuery Parse(string value) { if (value == null) { throw new ArgumentNullException(nameof(value)); } var valStack = new Stack <NpgsqlTsQuery>(); var opStack = new Stack <NpgsqlTsQueryOperator>(); var sb = new StringBuilder(); var pos = 0; var expectingBinOp = false; var lastFollowedByOpDistance = -1; NextToken: if (pos >= value.Length) { goto Finish; } var 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 == '<') { var endOfOperatorConsumed = false; var sbCurrentLength = sb.Length; while (pos < value.Length) { var c = value[pos++]; if (c == '>') { endOfOperatorConsumed = true; break; } sb.Append(c); } if (sb.Length == sbCurrentLength || !endOfOperatorConsumed) { throw new FormatException("Syntax error in tsquery. Malformed 'followed by' operator."); } var followedByOpDistanceString = sb.ToString(sbCurrentLength, sb.Length - sbCurrentLength); if (followedByOpDistanceString == "-") { lastFollowedByOpDistance = 1; } else if (!int.TryParse(followedByOpDistanceString, out lastFollowedByOpDistance) || lastFollowedByOpDistance < 0) { throw new FormatException("Syntax error in tsquery. Malformed distance in 'followed by' operator."); } sb.Length -= followedByOpDistanceString.Length; } if (ch == '(' || ch == '!' || ch == '&' || ch == '<') { opStack.Push(new NpgsqlTsQueryOperator(ch, lastFollowedByOpDistance)); expectingBinOp = false; lastFollowedByOpDistance = 0; 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(); var tsOp = opStack.Pop(); switch (tsOp) { case '&': valStack.Push(new NpgsqlTsQueryAnd(left, right)); break; case '|': valStack.Push(new NpgsqlTsQueryOr(left, right)); break; case '<': valStack.Push(new NpgsqlTsQueryFollowedBy(left, tsOp.FollowedByDistance, right)); break; default: throw new FormatException("Syntax error in tsquery"); } } 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"); } // Proceed to 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; } 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(); var processTightBindingOperator = true; while (opStack.Count > 0 && processTightBindingOperator) { var tsOp = opStack.Peek(); switch (tsOp) { case '&': if (valStack.Count < 2) { throw new FormatException("Syntax error in tsquery"); } var andRight = valStack.Pop(); var andLeft = valStack.Pop(); valStack.Push(new NpgsqlTsQueryAnd(andLeft, andRight)); opStack.Pop(); break; case '!': if (valStack.Count == 0) { throw new FormatException("Syntax error in tsquery"); } valStack.Push(new NpgsqlTsQueryNot(valStack.Pop())); opStack.Pop(); break; case '<': if (valStack.Count < 2) { throw new FormatException("Syntax error in tsquery"); } var followedByRight = valStack.Pop(); var followedByLeft = valStack.Pop(); valStack.Push( new NpgsqlTsQueryFollowedBy( followedByLeft, tsOp.FollowedByDistance, followedByRight)); opStack.Pop(); break; default: processTightBindingOperator = false; break; } } expectingBinOp = true; goto NextToken; Finish: while (opStack.Count > 0) { if (valStack.Count < 2) { throw new FormatException("Syntax error in tsquery"); } var right = valStack.Pop(); var left = valStack.Pop(); NpgsqlTsQuery query; var tsOp = opStack.Pop(); switch (tsOp) { case '&': query = new NpgsqlTsQueryAnd(left, right); break; case '|': query = new NpgsqlTsQueryOr(left, right); break; case '<': query = new NpgsqlTsQueryFollowedBy(left, tsOp.FollowedByDistance, right); break; default: throw new FormatException("Syntax error in tsquery"); } valStack.Push(query); } if (valStack.Count != 1) { throw new FormatException("Syntax error in tsquery"); } return(valStack.Pop()); }