Skip to content

Commit

Permalink
Improve operator precedence mappings
Browse files Browse the repository at this point in the history
  • Loading branch information
timowest committed Mar 14, 2015
1 parent 7733095 commit 076a962
Show file tree
Hide file tree
Showing 44 changed files with 1,151 additions and 223 deletions.
@@ -0,0 +1,14 @@
package com.mysema.query.collections;

import org.junit.Test;

import com.mysema.query.types.TemplatesTestUtils;

public class CollQueryTemplatesTest {

@Test
public void Generic_Precedence() {
TemplatesTestUtils.testPrecedence(CollQueryTemplates.DEFAULT);
}

}
Expand Up @@ -26,30 +26,29 @@ public final class Normalization {

// TODO simplify
private static final Pattern FULL_OPERATION = Pattern.compile(
"(?<![\\d\\*/\"' ])" + "(\\b|\\(|\\s+)" +
"(?<![\\d*/\"?' ])" + "(\\b|\\(|\\s+)" +
"(" + NUMBER + WS + "[+\\-/*]" + WS + ")+" + NUMBER + WS +
"(?![\\d\\*/\"' ])");
"(?![\\d*/\"' ])");

private static final Pattern[] OPERATIONS = {
Pattern.compile(NUMBER + WS + "\\*" + WS + NUMBER),
Pattern.compile(NUMBER + WS + "/" + WS + NUMBER),
Pattern.compile(NUMBER + WS + "\\+" + WS + NUMBER),
Pattern.compile(NUMBER + WS + "\\-" + WS + NUMBER)
Pattern.compile(NUMBER + WS + "([*/])" + WS + NUMBER),
Pattern.compile(NUMBER + WS + "([+-])" + WS + NUMBER),
};

