diff --git a/src/main/java/io/ebeaninternal/server/grammer/EqlAdapter.java b/src/main/java/io/ebeaninternal/server/grammer/EqlAdapter.java index 397bd7d6a..503f4dc81 100644 --- a/src/main/java/io/ebeaninternal/server/grammer/EqlAdapter.java +++ b/src/main/java/io/ebeaninternal/server/grammer/EqlAdapter.java @@ -1,25 +1,15 @@ package io.ebeaninternal.server.grammer; -import io.ebean.Expression; +import io.ebean.ExpressionFactory; import io.ebean.ExpressionList; import io.ebean.FetchConfig; -import io.ebean.LikeType; import io.ebean.OrderBy; import io.ebeaninternal.api.SpiQuery; -import io.ebeaninternal.server.grammer.antlr.EQLBaseListener; -import io.ebeaninternal.server.grammer.antlr.EQLLexer; import io.ebeaninternal.server.grammer.antlr.EQLParser; import io.ebeaninternal.server.util.ArrayStack; -import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.tree.ParseTree; -import org.antlr.v4.runtime.tree.TerminalNode; -import java.util.ArrayList; -import java.util.List; - -class EqlAdapter extends EQLBaseListener { - - private static final OperatorMapping operatorMapping = new OperatorMapping(); +class EqlAdapter extends EqlWhereListener { private static final String DISTINCT = "distinct"; @@ -29,27 +19,28 @@ class EqlAdapter extends EQLBaseListener { private final SpiQuery query; - private final EqlAdapterHelper helper; + private final ExpressionFactory expressionFactory; - private ArrayStack> textStack; - - private ArrayStack> whereStack; - - private boolean textMode; - - private List inValues; - - private String inPropertyName; - - public EqlAdapter(SpiQuery query) { + EqlAdapter(SpiQuery query) { this.query = query; - this.helper = new EqlAdapterHelper(this); + this.expressionFactory = query.getExpressionFactory(); + } + + @Override + ExpressionFactory expressionFactory() { + return expressionFactory; + } + + @Override + Object namedParam(String parameterName) { + return query.createNamedParameter(parameterName); } /** * Return the current expression list that expressions should be added to. */ - protected ExpressionList peekExprList() { + @Override + ExpressionList peekExprList() { if (textMode) { // return the current text expression list @@ -74,28 +65,6 @@ class EqlAdapter extends EQLBaseListener { return textStack.peek(); } - /** - * Push the expression list onto the appropriate stack. - */ - private void pushExprList(ExpressionList list) { - if (textMode) { - textStack.push(list); - } else { - whereStack.push(list); - } - } - - /** - * End a list of expressions added by 'OR'. - */ - private void popJunction() { - if (textMode) { - textStack.pop(); - } else { - whereStack.pop(); - } - } - @Override public void enterSelect_clause(EQLParser.Select_clauseContext ctx) { @@ -199,233 +168,4 @@ class EqlAdapter extends EQLBaseListener { } } - private String getLeftHandSidePath(ParserRuleContext ctx) { - TerminalNode pathToken = ctx.getToken(EQLLexer.PATH_VARIABLE, 0); - return pathToken.getText(); - } - - @Override - public void enterInrange_expression(EQLParser.Inrange_expressionContext ctx) { - checkChildren(ctx, 5); - String path = getLeftHandSidePath(ctx); - EqlOperator op = getOperator(ctx); - if (op != EqlOperator.INRANGE) { - throw new IllegalStateException("Expecting INRANGE operator but got " + op); - } - helper.addInRange(path, child(ctx, 2), child(ctx, 4)); - } - - @Override - public void enterBetween_expression(EQLParser.Between_expressionContext ctx) { - - checkChildren(ctx, 5); - String path = getLeftHandSidePath(ctx); - EqlOperator op = getOperator(ctx); - if (op != EqlOperator.BETWEEN) { - throw new IllegalStateException("Expecting BETWEEN operator but got " + op); - } - helper.addBetween(path, child(ctx, 2), child(ctx, 4)); - } - - @Override - public void enterPropertyBetween_expression(EQLParser.PropertyBetween_expressionContext ctx) { - checkChildren(ctx, 5); - String rawValue = child(ctx, 0); - EqlOperator op = getOperator(ctx); - if (op != EqlOperator.BETWEEN) { - throw new IllegalStateException("Expecting BETWEEN operator but got " + op); - } - helper.addBetweenProperty(rawValue, child(ctx, 2), child(ctx, 4)); - } - - @Override - public void enterIn_expression(EQLParser.In_expressionContext ctx) { - this.inValues = new ArrayList<>(); - this.inPropertyName = getLeftHandSidePath(ctx); - } - - @Override - public void enterIn_value(EQLParser.In_valueContext ctx) { - int childCount = ctx.getChildCount(); - for (int i = 0; i < childCount; i++) { - String text = child(ctx, i); - if (isValue(text)) { - inValues.add(helper.bind(text)); - } - } - } - - private String child(ParserRuleContext ctx, int position) { - ParseTree child = ctx.getChild(position); - return child.getText(); - } - - private boolean isValue(String text) { - return text.length() != 1 || (!text.equals("(") && !text.equals(")") && !text.equals(",")); - } - - @Override - public void exitIn_expression(EQLParser.In_expressionContext ctx) { - helper.addIn(inPropertyName, inValues); - } - - @Override - public void enterIsNull_expression(EQLParser.IsNull_expressionContext ctx) { - String path = getLeftHandSidePath(ctx); - peekExprList().isNull(path); - } - - @Override - public void enterIsNotNull_expression(EQLParser.IsNotNull_expressionContext ctx) { - String path = getLeftHandSidePath(ctx); - peekExprList().isNotNull(path); - } - - @Override - public void enterIsEmpty_expression(EQLParser.IsEmpty_expressionContext ctx) { - String path = getLeftHandSidePath(ctx); - peekExprList().isEmpty(path); - } - - @Override - public void enterIsNotEmpty_expression(EQLParser.IsNotEmpty_expressionContext ctx) { - String path = getLeftHandSidePath(ctx); - peekExprList().isNotEmpty(path); - } - - @Override - public void enterLike_expression(EQLParser.Like_expressionContext ctx) { - addExpression(ctx); - } - - @Override - public void enterComparison_expression(EQLParser.Comparison_expressionContext ctx) { - addExpression(ctx); - } - - private void addExpression(ParserRuleContext ctx) { - int childCount = ctx.getChildCount(); - if (childCount < 3) { - throw new IllegalStateException("expecting 3 children for comparison? " + ctx); - } - String operator = child(ctx, 1); - EqlOperator op = operatorMapping.get(operator); - if (op == null) { - throw new IllegalStateException("No operator found for " + operator); - } - String path = getLeftHandSidePath(ctx); - String rhs = child(ctx, 2); - if (path.equals(rhs)) { - // the 'value operator path' form - // invert the operator and use LHS as RHS - op = invert(op); - rhs = child(ctx, 0); - } - - // RHS is Path, Literal or Named input parameter - helper.addExpression(path, op, rhs); - } - - private EqlOperator invert(EqlOperator op) { - switch (op) { - // no change - case EQ: - return EqlOperator.EQ; - case IEQ: - return EqlOperator.IEQ; - case INE: - return EqlOperator.INE; - case NE: - return EqlOperator.NE; - // invert - case LT: - return EqlOperator.GT; - case LTE: - return EqlOperator.GTE; - case GT: - return EqlOperator.LT; - case GTE: - return EqlOperator.LTE; - default: - throw new IllegalStateException("Can not invert operator " + op); - } - } - - - @Override - public void enterConditional_term(EQLParser.Conditional_termContext ctx) { - int childCount = ctx.getChildCount(); - if (childCount > 1) { - pushExprList(peekExprList().and()); - } - } - - @Override - public void exitConditional_term(EQLParser.Conditional_termContext ctx) { - if (ctx.getChildCount() > 1) { - popJunction(); - } - } - - @Override - public void enterConditional_expression(EQLParser.Conditional_expressionContext ctx) { - if (ctx.getChildCount() > 1) { - pushExprList(peekExprList().or()); - } - } - - @Override - public void exitConditional_expression(EQLParser.Conditional_expressionContext ctx) { - if (ctx.getChildCount() > 1) { - popJunction(); - } - } - - @Override - public void enterConditional_factor(EQLParser.Conditional_factorContext ctx) { - if (ctx.getChildCount() > 1) { - pushExprList(peekExprList().not()); - } - } - - @Override - public void exitConditional_factor(EQLParser.Conditional_factorContext ctx) { - if (ctx.getChildCount() > 1) { - popJunction(); - } - } - - private EqlOperator getOperator(ParserRuleContext ctx) { - String operator = child(ctx, 1); - EqlOperator op = operatorMapping.get(operator); - if (op == null) { - throw new IllegalStateException("No operator found for " + operator); - } - return op; - } - - /** - * Check for the minimum number of children. - */ - private void checkChildren(ParserRuleContext ctx, int min) { - if (ctx.getChildCount() < min) { - throw new IllegalStateException("expecting " + min + " children for comparison but got " + ctx.getChildCount()); - } - } - - public Object namedParam(String parameterName) { - return query.createNamedParameter(parameterName); - } - - public Expression like(boolean caseInsensitive, LikeType likeType, String property, Object bindValue) { - return query.getExpressionFactory().like(property, bindValue, caseInsensitive, likeType); - } - - public Expression ieq(String property, Object bindValue) { - return query.getExpressionFactory().ieqObject(property, bindValue); - } - - public Expression ine(String property, Object bindValue) { - return query.getExpressionFactory().ineObject(property, bindValue); - } } diff --git a/src/main/java/io/ebeaninternal/server/grammer/EqlAdapterHelper.java b/src/main/java/io/ebeaninternal/server/grammer/EqlAdapterHelper.java deleted file mode 100644 index 70b9355ab..000000000 --- a/src/main/java/io/ebeaninternal/server/grammer/EqlAdapterHelper.java +++ /dev/null @@ -1,148 +0,0 @@ -package io.ebeaninternal.server.grammer; - -import io.ebean.ExpressionList; -import io.ebean.LikeType; - -import java.math.BigDecimal; -import java.util.List; - -class EqlAdapterHelper { - - private final EqlAdapter owner; - - public EqlAdapterHelper(EqlAdapter owner) { - this.owner = owner; - } - - enum ValueType { - NAMED_PARAM, - STRING, - BOOL, - NUMBER - } - - private ValueType getValueType(String valueAsText) { - - char firstChar = Character.toLowerCase(valueAsText.charAt(0)); - switch (firstChar) { - case ':': - return ValueType.NAMED_PARAM; - case 't': - return ValueType.BOOL; - case 'f': - return ValueType.BOOL; - case '\'': - return ValueType.STRING; - default: - if (Character.isDigit(firstChar)) { - return ValueType.NUMBER; - } - throw new IllegalArgumentException("Unexpected first character in value [" + valueAsText + "]"); - } - } - - protected void addBetweenProperty(String rawValue, String lowProperty, String highProperty) { - peekExprList().betweenProperties(lowProperty, highProperty, bind(rawValue)); - } - - protected void addBetween(String path, String value1, String value2) { - peekExprList().between(path, bind(value1), bind(value2)); - } - - protected void addInRange(String path, String value1, String value2) { - peekExprList().inRange(path, bind(value1), bind(value2)); - } - - protected void addIn(String path, List inValues) { - peekExprList().in(path, inValues); - } - - protected void addExpression(String path, EqlOperator op, String value) { - - switch (op) { - case EQ: - peekExprList().eq(path, bind(value)); - break; - case IEQ: - peekExprList().add(owner.ieq(path, bind(value))); - break; - case NE: - peekExprList().ne(path, bind(value)); - break; - case INE: - peekExprList().add(owner.ine(path, bind(value))); - break; - case GT: - peekExprList().gt(path, bind(value)); - break; - case LT: - peekExprList().lt(path, bind(value)); - break; - case GTE: - peekExprList().ge(path, bind(value)); - break; - case LTE: - peekExprList().le(path, bind(value)); - break; - case LIKE: - addLike(false, LikeType.RAW, path, bind(value)); - break; - case CONTAINS: - addLike(false, LikeType.CONTAINS, path, bind(value)); - break; - case STARTS_WITH: - addLike(false, LikeType.STARTS_WITH, path, bind(value)); - break; - case ENDS_WITH: - addLike(false, LikeType.ENDS_WITH, path, bind(value)); - break; - case ILIKE: - addLike(true, LikeType.RAW, path, bind(value)); - break; - case ICONTAINS: - addLike(true, LikeType.CONTAINS, path, bind(value)); - break; - case ISTARTS_WITH: - addLike(true, LikeType.STARTS_WITH, path, bind(value)); - break; - case IENDS_WITH: - addLike(true, LikeType.ENDS_WITH, path, bind(value)); - break; - default: - throw new IllegalStateException("Unhandled operator " + op); - } - - } - - private void addLike(boolean caseInsensitive, LikeType likeType, String path, Object bindValue) { - peekExprList().add(owner.like(caseInsensitive, likeType, path, bindValue)); - } - - protected Object bind(String value) { - ValueType valueType = getValueType(value); - return getBindValue(valueType, value); - } - - private ExpressionList peekExprList() { - return owner.peekExprList(); - } - - private Object getBindValue(ValueType valueType, String value) { - switch (valueType) { - case BOOL: - return Boolean.parseBoolean(value); - case NUMBER: - return new BigDecimal(value); - case STRING: - return unquote(value); - case NAMED_PARAM: - return owner.namedParam(value.substring(1)); - default: - throw new IllegalArgumentException("Unhandled valueType " + valueType); - } - } - - private String unquote(String value) { - return value.substring(1, value.length() - 1); - } -} diff --git a/src/main/java/io/ebeaninternal/server/grammer/EqlParser.java b/src/main/java/io/ebeaninternal/server/grammer/EqlParser.java index b65b8ada5..949c3442c 100644 --- a/src/main/java/io/ebeaninternal/server/grammer/EqlParser.java +++ b/src/main/java/io/ebeaninternal/server/grammer/EqlParser.java @@ -1,5 +1,7 @@ package io.ebeaninternal.server.grammer; +import io.ebean.ExpressionFactory; +import io.ebean.ExpressionList; import io.ebeaninternal.api.SpiQuery; import io.ebeaninternal.server.grammer.antlr.EQLLexer; import io.ebeaninternal.server.grammer.antlr.EQLParser; @@ -22,20 +24,21 @@ public class EqlParser { */ public static void parse(String raw, SpiQuery query) { - EQLLexer lexer = new EQLLexer(CharStreams.fromString(raw)); - CommonTokenStream tokens = new CommonTokenStream(lexer); - EQLParser parser = new EQLParser(tokens); + EQLParser parser = new EQLParser(new CommonTokenStream(new EQLLexer(CharStreams.fromString(raw)))); parser.addErrorListener(errorListener); - EQLParser.Select_statementContext context = parser.select_statement(); - - EqlAdapter adapter = new EqlAdapter<>(query); - - ParseTreeWalker walker = new ParseTreeWalker(); - walker.walk(adapter, context); + new ParseTreeWalker().walk(new EqlAdapter<>(query), parser.select_statement()); query.simplifyExpressions(); } + public static void parseWhere(String raw, ExpressionList where, ExpressionFactory expr, Object[] params) { + + EQLParser parser = new EQLParser(new CommonTokenStream(new EQLLexer(CharStreams.fromString(raw)))); + parser.addErrorListener(errorListener); + + new ParseTreeWalker().walk(new EqlWhereAdapter<>(where, expr), parser.conditional_expression()); + } + static class ErrorListener extends BaseErrorListener { @Override diff --git a/src/main/java/io/ebeaninternal/server/grammer/EqlValueType.java b/src/main/java/io/ebeaninternal/server/grammer/EqlValueType.java new file mode 100644 index 000000000..a1156796c --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/grammer/EqlValueType.java @@ -0,0 +1,8 @@ +package io.ebeaninternal.server.grammer; + +enum EqlValueType { + NAMED_PARAM, + STRING, + BOOL, + NUMBER +} diff --git a/src/main/java/io/ebeaninternal/server/grammer/EqlWhereAdapter.java b/src/main/java/io/ebeaninternal/server/grammer/EqlWhereAdapter.java new file mode 100644 index 000000000..47ccf6a46 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/grammer/EqlWhereAdapter.java @@ -0,0 +1,30 @@ +package io.ebeaninternal.server.grammer; + +import io.ebean.ExpressionFactory; +import io.ebean.ExpressionList; + +class EqlWhereAdapter extends EqlWhereListener { + + private final ExpressionList where; + private final ExpressionFactory expr; + + EqlWhereAdapter(ExpressionList where, ExpressionFactory expr) { + this.where = where; + this.expr = expr; + } + + @Override + ExpressionList peekExprList() { + return where; + } + + @Override + ExpressionFactory expressionFactory() { + return expr; + } + + @Override + Object namedParam(String substring) { + return null; + } +} diff --git a/src/main/java/io/ebeaninternal/server/grammer/EqlWhereListener.java b/src/main/java/io/ebeaninternal/server/grammer/EqlWhereListener.java new file mode 100644 index 000000000..0e2f5015f --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/grammer/EqlWhereListener.java @@ -0,0 +1,407 @@ +package io.ebeaninternal.server.grammer; + +import io.ebean.Expression; +import io.ebean.ExpressionFactory; +import io.ebean.ExpressionList; +import io.ebean.LikeType; +import io.ebeaninternal.server.grammer.antlr.EQLBaseListener; +import io.ebeaninternal.server.grammer.antlr.EQLLexer; +import io.ebeaninternal.server.grammer.antlr.EQLParser; +import io.ebeaninternal.server.util.ArrayStack; +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.TerminalNode; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +abstract class EqlWhereListener extends EQLBaseListener { + + private static final OperatorMapping operatorMapping = new OperatorMapping(); + + ArrayStack> textStack; + + ArrayStack> whereStack; + + boolean textMode; + + private List inValues; + + private String inPropertyName; + + /** + * Return the current expression list that expressions should be added to. + */ + abstract ExpressionList peekExprList(); + + abstract ExpressionFactory expressionFactory(); + + abstract Object namedParam(String substring); + + /** + * Push the expression list onto the appropriate stack. + */ + private void pushExprList(ExpressionList list) { + if (textMode) { + textStack.push(list); + } else { + whereStack.push(list); + } + } + + /** + * End a list of expressions added by 'OR'. + */ + private void popJunction() { + if (textMode) { + textStack.pop(); + } else { + whereStack.pop(); + } + } + + private String getLeftHandSidePath(ParserRuleContext ctx) { + TerminalNode pathToken = ctx.getToken(EQLLexer.PATH_VARIABLE, 0); + return pathToken.getText(); + } + + @Override + public void enterInrange_expression(EQLParser.Inrange_expressionContext ctx) { + checkChildren(ctx, 5); + String path = getLeftHandSidePath(ctx); + EqlOperator op = getOperator(ctx); + if (op != EqlOperator.INRANGE) { + throw new IllegalStateException("Expecting INRANGE operator but got " + op); + } + addInRange(path, child(ctx, 2), child(ctx, 4)); + } + + @Override + public void enterBetween_expression(EQLParser.Between_expressionContext ctx) { + + checkChildren(ctx, 5); + String path = getLeftHandSidePath(ctx); + EqlOperator op = getOperator(ctx); + if (op != EqlOperator.BETWEEN) { + throw new IllegalStateException("Expecting BETWEEN operator but got " + op); + } + addBetween(path, child(ctx, 2), child(ctx, 4)); + } + + @Override + public void enterPropertyBetween_expression(EQLParser.PropertyBetween_expressionContext ctx) { + checkChildren(ctx, 5); + String rawValue = child(ctx, 0); + EqlOperator op = getOperator(ctx); + if (op != EqlOperator.BETWEEN) { + throw new IllegalStateException("Expecting BETWEEN operator but got " + op); + } + addBetweenProperty(rawValue, child(ctx, 2), child(ctx, 4)); + } + + @Override + public void enterIn_expression(EQLParser.In_expressionContext ctx) { + this.inValues = new ArrayList<>(); + this.inPropertyName = getLeftHandSidePath(ctx); + } + + @Override + public void enterIn_value(EQLParser.In_valueContext ctx) { + int childCount = ctx.getChildCount(); + for (int i = 0; i < childCount; i++) { + String text = child(ctx, i); + if (isValue(text)) { + inValues.add(bind(text)); + } + } + } + + String child(ParserRuleContext ctx, int position) { + ParseTree child = ctx.getChild(position); + return child.getText(); + } + + private boolean isValue(String text) { + return text.length() != 1 || (!text.equals("(") && !text.equals(")") && !text.equals(",")); + } + + @Override + public void exitIn_expression(EQLParser.In_expressionContext ctx) { + addIn(inPropertyName, inValues); + } + + @Override + public void enterIsNull_expression(EQLParser.IsNull_expressionContext ctx) { + String path = getLeftHandSidePath(ctx); + peekExprList().isNull(path); + } + + @Override + public void enterIsNotNull_expression(EQLParser.IsNotNull_expressionContext ctx) { + String path = getLeftHandSidePath(ctx); + peekExprList().isNotNull(path); + } + + @Override + public void enterIsEmpty_expression(EQLParser.IsEmpty_expressionContext ctx) { + String path = getLeftHandSidePath(ctx); + peekExprList().isEmpty(path); + } + + @Override + public void enterIsNotEmpty_expression(EQLParser.IsNotEmpty_expressionContext ctx) { + String path = getLeftHandSidePath(ctx); + peekExprList().isNotEmpty(path); + } + + @Override + public void enterLike_expression(EQLParser.Like_expressionContext ctx) { + addExpression(ctx); + } + + @Override + public void enterComparison_expression(EQLParser.Comparison_expressionContext ctx) { + addExpression(ctx); + } + + private void addExpression(ParserRuleContext ctx) { + int childCount = ctx.getChildCount(); + if (childCount < 3) { + throw new IllegalStateException("expecting 3 children for comparison? " + ctx); + } + String operator = child(ctx, 1); + EqlOperator op = operatorMapping.get(operator); + if (op == null) { + throw new IllegalStateException("No operator found for " + operator); + } + String path = getLeftHandSidePath(ctx); + String rhs = child(ctx, 2); + if (path.equals(rhs)) { + // the 'value operator path' form + // invert the operator and use LHS as RHS + op = invert(op); + rhs = child(ctx, 0); + } + + // RHS is Path, Literal or Named input parameter + addExpression(path, op, rhs); + } + + private EqlOperator invert(EqlOperator op) { + switch (op) { + // no change + case EQ: + return EqlOperator.EQ; + case IEQ: + return EqlOperator.IEQ; + case INE: + return EqlOperator.INE; + case NE: + return EqlOperator.NE; + // invert + case LT: + return EqlOperator.GT; + case LTE: + return EqlOperator.GTE; + case GT: + return EqlOperator.LT; + case GTE: + return EqlOperator.LTE; + default: + throw new IllegalStateException("Can not invert operator " + op); + } + } + + @Override + public void enterConditional_term(EQLParser.Conditional_termContext ctx) { + int childCount = ctx.getChildCount(); + if (childCount > 1) { + pushExprList(peekExprList().and()); + } + } + + @Override + public void exitConditional_term(EQLParser.Conditional_termContext ctx) { + if (ctx.getChildCount() > 1) { + popJunction(); + } + } + + @Override + public void enterConditional_expression(EQLParser.Conditional_expressionContext ctx) { + if (ctx.getChildCount() > 1) { + pushExprList(peekExprList().or()); + } + } + + @Override + public void exitConditional_expression(EQLParser.Conditional_expressionContext ctx) { + if (ctx.getChildCount() > 1) { + popJunction(); + } + } + + @Override + public void enterConditional_factor(EQLParser.Conditional_factorContext ctx) { + if (ctx.getChildCount() > 1) { + pushExprList(peekExprList().not()); + } + } + + @Override + public void exitConditional_factor(EQLParser.Conditional_factorContext ctx) { + if (ctx.getChildCount() > 1) { + popJunction(); + } + } + + private EqlOperator getOperator(ParserRuleContext ctx) { + String operator = child(ctx, 1); + EqlOperator op = operatorMapping.get(operator); + if (op == null) { + throw new IllegalStateException("No operator found for " + operator); + } + return op; + } + + /** + * Check for the minimum number of children. + */ + void checkChildren(ParserRuleContext ctx, int min) { + if (ctx.getChildCount() < min) { + throw new IllegalStateException("expecting " + min + " children for comparison but got " + ctx.getChildCount()); + } + } + + private Expression like(boolean caseInsensitive, LikeType likeType, String property, Object bindValue) { + return expressionFactory().like(property, bindValue, caseInsensitive, likeType); + } + + private Expression ieq(String property, Object bindValue) { + return expressionFactory().ieqObject(property, bindValue); + } + + private Expression ine(String property, Object bindValue) { + return expressionFactory().ineObject(property, bindValue); + } + + private EqlValueType getValueType(String valueAsText) { + + char firstChar = Character.toLowerCase(valueAsText.charAt(0)); + switch (firstChar) { + case ':': + return EqlValueType.NAMED_PARAM; + case 't': + return EqlValueType.BOOL; + case 'f': + return EqlValueType.BOOL; + case '\'': + return EqlValueType.STRING; + default: + if (Character.isDigit(firstChar)) { + return EqlValueType.NUMBER; + } + throw new IllegalArgumentException("Unexpected first character in value [" + valueAsText + "]"); + } + } + + private void addBetweenProperty(String rawValue, String lowProperty, String highProperty) { + peekExprList().betweenProperties(lowProperty, highProperty, bind(rawValue)); + } + + private void addBetween(String path, String value1, String value2) { + peekExprList().between(path, bind(value1), bind(value2)); + } + + private void addInRange(String path, String value1, String value2) { + peekExprList().inRange(path, bind(value1), bind(value2)); + } + + private void addIn(String path, List inValues) { + peekExprList().in(path, inValues); + } + + private void addExpression(String path, EqlOperator op, String value) { + + switch (op) { + case EQ: + peekExprList().eq(path, bind(value)); + break; + case IEQ: + peekExprList().add(ieq(path, bind(value))); + break; + case NE: + peekExprList().ne(path, bind(value)); + break; + case INE: + peekExprList().add(ine(path, bind(value))); + break; + case GT: + peekExprList().gt(path, bind(value)); + break; + case LT: + peekExprList().lt(path, bind(value)); + break; + case GTE: + peekExprList().ge(path, bind(value)); + break; + case LTE: + peekExprList().le(path, bind(value)); + break; + case LIKE: + addLike(false, LikeType.RAW, path, bind(value)); + break; + case CONTAINS: + addLike(false, LikeType.CONTAINS, path, bind(value)); + break; + case STARTS_WITH: + addLike(false, LikeType.STARTS_WITH, path, bind(value)); + break; + case ENDS_WITH: + addLike(false, LikeType.ENDS_WITH, path, bind(value)); + break; + case ILIKE: + addLike(true, LikeType.RAW, path, bind(value)); + break; + case ICONTAINS: + addLike(true, LikeType.CONTAINS, path, bind(value)); + break; + case ISTARTS_WITH: + addLike(true, LikeType.STARTS_WITH, path, bind(value)); + break; + case IENDS_WITH: + addLike(true, LikeType.ENDS_WITH, path, bind(value)); + break; + default: + throw new IllegalStateException("Unhandled operator " + op); + } + } + + private void addLike(boolean caseInsensitive, LikeType likeType, String path, Object bindValue) { + peekExprList().add(like(caseInsensitive, likeType, path, bindValue)); + } + + private Object bind(String value) { + return getBindValue(getValueType(value), value); + } + + private Object getBindValue(EqlValueType valueType, String value) { + switch (valueType) { + case BOOL: + return Boolean.parseBoolean(value); + case NUMBER: + return new BigDecimal(value); + case STRING: + return unquote(value); + case NAMED_PARAM: + return namedParam(value.substring(1)); + default: + throw new IllegalArgumentException("Unhandled valueType " + valueType); + } + } + + private String unquote(String value) { + return value.substring(1, value.length() - 1); + } +} diff --git a/src/test/java/io/ebeaninternal/server/grammer/EqlParserTest.java b/src/test/java/io/ebeaninternal/server/grammer/EqlParserTest.java index 43f79b915..9d0bf98e2 100644 --- a/src/test/java/io/ebeaninternal/server/grammer/EqlParserTest.java +++ b/src/test/java/io/ebeaninternal/server/grammer/EqlParserTest.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.grammer; import io.ebean.BaseTestCase; +import io.ebean.DB; import io.ebean.Ebean; import io.ebean.Query; import io.ebean.annotation.ForPlatform; @@ -675,9 +676,36 @@ public class EqlParserTest extends BaseTestCase { private Query parse(String raw) { - Query query = Ebean.find(Customer.class); + Query query = DB.find(Customer.class); EqlParser.parse(raw, (SpiQuery) query); return query; } + @Test + public void where_simple() { + + final Query query = where("name isNotNull"); + query.findList(); + if (isH2()) { + assertThat(query.getGeneratedSql()).contains(" from o_customer t0 where t0.name is not null"); + } + } + +// @Test +// public void where_withParams() { +// +// final Query query = where("name = ? and smallnote istartsWith ?", "Rob", "Foo"); +// query.findList(); +// if (isH2()) { +// assertThat(query.getGeneratedSql()).contains(" from o_customer t0 where t0.name is not null"); +// } +// } + + private Query where(String where, Object... params) { + + Query query = DB.find(Customer.class); + EqlParser.parseWhere(where, query.where(), query.getExpressionFactory(), params); + + return query; + } }