Example #1
0
        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("()"));
        }
Example #2
0
        /// <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());
        }