private static String normalizeOperation(String queryString) {
for (int i = 0; i < OPERATIONS.length; i++) {
Pattern operation = OPERATIONS[i];
Matcher matcher;
while ((matcher = operation.matcher(queryString)).find()) {
char operator = matcher.group(2).charAt(0);
BigDecimal first = new BigDecimal(matcher.group(1));
BigDecimal second = new BigDecimal(matcher.group(2));
BigDecimal second = new BigDecimal(matcher.group(3));
BigDecimal result;
switch (i) {
case 0: result = first.multiply(second); break;
case 1: result = first.divide(second, 10, RoundingMode.HALF_UP); break;
case 2: result = first.add(second); break;
case 3: result = first.subtract(second); break;
switch (operator) {
case '*': result = first.multiply(second); break;
case '/': result = first.divide(second, 10, RoundingMode.HALF_UP); break;
case '+': result = first.add(second); break;
case '-': result = first.subtract(second); break;
default: throw new IllegalStateException();
}
StringBuffer buffer = new StringBuffer();
Expand Down
Expand Up @@ -19,20 +19,10 @@
import java.util.Set;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.mysema.query.JoinFlag;
import com.mysema.query.QueryFlag;
import com.mysema.query.types.Constant;
import com.mysema.query.types.Expression;
import com.mysema.query.types.FactoryExpression;
import com.mysema.query.types.Operation;
import com.mysema.query.types.Operator;
import com.mysema.query.types.ParamExpression;
import com.mysema.query.types.Path;
import com.mysema.query.types.PathType;
import com.mysema.query.types.Template;
import com.mysema.query.types.TemplateExpression;
import com.mysema.query.types.Templates;
import com.mysema.query.types.Visitor;
import com.mysema.query.types.*;

/**
* SerializerBase is a stub for Serializer implementations which serialize query metadata to Strings
Expand All @@ -41,6 +31,9 @@
*/
public abstract class SerializerBase<S extends SerializerBase<S>> implements Visitor<Void,Void> {

private static final Set<Operator> SAME_PRECEDENCE = ImmutableSet.<Operator>of(Ops.CASE,
Ops.CASE_WHEN, Ops.CASE_ELSE, Ops.CASE_EQ, Ops.CASE_EQ_WHEN, Ops.CASE_EQ_ELSE);

private final StringBuilder builder = new StringBuilder(128);

private String constantPrefix = "a";
Expand Down Expand Up @@ -264,38 +257,44 @@ public Void visit(Path<?> path, Void context) {
handleTemplate(template, args);
return null;
}

protected void visitOperation(Class<?> type, Operator<?> operator, final List<? extends Expression<?>> args) {
final Template template = templates.getTemplate(operator);
if (template != null) {
final int precedence = templates.getPrecedence(operator);
final int precedence = templates.getPrecedence(operator);
boolean first = true;
for (final Template.Element element : template.getElements()) {
final Object rv = element.convert(args);
if (rv instanceof Expression) {
final Expression<?> expr = (Expression<?>) rv;
if (precedence > -1 && expr instanceof Operation) {
if (precedence < templates.getPrecedence(((Operation<?>) expr).getOperator())) {
Operator op = ((Operation<?>) expr).getOperator();
int opPrecedence = templates.getPrecedence(op);
if (precedence < opPrecedence) {
append("(").handle(expr).append(")");
} else if (!first && precedence == opPrecedence && !SAME_PRECEDENCE.contains(op)) {
append("(").handle(expr).append(")");
} else {
handle(expr);
}
} else {
handle(expr);
}
first = false;
} else if (element.isString()) {
append(rv.toString());
} else {
visitConstant(rv);
}
}
}
} else if (strict) {
throw new IllegalArgumentException("Got no pattern for " + operator);
} else {
append(operator.toString());
append("(");
handle(", ", args);
append(")");
}
}
}

}
106 changes: 65 additions & 41 deletions querydsl-core/src/main/java/com/mysema/query/types/Templates.java
Expand Up @@ -13,22 +13,41 @@
*/
package com.mysema.query.types;

import javax.annotation.Nullable;
import java.util.IdentityHashMap;
import java.util.Map;

import javax.annotation.Nullable;

/**
* Templates provides operator patterns for query expression serialization
*
* @author tiwe
*/
public class Templates {

/**
* Precedence order based on Java language operator precedence
*/
protected static class Precedence {
public static final int HIGHEST = -1;
public static final int NOT_HIGH = 10;
public static final int NEGATE = 20;
public static final int ARITH_HIGH = 30;
public static final int ARITH_LOW = 40;
public static final int COMPARISON = 50;
public static final int EQUALITY = 60;
public static final int CASE = 70, LIST = 70;
public static final int NOT = 80;
public static final int AND = 90;
public static final int XOR = 100, XNOR = 100;
public static final int OR = 110;
}

public static final Templates DEFAULT = new Templates();

private final Map<Operator<?>, Template> templates = new IdentityHashMap<Operator<?>, Template>(150);
private final Map<Operator, Template> templates = new IdentityHashMap<Operator, Template>(150);

private final Map<Operator<?>, Integer> precedence = new IdentityHashMap<Operator<?>, Integer>(150);
private final Map<Operator, Integer> precedence = new IdentityHashMap<Operator, Integer>(150);

private final TemplateFactory templateFactory;

Expand All @@ -47,16 +66,16 @@ public String escapeForLike(String str) {
};
//CHECKSTYLE:OFF

add(Ops.LIST, "{0}, {1}", 40);
add(Ops.SINGLETON, "{0}", 40);
add(Ops.LIST, "{0}, {1}", Precedence.LIST);
add(Ops.SINGLETON, "{0}", Precedence.LIST);
add(Ops.WRAPPED, "({0})");

// boolean
add(Ops.AND, "{0} && {1}", 36);
add(Ops.NOT, "!{0}", 3);
add(Ops.OR, "{0} || {1}", 38);
add(Ops.XNOR, "{0} xnor {1}", 39);
add(Ops.XOR, "{0} xor {1}", 39);
add(Ops.AND, "{0} && {1}", Precedence.AND);
add(Ops.NOT, "!{0}", Precedence.NOT_HIGH);
add(Ops.OR, "{0} || {1}", Precedence.OR);
add(Ops.XNOR, "{0} xnor {1}", Precedence.XNOR);
add(Ops.XOR, "{0} xor {1}", Precedence.XOR);

// collection
add(Ops.COL_IS_EMPTY, "empty({0})");
Expand All @@ -72,38 +91,37 @@ public String escapeForLike(String str) {
add(Ops.CONTAINS_VALUE, "containsValue({0},{1})");

// comparison
add(Ops.BETWEEN, "{0} between {1} and {2}", 30);
add(Ops.GOE, "{0} >= {1}", 20);
add(Ops.GT, "{0} > {1}", 21);
add(Ops.LOE, "{0} <= {1}", 22);
add(Ops.LT, "{0} < {1}", 23);
add(Ops.BETWEEN, "{0} between {1} and {2}", Precedence.COMPARISON);
add(Ops.GOE, "{0} >= {1}", Precedence.COMPARISON);
add(Ops.GT, "{0} > {1}", Precedence.COMPARISON);
add(Ops.LOE, "{0} <= {1}", Precedence.COMPARISON);
add(Ops.LT, "{0} < {1}", Precedence.COMPARISON);

// numeric
add(Ops.NEGATE, "-{0}", 6);
add(Ops.ADD, "{0} + {1}", 13);
add(Ops.DIV, "{0} / {1}", 8);
add(Ops.MOD, "{0} % {1}", 10);
add(Ops.MULT, "{0} * {1}", 7);
add(Ops.SUB, "{0} - {1}", 12);
add(Ops.NEGATE, "-{0}", Precedence.NEGATE);
add(Ops.ADD, "{0} + {1}", Precedence.ARITH_LOW);
add(Ops.DIV, "{0} / {1}", Precedence.ARITH_HIGH);
add(Ops.MOD, "{0} % {1}", Precedence.ARITH_HIGH);
add(Ops.MULT, "{0} * {1}", Precedence.ARITH_HIGH);
add(Ops.SUB, "{0} - {1}", Precedence.ARITH_LOW);

// various
add(Ops.EQ, "{0} = {1}", 18);
add(Ops.EQ_IGNORE_CASE, "eqIc({0},{1})", 18);
add(Ops.INSTANCE_OF, "{0}.class = {1}");
add(Ops.NE, "{0} != {1}", 25);
add(Ops.IN, "{0} in {1}", 27);
add(Ops.NOT_IN, "{0} not in {1}", 27);
add(Ops.IS_NULL, "{0} is null", 26);
add(Ops.IS_NOT_NULL, "{0} is not null", 26);
add(Ops.EQ, "{0} = {1}", Precedence.EQUALITY);
add(Ops.EQ_IGNORE_CASE, "eqIc({0},{1})", Precedence.EQUALITY);
add(Ops.INSTANCE_OF, "{0} instanceof {1}", Precedence.COMPARISON);
add(Ops.NE, "{0} != {1}", Precedence.EQUALITY);

add(Ops.IN, "{0} in {1}", Precedence.COMPARISON);
add(Ops.NOT_IN, "{0} not in {1}", Precedence.COMPARISON);
add(Ops.IS_NULL, "{0} is null", Precedence.COMPARISON);
add(Ops.IS_NOT_NULL, "{0} is not null", Precedence.COMPARISON);
add(Ops.ALIAS, "{0} as {1}", 0);

add(Ops.EXISTS, "exists({0})");

add(Ops.NUMCAST, "cast({0},{1})");
add(Ops.STRING_CAST, "str({0})");

// string
add(Ops.CONCAT, "{0} + {1}", 37);
add(Ops.CONCAT, "{0} + {1}", Precedence.ARITH_LOW);
add(Ops.LOWER, "lower({0})");
add(Ops.SUBSTR_1ARG, "substring({0},{1})");
add(Ops.SUBSTR_2ARGS, "substring({0},{1},{2})");
Expand All @@ -122,8 +140,8 @@ public String escapeForLike(String str) {
add(Ops.INDEX_OF, "indexOf({0},{1})");
add(Ops.INDEX_OF_2ARGS, "indexOf({0},{1},{2})");
add(Ops.STRING_IS_EMPTY, "empty({0})");
add(Ops.LIKE, "{0} like {1}", 26);
add(Ops.LIKE_ESCAPE, "{0} like {1} escape '{2s}'", 26);
add(Ops.LIKE, "{0} like {1}", Precedence.COMPARISON);
add(Ops.LIKE_ESCAPE, "{0} like {1} escape '{2s}'", Precedence.COMPARISON);

add(Ops.StringOps.LEFT, "left({0},{1})");
add(Ops.StringOps.RIGHT, "right({0},{1})");
Expand Down Expand Up @@ -229,22 +247,22 @@ public String escapeForLike(String str) {
add(PathType.ARRAYVALUE_CONSTANT, "{0}[{1s}]"); // serialized constant

// case
add(Ops.CASE, "case {0} end", 30);
add(Ops.CASE_WHEN, "when {0} then {1} {2}", 30);
add(Ops.CASE_ELSE, "else {0}", 30);
add(Ops.CASE, "case {0} end", Precedence.CASE);
add(Ops.CASE_WHEN, "when {0} then {1} {2}", Precedence.CASE);
add(Ops.CASE_ELSE, "else {0}", Precedence.CASE);

// case for
add(Ops.CASE_EQ, "case {0} {1} end", 30);
add(Ops.CASE_EQ_WHEN, "when {1} then {2} {3}", 30);
add(Ops.CASE_EQ_ELSE, "else {0}", 30);
add(Ops.CASE_EQ, "case {0} {1} end", Precedence.CASE);
add(Ops.CASE_EQ_WHEN, "when {1} then {2} {3}", Precedence.CASE);
add(Ops.CASE_EQ_ELSE, "else {0}", Precedence.CASE);

// coalesce
add(Ops.COALESCE, "coalesce({0})");

add(Ops.NULLIF, "nullif({0},{1})");

// subquery
add(Ops.EXISTS, "exists {0}");
add(Ops.EXISTS, "exists {0}", 0);

// numeric aggregates
add(Ops.AggOps.BOOLEAN_ALL, "all({0})");
Expand Down Expand Up @@ -310,4 +328,10 @@ public final int getPrecedence(Operator<?> op) {
return precedence.get(op).intValue();
}

protected void setPrecedence(int p, Operator... ops) {
for (Operator op : ops) {
precedence.put(op, p);
}
}

}
Expand Up @@ -60,6 +60,8 @@ public static <T extends Number & Comparable<?>> NumberExpression<T> create(Clas

public static final NumberExpression<Integer> THREE = create(Integer.class, "3");

public static final NumberExpression<Integer> FOUR = create(Integer.class, "4");

public static final NumberExpression<Integer> ZERO = create(Integer.class, "0");

private final TemplateExpressionImpl<T> templateMixin;
Expand Down
Expand Up @@ -30,6 +30,11 @@ public void Variables() {
assertEquals("var1 + 3", Normalization.normalize("var1 + 3"));
}

@Test
public void Arithmetic() {
assertEquals("3", Normalization.normalize("1 - 2 + 4"));
}

@Test
public void Normalize_Addition() {
assertEquals("3", Normalization.normalize("1+2"));
Expand Down Expand Up @@ -109,4 +114,9 @@ public void Literals() {

assertEquals("column = 'INPS-ISET-0000-00000000A' limit 1", Normalization.normalize("column = 'INPS-ISET-0000-00000000A' limit 1"));
}

@Test
public void Parameters() {
assertEquals("?1 + 1", Normalization.normalize("?1 + 1"));
}
}

0 comments on commit 076a962

Please sign in to comment.