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