Skip to content

Commit f04b4cb

Browse files
authored
SQL: Optimisation fixes for conjunction merges (#50703) (#50933)
* SQL: Optimisation fixes for conjunction merges This commit fixes the following issues around the way comparisions are merged with ranges in conjunctions: * the decision to include the equality of the lower limit is corrected; * the selection of the upper limit is corrected to use the upper bound of the range; * the list of terms in the conjunction is sorted to have the ranges at the bottom; this allows subsequent binary comarisions to find compatible ranges and potentially be merged away. The end guarantee being that the optimisation takes place irrespective of the order of the conjunction terms in the statement. Some comments are also corrected. * adress review observation on anon. comparator Replace anonymous comparator of split AND Expressions with a lambda. (cherry picked from commit 9828cb1)
1 parent 2dc23bd commit f04b4cb

File tree

3 files changed

+72
-10
lines changed

3 files changed

+72
-10
lines changed

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/Expression.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
* In a SQL statement, an Expression is whatever a user specifies inside an
2020
* action, so for instance:
2121
*
22-
* {@code SELECT a, b, MAX(c, d) FROM i}
22+
* {@code SELECT a, b, ABS(c) FROM i}
2323
*
2424
* a, b, ABS(c), and i are all Expressions, with ABS(c) being a Function
2525
* (which is a type of expression) with a single child, c.

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

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,7 @@ protected LogicalPlan rule(OrderBy ob) {
397397
return ob;
398398
}
399399
}
400-
400+
401401
static class PruneOrderByForImplicitGrouping extends OptimizerRule<OrderBy> {
402402

403403
@Override
@@ -1090,7 +1090,18 @@ private Expression combine(And and) {
10901090

10911091
boolean changed = false;
10921092

1093-
for (Expression ex : Predicates.splitAnd(and)) {
1093+
List<Expression> andExps = Predicates.splitAnd(and);
1094+
// Ranges need to show up before BinaryComparisons in list, to allow the latter be optimized away into a Range, if possible
1095+
andExps.sort((o1, o2) -> {
1096+
if (o1 instanceof Range && o2 instanceof Range) {
1097+
return 0; // keep ranges' order
1098+
} else if (o1 instanceof Range || o2 instanceof Range) {
1099+
return o2 instanceof Range ? 1 : -1;
1100+
} else {
1101+
return 0; // keep non-ranges' order
1102+
}
1103+
});
1104+
for (Expression ex : andExps) {
10941105
if (ex instanceof Range) {
10951106
Range r = (Range) ex;
10961107
if (findExistingRange(r, ranges, true)) {
@@ -1215,9 +1226,9 @@ private static boolean findExistingRange(Range main, List<Range> ranges, boolean
12151226
lowerEq = comp == 0 && main.includeLower() == other.includeLower();
12161227
// AND
12171228
if (conjunctive) {
1218-
// (2 < a < 3) AND (1 < a < 3) -> (1 < a < 3)
1229+
// (2 < a < 3) AND (1 < a < 3) -> (2 < a < 3)
12191230
lower = comp > 0 ||
1220-
// (2 < a < 3) AND (2 < a <= 3) -> (2 < a < 3)
1231+
// (2 < a < 3) AND (2 <= a < 3) -> (2 < a < 3)
12211232
(comp == 0 && !main.includeLower() && other.includeLower());
12221233
}
12231234
// OR
@@ -1316,7 +1327,7 @@ private boolean findConjunctiveComparisonInRange(BinaryComparison main, List<Ran
13161327
ranges.remove(i);
13171328
ranges.add(i,
13181329
new Range(other.source(), other.value(),
1319-
main.right(), lowerEq ? true : other.includeLower(),
1330+
main.right(), lowerEq ? false : main instanceof GreaterThanOrEqual,
13201331
other.upper(), other.includeUpper()));
13211332
}
13221333

@@ -1325,19 +1336,19 @@ private boolean findConjunctiveComparisonInRange(BinaryComparison main, List<Ran
13251336
}
13261337
}
13271338
} else if (main instanceof LessThan || main instanceof LessThanOrEqual) {
1328-
if (other.lower().foldable()) {
1329-
Integer comp = BinaryComparison.compare(value, other.lower().fold());
1339+
if (other.upper().foldable()) {
1340+
Integer comp = BinaryComparison.compare(value, other.upper().fold());
13301341
if (comp != null) {
13311342
// a < 2 AND (1 < a <= 2) -> 1 < a < 2
13321343
boolean upperEq = comp == 0 && other.includeUpper() && main instanceof LessThan;
13331344
// a < 2 AND (1 < a < 3) -> 1 < a < 2
1334-
boolean upper = comp > 0 || upperEq;
1345+
boolean upper = comp < 0 || upperEq;
13351346

13361347
if (upper) {
13371348
ranges.remove(i);
13381349
ranges.add(i, new Range(other.source(), other.value(),
13391350
other.lower(), other.includeLower(),
1340-
main.right(), upperEq ? true : other.includeUpper()));
1351+
main.right(), upperEq ? false : main instanceof LessThanOrEqual));
13411352
}
13421353

13431354
// found a match

x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/optimizer/OptimizerTests.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,6 +1012,57 @@ public void testCombineBinaryComparisonsInclude() {
10121012
assertEquals(FIVE, r.right());
10131013
}
10141014

1015+
// 2 < a AND (2 <= a < 3) -> 2 < a < 3
1016+
public void testCombineBinaryComparisonsAndRangeLower() {
1017+
FieldAttribute fa = getFieldAttribute();
1018+
1019+
GreaterThan gt = new GreaterThan(EMPTY, fa, TWO);
1020+
Range range = new Range(EMPTY, fa, TWO, true, THREE, false);
1021+
1022+
CombineBinaryComparisons rule = new CombineBinaryComparisons();
1023+
Expression exp = rule.rule(new And(EMPTY, gt, range));
1024+
assertEquals(Range.class, exp.getClass());
1025+
Range r = (Range)exp;
1026+
assertEquals(TWO, r.lower());
1027+
assertFalse(r.includeLower());
1028+
assertEquals(THREE, r.upper());
1029+
assertFalse(r.includeUpper());
1030+
}
1031+
1032+
// a < 4 AND (1 < a < 3) -> 1 < a < 3
1033+
public void testCombineBinaryComparisonsAndRangeUpper() {
1034+
FieldAttribute fa = getFieldAttribute();
1035+
1036+
LessThan lt = new LessThan(EMPTY, fa, FOUR);
1037+
Range range = new Range(EMPTY, fa, ONE, false, THREE, false);
1038+
1039+
CombineBinaryComparisons rule = new CombineBinaryComparisons();
1040+
Expression exp = rule.rule(new And(EMPTY, range, lt));
1041+
assertEquals(Range.class, exp.getClass());
1042+
Range r = (Range)exp;
1043+
assertEquals(ONE, r.lower());
1044+
assertFalse(r.includeLower());
1045+
assertEquals(THREE, r.upper());
1046+
assertFalse(r.includeUpper());
1047+
}
1048+
1049+
// a <= 2 AND (1 < a < 3) -> 1 < a <= 2
1050+
public void testCombineBinaryComparisonsAndRangeUpperEqual() {
1051+
FieldAttribute fa = getFieldAttribute();
1052+
1053+
LessThanOrEqual lte = new LessThanOrEqual(EMPTY, fa, TWO);
1054+
Range range = new Range(EMPTY, fa, ONE, false, THREE, false);
1055+
1056+
CombineBinaryComparisons rule = new CombineBinaryComparisons();
1057+
Expression exp = rule.rule(new And(EMPTY, lte, range));
1058+
assertEquals(Range.class, exp.getClass());
1059+
Range r = (Range)exp;
1060+
assertEquals(ONE, r.lower());
1061+
assertFalse(r.includeLower());
1062+
assertEquals(TWO, r.upper());
1063+
assertTrue(r.includeUpper());
1064+
}
1065+
10151066
// 3 <= a AND 4 < a AND a <= 7 AND a < 6 -> 4 < a < 6
10161067
public void testCombineMultipleBinaryComparisons() {
10171068
FieldAttribute fa = getFieldAttribute();

0 commit comments

Comments
 (0)