/* * Copyright 2000-2013 Enonic AS * http://www.enonic.com/license */ package com.enonic.cms.core.content.index.queryexpression; import org.codehaus.jparsec.OperatorTable; import org.codehaus.jparsec.Parser; import org.codehaus.jparsec.Parsers; import org.codehaus.jparsec.Scanners; import org.codehaus.jparsec.Terminals; import org.codehaus.jparsec.Tokens; import org.codehaus.jparsec.functors.Binary; import org.codehaus.jparsec.functors.Unary; import org.codehaus.jparsec.misc.Mapper; import org.codehaus.jparsec.pattern.Patterns; public final class QueryParser { private final static String[] OPERATORS = {"=", "!=", ">", ">=", "<", "<=", "(", ")", ","}; private final static String[] KEYWORDS = {"LIKE", "NOT", "IN", "CONTAINS", "STARTS", "ENDS", "WITH", "OR", "AND", "ORDER", "BY", "ASC", "DESC", "FT"}; private final Terminals terms; private final Parser<Tokens.Fragment> identifierToken; public QueryParser() { this.identifierToken = identifierToken(); this.terms = Terminals.caseInsensitive( this.identifierToken.source(), OPERATORS, KEYWORDS ); } private Parser<Tokens.Fragment> fragmentToken( final String pattern, final String tag ) { return Scanners.pattern( Patterns.regex( pattern ), tag ).source().map( QueryMapper.stringToFragment( tag ) ); } private Parser<Tokens.Fragment> identifierToken() { return fragmentToken( "[a-zA-Z\\*@]+[a-zA-Z0-9\\-_/\\.\\*@]*", Tokens.Tag.IDENTIFIER.name() ); } private Parser<Tokens.Fragment> decimalToken() { return fragmentToken( "([-+])?[0-9]+(\\.[0-9]+)?", Tokens.Tag.DECIMAL.name() ); } private Parser<?> tokenizer() { return Parsers.or( this.terms.tokenizer(), this.identifierToken, decimalToken(), Terminals.StringLiteral.DOUBLE_QUOTE_TOKENIZER, Terminals.StringLiteral.SINGLE_QUOTE_TOKENIZER ); } private Parser<Void> ignored() { return Scanners.SQL_DELIMITER; } private Parser<String> identifier() { return Terminals.fragment( Tokens.Tag.IDENTIFIER ); } private Parser<FieldExpr> fieldExpr() { return identifier().map( QueryMapper.stringToFieldExpr() ); } private Parser<ValueExpr> numberExpr() { return Terminals.fragment( Tokens.Tag.DECIMAL.name() ).map( QueryMapper.stringToNumberExpr() ); } private Parser<ValueExpr> stringExpr() { return Terminals.StringLiteral.PARSER.map( QueryMapper.stringToStringExpr() ); } private Parser<ValueExpr> valueExpr() { return Parsers.or( numberExpr(), stringExpr() ); } private Parser<ArrayExpr> arrayExpr() { return valueExpr().sepBy( term( "," ) ).map( QueryMapper.valuesToArrayExpr() ).between( term( "(" ), term( ")" ) ); } private Parser<FunctionExpr> functionExpr() { return Parsers.sequence( identifier(), arrayExpr(), QueryMapper.functionExprMapper() ); } private Parser<Expression> computedExpr() { return Parsers.or( valueExpr(), functionExpr() ); } private Parser<CompareExpr> compareExpr( final String opStr, final Integer opNum, final Parser<? extends Expression> right ) { return Parsers.sequence( fieldExpr(), term( opStr ).retn( opNum ), right, QueryMapper.compareExprMapper() ); } private Parser<CompareExpr> relationalEqExpr() { return compareExpr( "=", CompareExpr.EQ, computedExpr() ); } private Parser<CompareExpr> relationalNeqExpr() { return compareExpr( "!=", CompareExpr.NEQ, computedExpr() ); } private Parser<CompareExpr> relationalGtExpr() { return compareExpr( ">", CompareExpr.GT, computedExpr() ); } private Parser<CompareExpr> relationalGteExpr() { return compareExpr( ">=", CompareExpr.GTE, computedExpr() ); } private Parser<CompareExpr> relationalLtExpr() { return compareExpr( "<", CompareExpr.LT, computedExpr() ); } private Parser<CompareExpr> relationalLteExpr() { return compareExpr( "<=", CompareExpr.LTE, computedExpr() ); } private Parser<CompareExpr> compareWithNotExpr( final Parser<?> opStr, final Integer opNum, final Integer notOpNum, final Parser<? extends Expression> right ) { final Parser<Integer> op = Parsers.or( phrase( "NOT" ).followedBy( opStr ).retn( notOpNum ), opStr.retn( opNum ) ); return Parsers.sequence( fieldExpr(), op, right, QueryMapper.compareExprMapper() ); } private Parser<CompareExpr> compareLikeExpr( final Parser<?> op, final Parser<ValueExpr> right ) { return compareWithNotExpr( op, CompareExpr.LIKE, CompareExpr.NOT_LIKE, right ); } private Parser<CompareExpr> compareLikeExpr() { return compareLikeExpr( term( "LIKE" ), stringExpr() ); } private Parser<CompareExpr> compareLikePrefixSuffixExpr( final Parser<?> op, final String prefix, final String suffix ) { return compareLikeExpr( op, stringExpr().map( QueryMapper.prefixSuffixMapper( prefix, suffix ) ) ); } private Parser<CompareExpr> compareInExpr() { return compareWithNotExpr( term( "IN" ), CompareExpr.IN, CompareExpr.NOT_IN, arrayExpr() ); } private Parser<CompareExpr> compareContainsExpr() { return compareLikePrefixSuffixExpr( term( "CONTAINS" ), "%", "%" ); } private Parser<CompareExpr> compareStartsWithExpr() { return compareLikePrefixSuffixExpr( term( "STARTS" ).followedBy( term( "WITH" ).optional() ), null, "%" ); } private Parser<CompareExpr> compareEndsWithExpr() { return compareLikePrefixSuffixExpr( term( "ENDS" ).followedBy( term( "WITH" ).optional() ), "%", null ); } private Parser<CompareExpr> compareFulltextExpr() { final Parser<Integer> op = term( "FT" ).retn( CompareExpr.FT ); return Parsers.sequence( fieldExpr(), op, stringExpr(), QueryMapper.compareExprMapper() ); } private Parser<CompareExpr> relationalExpr() { return Parsers.or( relationalEqExpr(), relationalNeqExpr(), relationalLtExpr(), relationalLteExpr(), relationalGtExpr(), relationalGteExpr() ); } private Parser<CompareExpr> matchExpr() { return Parsers.or( compareLikeExpr(), compareFulltextExpr(), compareInExpr(), compareContainsExpr(), compareStartsWithExpr(), compareEndsWithExpr() ); } private Parser<CompareExpr> compareExpr() { return Parsers.or( relationalExpr(), matchExpr() ); } private Parser<Expression> logicalExpr() { final Parser.Reference<Expression> ref = Parser.newReference(); final Parser<Expression> parser = new OperatorTable<Expression>().prefix( notExpr(), 30 ).infixl( logicalExpr( "AND", LogicalExpr.AND ), 20 ).infixl( logicalExpr( "OR", LogicalExpr.OR ), 10 ).build( paren( ref.lazy() ).or( compareExpr() ) ).label( "logicalExpr" ); ref.set( parser ); return parser; } private <T> Parser<T> paren( final Parser<T> parser ) { return parser.between( term( "(" ), term( ")" ) ); } private Parser<Unary<Expression>> notExpr() { return term( "NOT" ).next( Parsers.constant( QueryMapper.notExprMapper() ) ); } private Parser<Binary<Expression>> logicalExpr( final String opStr, final Integer opNum ) { return term( opStr ).next( Parsers.constant( QueryMapper.logicalExprMapper( opNum ) ) ); } private Parser<OrderByExpr> orderByExpr() { return Parsers.sequence( term( "ORDER" ), term( "BY" ).optional(), orderFieldExpr().sepBy( term( "," ) ) ).map( QueryMapper.orderByExprMapper() ); } private Parser<OrderFieldExpr> orderFieldExpr() { return Parsers.sequence( fieldExpr(), Parsers.or( term( "ASC" ).retn( false ), term( "DESC" ).retn( true ) ).optional( false ), QueryMapper.orderFieldExprMapper() ); } private Parser<QueryExpr> queryExpr() { return Parsers.sequence( logicalExpr().optional(), orderByExpr().optional(), QueryMapper.queryExprMapper() ); } private Parser<?> term( final String term ) { return Mapper._( this.terms.token( term ) ); } private Parser<?> phrase( final String phrase ) { return Mapper._( this.terms.phrase( phrase.split( "\\s" ) ) ); } public QueryExpr parse( final String str ) { try { return queryExpr().from( tokenizer(), ignored() ).parse( str ); } catch ( Exception e ) { throw new QueryParserException( e ); } } public static QueryParser newInstance() { return new QueryParser(); } }