All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.enonic.xp.query.parser.QueryGrammar Maven / Gradle / Ivy

The newest version!
package com.enonic.xp.query.parser;

import java.util.List;
import java.util.function.BinaryOperator;
import java.util.function.UnaryOperator;

import org.jparsec.OperatorTable;
import org.jparsec.Parser;
import org.jparsec.Parsers;
import org.jparsec.Scanners;
import org.jparsec.Terminals;
import org.jparsec.Tokens;
import org.jparsec.pattern.Patterns;

import com.enonic.xp.query.expr.CompareExpr;
import com.enonic.xp.query.expr.ConstraintExpr;
import com.enonic.xp.query.expr.DynamicConstraintExpr;
import com.enonic.xp.query.expr.DynamicOrderExpr;
import com.enonic.xp.query.expr.FieldExpr;
import com.enonic.xp.query.expr.FieldOrderExpr;
import com.enonic.xp.query.expr.FunctionExpr;
import com.enonic.xp.query.expr.OrderExpr;
import com.enonic.xp.query.expr.QueryExpr;
import com.enonic.xp.query.expr.ValueExpr;

final class QueryGrammar
{
    private static final String[] OPERATORS = {"=", "!=", ">", ">=", "<", "<=", ",", "(", ")"};

    private static final String[] KEYWORDS = {"AND", "OR", "NOT", "LIKE", "IN", "ASC", "DESC", "ORDER", "BY"};

    private final Terminals terminals;

    private final Parser identifierToken;

    QueryGrammar()
    {
        this.identifierToken = identifierToken();
        this.terminals =
            Terminals.operators( OPERATORS ).words( this.identifierToken.source() ).caseInsensitiveKeywords( KEYWORDS ).build();
    }

    private Parser identifierToken()
    {
        return fragmentToken( "[a-zA-Z_\\*@]+[a-zA-Z0-9\\-_/\\.\\*@]*", Tokens.Tag.IDENTIFIER.name() );
    }

    private Parser fragmentToken( final String pattern, final String tag )
    {
        return Patterns.regex( pattern ).toScanner( tag ).source().map( QueryMapper.fragment( tag ) );
    }

    private Parser tokenizer()
    {
        return Parsers.or( this.terminals.tokenizer(), this.identifierToken, Terminals.StringLiteral.DOUBLE_QUOTE_TOKENIZER,
                           Terminals.StringLiteral.SINGLE_QUOTE_TOKENIZER, decimalToken() );
    }

    private Parser identifier()
    {
        return Terminals.fragment( Tokens.Tag.IDENTIFIER );
    }

    private Parser decimalToken()
    {
        return fragmentToken( "([-+])?[0-9]+(\\.[0-9]+)?", Tokens.Tag.DECIMAL.name() );
    }

    private Parser stringLiteral()
    {
        return Terminals.StringLiteral.PARSER.map( QueryMapper.stringValueExpr() );
    }

    private Parser numberLiteral()
    {
        return Terminals.fragment( Tokens.Tag.DECIMAL.name() ).map( QueryMapper.numberValueExpr() );
    }

    private Parser parseValue( final boolean allowValueFunctions )
    {
        final Parser simple = Parsers.or( stringLiteral(), numberLiteral() );

        if ( !allowValueFunctions )
        {
            return simple;
        }

        return Parsers.or( simple, parseValueFunction() );
    }

    private Parser parseValueFunction()
    {
        final Parser function = parseFunction( false );
        return function.map( QueryMapper.executeValueFunction() );
    }

    private Parser> parseValues( final boolean allowValueFunctions )
    {
        return parseValue( allowValueFunctions ).sepBy( term( "," ) ).between( term( "(" ), term( ")" ) );
    }

    private Parser parseName()
    {
        return identifier().or( this.terminals.token( KEYWORDS ).source() );
    }

    private Parser term( final String term )
    {
        return this.terminals.token( term ).map( QueryMapper.skip() );
    }

    private Parser parseField()
    {
        return parseName().map( QueryMapper.fieldExpr() );
    }

