Skip to content

Commit fb65ef3

Browse files
authored
SQL: Extend the optimisations for equalities (#50792) (#51098)
* Extend the optimizations for equalities This commit supplements the optimisations of equalities in conjunctions and disjunctions: * for conjunctions, the existing optimizations with ranges are extended with not-equalities and inequalities; these lead to a fast resolution, the conjunction either being evaluate to a FALSE, or the non-equality conditions being dropped as superfluous; * optimisations for disjunctions are added to be applied against ranges, inequalities and not-equalities; these lead to disjunction either becoming TRUE or the equality being dropped, either as superfluous or merged into a range/inequality. * Adress review notes * Fix the bug around wrongly optimizing 'a=2 OR a!=?', which only yields TRUE for same values in equality and inequality. * Var renamings, code style adjustments, comments corrections. * Address further review comments. Extend optim. - fix a few code comments; - extend the Equals OR NotEquals optimitsation (a=2 OR a!=5 -> a!=5); - extend the Equals OR Range optimisation on limits equality (a=2 OR 2<=a<5 -> 2<=a<5); - in case an equality is being removed in a conjunction, the rest of possible optimisations to test is now skipped. * rename one var for better legiblity - s/rmEqual/removeEquals (cherry picked from commit 62e7c6a)
1 parent fda25ed commit fb65ef3

File tree

2 files changed

+467
-22
lines changed

2 files changed

+467
-22
lines changed

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/optimizer/Optimizer.java

Lines changed: 221 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
import java.util.ArrayList;
8585
import java.util.Arrays;
8686
import java.util.HashMap;
87+
import java.util.Iterator;
8788
import java.util.LinkedHashMap;
8889
import java.util.LinkedHashSet;
8990
import java.util.LinkedList;
@@ -814,7 +815,7 @@ private Expression simplifyAndOr(BinaryPredicate<?, ?, ?, ?> bc) {
814815
}
815816

816817
//
817-
// common factor extraction -> (a || b) && (a || c) => a && (b || c)
818+
// common factor extraction -> (a || b) && (a || c) => a || (b && c)
818819
//
819820
List<Expression> leftSplit = splitOr(l);
820821
List<Expression> rightSplit = splitOr(r);
@@ -852,7 +853,7 @@ private Expression simplifyAndOr(BinaryPredicate<?, ?, ?, ?> bc) {
852853
}
853854

854855
//
855-
// common factor extraction -> (a && b) || (a && c) => a || (b & c)
856+
// common factor extraction -> (a && b) || (a && c) => a && (b || c)
856857
//
857858
List<Expression> leftSplit = splitAnd(l);
858859
List<Expression> rightSplit = splitAnd(r);
@@ -958,9 +959,11 @@ private Expression literalToTheRight(BinaryOperator<?, ?, ?, ?> be) {
958959
}
959960

960961
/**
961-
* Propagate Equals to eliminate conjuncted Ranges.
962-
* When encountering a different Equals or non-containing {@link Range}, the conjunction becomes false.
963-
* When encountering a containing {@link Range}, the range gets eliminated by the equality.
962+
* Propagate Equals to eliminate conjuncted Ranges or BinaryComparisons.
963+
* When encountering a different Equals, non-containing {@link Range} or {@link BinaryComparison}, the conjunction becomes false.
964+
* When encountering a containing {@link Range}, {@link BinaryComparison} or {@link NotEquals}, these get eliminated by the equality.
965+
*
966+
* Since this rule can eliminate Ranges and BinaryComparisons, it should be applied before {@link CombineBinaryComparisons}.
964967
*
965968
* This rule doesn't perform any promotion of {@link BinaryComparison}s, that is handled by
966969
* {@link CombineBinaryComparisons} on purpose as the resulting Range might be foldable
@@ -976,14 +979,20 @@ static class PropagateEquals extends OptimizerExpressionRule {
976979
protected Expression rule(Expression e) {
977980
if (e instanceof And) {
978981
return propagate((And) e);
982+
} else if (e instanceof Or) {
983+
return propagate((Or) e);
979984
}
980985
return e;
981986
}
982987

983988
// combine conjunction
984989
private Expression propagate(And and) {
985990
List<Range> ranges = new ArrayList<>();
991+
// Only equalities, not-equalities and inequalities with a foldable .right are extracted separately;
992+
// the others go into the general 'exps'.
986993
List<BinaryComparison> equals = new ArrayList<>();
994+
List<NotEquals> notEquals = new ArrayList<>();
995+
List<BinaryComparison> inequalities = new ArrayList<>();
987996
List<Expression> exps = new ArrayList<>();
988997

989998
boolean changed = false;
@@ -996,35 +1005,42 @@ private Expression propagate(And and) {
9961005
// equals on different values evaluate to FALSE
9971006
if (otherEq.right().foldable()) {
9981007
for (BinaryComparison eq : equals) {
999-
// cannot evaluate equals so skip it
1000-
if (!eq.right().foldable()) {
1001-
continue;
1002-
}
10031008
if (otherEq.left().semanticEquals(eq.left())) {
1004-
if (eq.right().foldable() && otherEq.right().foldable()) {
1005-
Integer comp = BinaryComparison.compare(eq.right().fold(), otherEq.right().fold());
1006-
if (comp != null) {
1007-
// var cannot be equal to two different values at the same time
1008-
if (comp != 0) {
1009-
return FALSE;
1010-
}
1009+
Integer comp = BinaryComparison.compare(eq.right().fold(), otherEq.right().fold());
1010+
if (comp != null) {
1011+
// var cannot be equal to two different values at the same time
1012+
if (comp != 0) {
1013+
return FALSE;
10111014
}
10121015
}
10131016
}
10141017
}
1018+
equals.add(otherEq);
1019+
} else {
1020+
exps.add(otherEq);
1021+
}
1022+
} else if (ex instanceof GreaterThan || ex instanceof GreaterThanOrEqual ||
1023+
ex instanceof LessThan || ex instanceof LessThanOrEqual) {
1024+
BinaryComparison bc = (BinaryComparison) ex;
1025+
if (bc.right().foldable()) {
1026+
inequalities.add(bc);
1027+
} else {
1028+
exps.add(ex);
1029+
}
1030+
} else if (ex instanceof NotEquals) {
1031+
NotEquals otherNotEq = (NotEquals) ex;
1032+
if (otherNotEq.right().foldable()) {
1033+
notEquals.add(otherNotEq);
1034+
} else {
1035+
exps.add(ex);
10151036
}
1016-
equals.add(otherEq);
10171037
} else {
10181038
exps.add(ex);
10191039
}
10201040
}
10211041

10221042
// check
10231043
for (BinaryComparison eq : equals) {
1024-
// cannot evaluate equals so skip it
1025-
if (!eq.right().foldable()) {
1026-
continue;
1027-
}
10281044
Object eqValue = eq.right().fold();
10291045

10301046
for (int i = 0; i < ranges.size(); i++) {
@@ -1060,9 +1076,192 @@ private Expression propagate(And and) {
10601076
changed = true;
10611077
}
10621078
}
1079+
1080+
// evaluate all NotEquals against the Equal
1081+
for (Iterator<NotEquals> iter = notEquals.iterator(); iter.hasNext(); ) {
1082+
NotEquals neq = iter.next();
1083+
if (eq.left().semanticEquals(neq.left())) {
1084+
Integer comp = BinaryComparison.compare(eqValue, neq.right().fold());
1085+
if (comp != null) {
1086+
if (comp == 0) {
1087+
return FALSE; // clashing and conflicting: a = 1 AND a != 1
1088+
} else {
1089+
iter.remove(); // clashing and redundant: a = 1 AND a != 2
1090+
changed = true;
1091+
}
1092+
}
1093+
}
1094+
}
1095+
1096+
// evaluate all inequalities against the Equal
1097+
for (Iterator<BinaryComparison> iter = inequalities.iterator(); iter.hasNext(); ) {
1098+
BinaryComparison bc = iter.next();
1099+
if (eq.left().semanticEquals(bc.left())) {
1100+
Integer compare = BinaryComparison.compare(eqValue, bc.right().fold());
1101+
if (compare != null) {
1102+
if (bc instanceof LessThan || bc instanceof LessThanOrEqual) { // a = 2 AND a </<= ?
1103+
if ((compare == 0 && bc instanceof LessThan) || // a = 2 AND a < 2
1104+
0 < compare) { // a = 2 AND a </<= 1
1105+
return FALSE;
1106+
}
1107+
} else if (bc instanceof GreaterThan || bc instanceof GreaterThanOrEqual) { // a = 2 AND a >/>= ?
1108+
if ((compare == 0 && bc instanceof GreaterThan) || // a = 2 AND a > 2
1109+
compare < 0) { // a = 2 AND a >/>= 3
1110+
return FALSE;
1111+
}
1112+
}
1113+
1114+
iter.remove();
1115+
changed = true;
1116+
}
1117+
}
1118+
}
1119+
}
1120+
1121+
return changed ? Predicates.combineAnd(CollectionUtils.combine(exps, equals, notEquals, inequalities, ranges)) : and;
1122+
}
1123+
1124+
// combine disjunction:
1125+
// a = 2 OR a > 3 -> nop; a = 2 OR a > 1 -> a > 1
1126+
// a = 2 OR a < 3 -> a < 3; a = 2 OR a < 1 -> nop
1127+
// a = 2 OR 3 < a < 5 -> nop; a = 2 OR 1 < a < 3 -> 1 < a < 3; a = 2 OR 0 < a < 1 -> nop
1128+
// a = 2 OR a != 2 -> TRUE; a = 2 OR a = 5 -> nop; a = 2 OR a != 5 -> a != 5
1129+
private Expression propagate(Or or) {
1130+
List<Expression> exps = new ArrayList<>();
1131+
List<Equals> equals = new ArrayList<>(); // foldable right term Equals
1132+
List<NotEquals> notEquals = new ArrayList<>(); // foldable right term NotEquals
1133+
List<Range> ranges = new ArrayList<>();
1134+
List<BinaryComparison> inequalities = new ArrayList<>(); // foldable right term (=limit) BinaryComparision
1135+
1136+
// split expressions by type
1137+
for (Expression ex : Predicates.splitOr(or)) {
1138+
if (ex instanceof Equals) {
1139+
Equals eq = (Equals) ex;
1140+
if (eq.right().foldable()) {
1141+
equals.add(eq);
1142+
} else {
1143+
exps.add(ex);
1144+
}
1145+
} else if (ex instanceof NotEquals) {
1146+
NotEquals neq = (NotEquals) ex;
1147+
if (neq.right().foldable()) {
1148+
notEquals.add(neq);
1149+
} else {
1150+
exps.add(ex);
1151+
}
1152+
} else if (ex instanceof Range) {
1153+
ranges.add((Range) ex);
1154+
} else if (ex instanceof BinaryComparison) {
1155+
BinaryComparison bc = (BinaryComparison) ex;
1156+
if (bc.right().foldable()) {
1157+
inequalities.add(bc);
1158+
} else {
1159+
exps.add(ex);
1160+
}
1161+
} else {
1162+
exps.add(ex);
1163+
}
1164+
}
1165+
1166+
boolean updated = false; // has the expression been modified?
1167+
1168+
// evaluate the impact of each Equal over the different types of Expressions
1169+
for (Iterator<Equals> iterEq = equals.iterator(); iterEq.hasNext(); ) {
1170+
Equals eq = iterEq.next();
1171+
Object eqValue = eq.right().fold();
1172+
boolean removeEquals = false;
1173+
1174+
// Equals OR NotEquals
1175+
for (NotEquals neq : notEquals) {
1176+
if (eq.left().semanticEquals(neq.left())) { // a = 2 OR a != ? -> ...
1177+
Integer comp = BinaryComparison.compare(eqValue, neq.right().fold());
1178+
if (comp != null) {
1179+
if (comp == 0) { // a = 2 OR a != 2 -> TRUE
1180+
return TRUE;
1181+
} else { // a = 2 OR a != 5 -> a != 5
1182+
removeEquals = true;
1183+
break;
1184+
}
1185+
}
1186+
}
1187+
}
1188+
if (removeEquals) {
1189+
iterEq.remove();
1190+
updated = true;
1191+
continue;
1192+
}
1193+
1194+
// Equals OR Range
1195+
for (int i = 0; i < ranges.size(); i ++) { // might modify list, so use index loop
1196+
Range range = ranges.get(i);
1197+
if (eq.left().semanticEquals(range.value())) {
1198+
Integer lowerComp = range.lower().foldable() ? BinaryComparison.compare(eqValue, range.lower().fold()) : null;
1199+
Integer upperComp = range.upper().foldable() ? BinaryComparison.compare(eqValue, range.upper().fold()) : null;
1200+
1201+
if (lowerComp != null && lowerComp == 0) {
1202+
if (!range.includeLower()) { // a = 2 OR 2 < a < ? -> 2 <= a < ?
1203+
ranges.set(i, new Range(range.source(), range.value(), range.lower(), true,
1204+
range.upper(), range.includeUpper()));
1205+
} // else : a = 2 OR 2 <= a < ? -> 2 <= a < ?
1206+
removeEquals = true; // update range with lower equality instead or simply superfluous
1207+
break;
1208+
} else if (upperComp != null && upperComp == 0) {
1209+
if (!range.includeUpper()) { // a = 2 OR ? < a < 2 -> ? < a <= 2
1210+
ranges.set(i, new Range(range.source(), range.value(), range.lower(), range.includeLower(),
1211+
range.upper(), true));
1212+
} // else : a = 2 OR ? < a <= 2 -> ? < a <= 2
1213+
removeEquals = true; // update range with upper equality instead
1214+
break;
1215+
} else if (lowerComp != null && upperComp != null) {
1216+
if (0 < lowerComp && upperComp < 0) { // a = 2 OR 1 < a < 3
1217+
removeEquals = true; // equality is superfluous
1218+
break;
1219+
}
1220+
}
1221+
}
1222+
}
1223+
if (removeEquals) {
1224+
iterEq.remove();
1225+
updated = true;
1226+
continue;
1227+
}
1228+
1229+
// Equals OR Inequality
1230+
for (int i = 0; i < inequalities.size(); i ++) {
1231+
BinaryComparison bc = inequalities.get(i);
1232+
if (eq.left().semanticEquals(bc.left())) {
1233+
Integer comp = BinaryComparison.compare(eqValue, bc.right().fold());
1234+
if (comp != null) {
1235+
if (bc instanceof GreaterThan || bc instanceof GreaterThanOrEqual) {
1236+
if (comp < 0) { // a = 1 OR a > 2 -> nop
1237+
continue;
1238+
} else if (comp == 0 && bc instanceof GreaterThan) { // a = 2 OR a > 2 -> a >= 2
1239+
inequalities.set(i, new GreaterThanOrEqual(bc.source(), bc.left(), bc.right()));
1240+
} // else (0 < comp || bc instanceof GreaterThanOrEqual) :
1241+
// a = 3 OR a > 2 -> a > 2; a = 2 OR a => 2 -> a => 2
1242+
1243+
removeEquals = true; // update range with equality instead or simply superfluous
1244+
break;
1245+
} else if (bc instanceof LessThan || bc instanceof LessThanOrEqual) {
1246+
if (comp > 0) { // a = 2 OR a < 1 -> nop
1247+
continue;
1248+
}
1249+
if (comp == 0 && bc instanceof LessThan) { // a = 2 OR a < 2 -> a <= 2
1250+
inequalities.set(i, new LessThanOrEqual(bc.source(), bc.left(), bc.right()));
1251+
} // else (comp < 0 || bc instanceof LessThanOrEqual) : a = 2 OR a < 3 -> a < 3; a = 2 OR a <= 2 -> a <= 2
1252+
removeEquals = true; // update range with equality instead or simply superfluous
1253+
break;
1254+
}
1255+
}
1256+
}
1257+
}
1258+
if (removeEquals) {
1259+
iterEq.remove();
1260+
updated = true;
1261+
}
10631262
}
10641263

1065-
return changed ? Predicates.combineAnd(CollectionUtils.combine(exps, equals, ranges)) : and;
1264+
return updated ? Predicates.combineOr(CollectionUtils.combine(exps, equals, notEquals, inequalities, ranges)) : or;
10661265
}
10671266
}
10681267

0 commit comments

Comments
 (0)