package org.infinispan.objectfilter.impl.syntax;
import java.util.List;
/**
* @author anistor@redhat.com
* @since 8.0
*/
final class PredicateOptimisations {
private PredicateOptimisations() {
}
/**
* Checks if two predicates are identical or opposite.
*
* @param isFirstNegated is first predicate negated?
* @param first the first predicate expression
* @param isSecondNegated is second predicate negated?
* @param second the second predicate expression
* @return -1 if unrelated predicates, 0 if identical predicates, 1 if opposite predicates
*/
public static int comparePrimaryPredicates(boolean isFirstNegated, PrimaryPredicateExpr first, boolean isSecondNegated, PrimaryPredicateExpr second) {
if (first.getClass() == second.getClass()) {
if (first instanceof ComparisonExpr) {
ComparisonExpr comparison1 = (ComparisonExpr) first;
ComparisonExpr comparison2 = (ComparisonExpr) second;
assert comparison1.getLeftChild() instanceof PropertyValueExpr;
assert comparison1.getRightChild() instanceof ConstantValueExpr;
assert comparison2.getLeftChild() instanceof PropertyValueExpr;
assert comparison2.getRightChild() instanceof ConstantValueExpr;
if (comparison1.getLeftChild().equals(comparison2.getLeftChild()) && comparison1.getRightChild().equals(comparison2.getRightChild())) {
ComparisonExpr.Type cmpType1 = comparison1.getComparisonType();
if (isFirstNegated) {
cmpType1 = cmpType1.negate();
}
ComparisonExpr.Type cmpType2 = comparison2.getComparisonType();
if (isSecondNegated) {
cmpType2 = cmpType2.negate();
}
return cmpType1 == cmpType2 ? 0 : (cmpType1 == cmpType2.negate() ? 1 : -1);
}
} else if (first.equals(second)) {
return isFirstNegated == isSecondNegated ? 0 : 1;
}
}
return -1;
}
public static void optimizePredicates(List<BooleanExpr> children, boolean isConjunction) {
removeRedundantPredicates(children, isConjunction);
optimizeOverlappingIntervalPredicates(children, isConjunction);
}
/**
* Removes duplicate occurrences of same predicate in a conjunction or disjunction. Also detects and removes
* tautology and contradiction. The following translation rules are applied:
* <ul>
* <li>X || X => X</li>
* <li>X && X => X</li>
* <li>!X || !X => !X</li>
* <li>!X && !X => !X</li>
* <li>X || !X => TRUE (tautology)</li>
* <li>X && !X => FALSE (contradiction)</li>
* </ul>
*
* @param children the list of children expressions
* @param isConjunction is the parent boolean expression a conjunction or a disjunction?
*/
private static void removeRedundantPredicates(List<BooleanExpr> children, boolean isConjunction) {
for (int i = 0; i < children.size(); i++) {
BooleanExpr ci = children.get(i);
if (ci instanceof BooleanOperatorExpr || ci instanceof FullTextBoostExpr || ci instanceof FullTextOccurExpr) {
// we may encounter non-predicate expressions, just ignore them
continue;
}
boolean isCiNegated = ci instanceof NotExpr;
if (isCiNegated) {
ci = ((NotExpr) ci).getChild();
}
assert ci instanceof PrimaryPredicateExpr;
PrimaryPredicateExpr ci1 = (PrimaryPredicateExpr) ci;
assert ci1.getChild() instanceof PropertyValueExpr;
PropertyValueExpr pve = (PropertyValueExpr) ci1.getChild();
if (pve.isRepeated()) {
// do not optimize repeated predicates
continue;
}
int j = i + 1;
while (j < children.size()) {
BooleanExpr cj = children.get(j);
// we may encounter non-predicate expressions, just ignore them
if (!(cj instanceof BooleanOperatorExpr || cj instanceof FullTextBoostExpr || cj instanceof FullTextOccurExpr)) {
boolean isCjNegated = cj instanceof NotExpr;
if (isCjNegated) {
cj = ((NotExpr) cj).getChild();
}
PrimaryPredicateExpr cj1 = (PrimaryPredicateExpr) cj;
assert cj1.getChild() instanceof PropertyValueExpr;
PropertyValueExpr pve2 = (PropertyValueExpr) cj1.getChild();
// do not optimize repeated predicates
if (!pve2.isRepeated()) {
int res = comparePrimaryPredicates(isCiNegated, ci1, isCjNegated, cj1);
if (res == 0) {
// found duplication
children.remove(j);
continue;
} else if (res == 1) {
// found tautology or contradiction
children.clear();
children.add(ConstantBooleanExpr.forBoolean(!isConjunction));
return;
}
}
}
j++;
}
}
}
private static void optimizeOverlappingIntervalPredicates(List<BooleanExpr> children, boolean isConjunction) {
for (int i = 0; i < children.size(); i++) {
BooleanExpr ci = children.get(i);
if (ci instanceof ComparisonExpr) {
ComparisonExpr first = (ComparisonExpr) ci;
assert first.getLeftChild() instanceof PropertyValueExpr;
assert first.getRightChild() instanceof ConstantValueExpr;
PropertyValueExpr pve = (PropertyValueExpr) first.getLeftChild();
if (pve.isRepeated()) {
// do not optimize repeated predicates
continue;
}
int j = i + 1;
while (j < children.size()) {
BooleanExpr cj = children.get(j);
if (cj instanceof ComparisonExpr) {
ComparisonExpr second = (ComparisonExpr) cj;
assert second.getLeftChild() instanceof PropertyValueExpr;
assert second.getRightChild() instanceof ConstantValueExpr;
PropertyValueExpr pve2 = (PropertyValueExpr) second.getLeftChild();
// do not optimize repeated predicates
if (!pve2.isRepeated()) {
if (first.getLeftChild().equals(second.getLeftChild())) {
BooleanExpr res = optimizeOverlappingIntervalPredicates(first, second, isConjunction);
if (res != null) {
if (res instanceof ConstantBooleanExpr) {
children.clear();
children.add(res);
return;
}
children.remove(j);
if (res != first) {
first = (ComparisonExpr) res;
children.set(i, first);
}
continue;
}
}
}
}
j++;
}
}
}
}
/**
* @param first
* @param second
* @param isConjunction
* @return null or a replacement BooleanExpr
*/
private static BooleanExpr optimizeOverlappingIntervalPredicates(ComparisonExpr first, ComparisonExpr second, boolean isConjunction) {
final ConstantValueExpr firstConstant = (ConstantValueExpr) first.getRightChild();
final ConstantValueExpr secondConstant = (ConstantValueExpr) second.getRightChild();
if (firstConstant.isParameter() || secondConstant.isParameter()) {
// if an interval end is a parameter then it is too early to do optimisations
return null;
}
final Comparable firstValue = firstConstant.getConstantValue();
final Comparable secondValue = secondConstant.getConstantValue();
final int cmp = firstValue.compareTo(secondValue);
if (first.getComparisonType() == ComparisonExpr.Type.EQUAL) {
return optimizeEqAndInterval(first, second, isConjunction, cmp);
} else if (second.getComparisonType() == ComparisonExpr.Type.EQUAL) {
return optimizeEqAndInterval(second, first, isConjunction, -cmp);
} else if (first.getComparisonType() == ComparisonExpr.Type.NOT_EQUAL) {
return optimizeNotEqAndInterval(first, second, isConjunction, cmp);
} else if (second.getComparisonType() == ComparisonExpr.Type.NOT_EQUAL) {
return optimizeNotEqAndInterval(second, first, isConjunction, -cmp);
}
if (cmp == 0) {
if (first.getComparisonType() == second.getComparisonType()) {
// identical intervals
return first;
}
if (first.getComparisonType() == second.getComparisonType().negate()) {
// opposite directions, disjoint, union is full coverage
return isConjunction ? ConstantBooleanExpr.FALSE : ConstantBooleanExpr.TRUE;
}
if (first.getComparisonType() == ComparisonExpr.Type.LESS_OR_EQUAL || first.getComparisonType() == ComparisonExpr.Type.GREATER_OR_EQUAL) {
// opposite directions, overlapping in one point, union is full coverage
return isConjunction ? new ComparisonExpr(first.getLeftChild(), first.getRightChild(), ComparisonExpr.Type.EQUAL) : ConstantBooleanExpr.TRUE;
} else {
// opposite directions, disjoint in one point, union is not full coverage
return isConjunction ? ConstantBooleanExpr.FALSE : new ComparisonExpr(first.getLeftChild(), first.getRightChild(), ComparisonExpr.Type.NOT_EQUAL);
}
}
// opposite direction intervals
if (first.getComparisonType() == second.getComparisonType().negate() || first.getComparisonType() == second.getComparisonType().reverse()) {
if (cmp < 0) {
if (first.getComparisonType() == ComparisonExpr.Type.LESS || first.getComparisonType() == ComparisonExpr.Type.LESS_OR_EQUAL) {
if (isConjunction) {
return ConstantBooleanExpr.FALSE;
}
} else if (!isConjunction) {
return ConstantBooleanExpr.TRUE;
}
} else {
if (first.getComparisonType() == ComparisonExpr.Type.GREATER || first.getComparisonType() == ComparisonExpr.Type.GREATER_OR_EQUAL) {
if (isConjunction) {
return ConstantBooleanExpr.FALSE;
}
} else if (!isConjunction) {
return ConstantBooleanExpr.TRUE;
}
}
return null;
}
// same direction intervals
if (first.getComparisonType() == ComparisonExpr.Type.LESS || first.getComparisonType() == ComparisonExpr.Type.LESS_OR_EQUAL) {
// less than
if (isConjunction) {
return cmp < 0 ? first : second;
} else {
return cmp < 0 ? second : first;
}
} else {
// greater than
if (isConjunction) {
return cmp < 0 ? second : first;
} else {
return cmp < 0 ? first : second;
}
}
}
private static BooleanExpr optimizeEqAndInterval(ComparisonExpr first, ComparisonExpr second, boolean isConjunction, int cmp) {
assert first.getComparisonType() == ComparisonExpr.Type.EQUAL;
switch (second.getComparisonType()) {
case EQUAL:
if (cmp == 0) {
return first;
}
return isConjunction ? ConstantBooleanExpr.FALSE : null;
case NOT_EQUAL:
if (cmp == 0) {
return ConstantBooleanExpr.forBoolean(!isConjunction);
}
return isConjunction ? first : null;
case LESS:
if (cmp == 0) {
return isConjunction ? ConstantBooleanExpr.FALSE : new ComparisonExpr(first.getLeftChild(), first.getRightChild(), ComparisonExpr.Type.LESS_OR_EQUAL);
}
if (cmp < 0) {
return isConjunction ? first : second;
}
return isConjunction ? ConstantBooleanExpr.FALSE : null;
case LESS_OR_EQUAL:
if (cmp <= 0) {
return isConjunction ? first : second;
}
return isConjunction ? ConstantBooleanExpr.FALSE : null;
case GREATER:
if (cmp == 0) {
return isConjunction ? ConstantBooleanExpr.FALSE : new ComparisonExpr(first.getLeftChild(), first.getRightChild(), ComparisonExpr.Type.GREATER_OR_EQUAL);
}
if (cmp > 0) {
return isConjunction ? first : second;
}
return isConjunction ? ConstantBooleanExpr.FALSE : null;
case GREATER_OR_EQUAL:
if (cmp >= 0) {
return isConjunction ? first : second;
}
return isConjunction ? ConstantBooleanExpr.FALSE : null;
default:
return null;
}
}
private static BooleanExpr optimizeNotEqAndInterval(ComparisonExpr first, ComparisonExpr second, boolean isConjunction, int cmp) {
assert first.getComparisonType() == ComparisonExpr.Type.NOT_EQUAL;
switch (second.getComparisonType()) {
case EQUAL:
if (cmp == 0) {
return ConstantBooleanExpr.FALSE;
}
return isConjunction ? second : first;
case NOT_EQUAL:
if (cmp == 0) {
return first;
}
return isConjunction ? null : ConstantBooleanExpr.TRUE;
case LESS:
if (cmp >= 0) {
return isConjunction ? second : first;
}
return isConjunction ? null : ConstantBooleanExpr.TRUE;
case LESS_OR_EQUAL:
if (cmp < 0) {
return isConjunction ? null : ConstantBooleanExpr.TRUE;
}
if (cmp > 0) {
return isConjunction ? second : first;
}
return isConjunction ? new ComparisonExpr(first.getLeftChild(), first.getRightChild(), ComparisonExpr.Type.LESS) : ConstantBooleanExpr.TRUE;
case GREATER:
if (cmp > 0) {
return isConjunction ? null : ConstantBooleanExpr.TRUE;
}
return isConjunction ? second : first;
case GREATER_OR_EQUAL:
if (cmp < 0) {
return isConjunction ? second : first;
}
if (cmp > 0) {
return isConjunction ? new ComparisonExpr(first.getLeftChild(), first.getRightChild(), ComparisonExpr.Type.GREATER) : ConstantBooleanExpr.TRUE;
}
return isConjunction ? ConstantBooleanExpr.FALSE : null;
default:
return null;
}
}
}