    private Parser parseCompare()
    {
        final Parser eq = parseCompare( "=", CompareExpr.Operator.EQ );
        final Parser neq = parseCompare( "!=", CompareExpr.Operator.NEQ );
        final Parser lt = parseCompare( "<", CompareExpr.Operator.LT );
        final Parser lte = parseCompare( "<=", CompareExpr.Operator.LTE );
        final Parser gt = parseCompare( ">", CompareExpr.Operator.GT );
        final Parser gte = parseCompare( ">=", CompareExpr.Operator.GTE );
        final Parser like = parseCompareWithNot( "LIKE", CompareExpr.Operator.LIKE, CompareExpr.Operator.NOT_LIKE );
        final Parser in = parseCompareWithNot( "IN", CompareExpr.Operator.IN, CompareExpr.Operator.NOT_IN );

        return Parsers.or( eq, neq, lt, lte, gt, gte, like, in );
    }

    private Parser parseCompare( final String op, final CompareExpr.Operator opCode )
    {
        return Parsers.sequence( parseField(), term( op ).retn( opCode ), parseValue( true ), QueryMapper.compareValueExpr() );
    }

    private Parser parseCompareWithNot( final String op, final CompareExpr.Operator opCode,
                                                     final CompareExpr.Operator notOpCode )
    {
        final Parser opParser = term( op ).retn( opCode );
        final Parser negOpParser = term( "NOT" ).followedBy( term( op ) ).retn( notOpCode );
        final Parser combined = Parsers.or( opParser, negOpParser );

        if ( opCode.allowMultipleValues() )
        {
            return Parsers.sequence( parseField(), combined, parseValues( true ), QueryMapper.compareValuesExpr() );
        }

        return Parsers.sequence( parseField(), combined, parseValue( true ), QueryMapper.compareValueExpr() );
    }

    private Parser> parseNot()
    {
        return term( "NOT" ).next( Parsers.constant( QueryMapper.notExpr() ) );
    }

    private Parser> parseAnd()
    {
        return term( "AND" ).next( Parsers.constant( QueryMapper.andExpr() ) );
    }

    private Parser> parseOr()
    {
        return term( "OR" ).next( Parsers.constant( QueryMapper.orExpr() ) );
    }

    private Parser parseConstraint()
    {
        final Parser.Reference ref = Parser.newReference();

        final OperatorTable table = new OperatorTable<>();
        table.prefix( parseNot(), 30 );
        table.infixl( parseAnd(), 20 );
        table.infixl( parseOr(), 10 );

        final Parser inner = Parsers.or( parseCompare(), parseDynamicConstraint() );
        final Parser unit = ref.lazy().between( term( "(" ), term( ")" ) ).or( inner );
        final Parser parser = table.build( unit );

        ref.set( parser );
        return parser;
    }

    private Parser parseFunction( final boolean allowValueFunctions )
    {
        return Parsers.sequence( parseName(), parseValues( allowValueFunctions ), QueryMapper.functionExpr() );
    }

    private Parser parseDynamicConstraint()
    {
        return parseFunction( true ).map( QueryMapper.dynamicConstraintExpr() );
    }

    private Parser parseFieldOrder()
    {
        return Parsers.sequence( parseField(), parseOrderDirection(), QueryMapper.fieldOrderExpr() );
    }

    private Parser parseDynamicOrder()
    {
        return Parsers.sequence( parseFunction( true ), parseOrderDirection(), QueryMapper.dynamicOrderExpr() );
    }

    private Parser parseOrderElement()
    {
        return Parsers.or( parseDynamicOrder(), parseFieldOrder() );
    }

    private Parser> parseOrderList()
    {
        return parseOrderElement().sepBy( term( "," ) );
    }

    private Parser> parseOrderBy()
    {
        return Parsers.sequence( term( "ORDER" ), term( "BY" ), parseOrderList() );
    }

    private Parser parseOrderDirection()
    {
        final Parser asc = term( "ASC" ).retn( OrderExpr.Direction.ASC );
        final Parser desc = term( "DESC" ).retn( OrderExpr.Direction.DESC );
        return Parsers.or( asc, desc ).optional( OrderExpr.Direction.ASC );
    }

    private Parser parseQuery()
    {
        final Parser constraint = parseConstraint().optional( null );
        final Parser> orderList = parseOrderBy().optional( null );
        return Parsers.sequence( constraint, orderList, QueryMapper.queryExpr() );
    }

    public Parser grammar()
    {
        return parseQuery().from( tokenizer(), Scanners.SQL_DELIMITER );
    }

    public Parser> orderExpressionsGrammar()
    {
        return parseOrderList().from( tokenizer(), Scanners.SQL_DELIMITER );
    }

    public Parser constraintExpressionsGrammar()
    {
        return parseConstraint().from( tokenizer(), Scanners.SQL_DELIMITER );
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy