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 );
}
}