Skip to content

Commit 7790cb5

Browse files
authored
SQL: Reduce number of ranges generated for comparisons (#30267)
* SQL: Reduce number of ranges generated for comparisons Rewrote optimization rule for combining ranges by improving the detection of binary comparisons in a tree to better combine them in a range, regardless of their place inside an expression. Additionally, improve the comparisons of Numbers of different types Also, improve reassembly of conjunction/disjunction into balanced trees. Do not promote BinaryComparisons to Ranges since it introduces NULL boundaries and thus a corner-case that needs too much handling Compare BinaryComparisons directly between themselves and to Ranges Fix #30017
1 parent f0e9267 commit 7790cb5

File tree

8 files changed

+1154
-47
lines changed

8 files changed

+1154
-47
lines changed

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

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import org.elasticsearch.xpack.sql.expression.Expression;
1010
import org.elasticsearch.xpack.sql.tree.Location;
1111
import org.elasticsearch.xpack.sql.type.DataType;
12-
import org.elasticsearch.xpack.sql.type.DataTypes;
1312

1413
// marker class to indicate operations that rely on values
1514
public abstract class BinaryComparison extends BinaryOperator {
@@ -33,11 +32,42 @@ public DataType dataType() {
3332
return DataType.BOOLEAN;
3433
}
3534

35+
/**
36+
* Compares two expression arguments (typically Numbers), if possible.
37+
* Otherwise returns null (the arguments are not comparable or at least
38+
* one of them is null).
39+
*/
3640
@SuppressWarnings({ "rawtypes", "unchecked" })
37-
static Integer compare(Object left, Object right) {
38-
if (left instanceof Comparable && right instanceof Comparable) {
39-
return Integer.valueOf(((Comparable) left).compareTo(right));
41+
public static Integer compare(Object l, Object r) {
42+
// typical number comparison
43+
if (l instanceof Number && r instanceof Number) {
44+
return compare((Number) l, (Number) r);
4045
}
46+
47+
if (l instanceof Comparable && r instanceof Comparable) {
48+
try {
49+
return Integer.valueOf(((Comparable) l).compareTo(r));
50+
} catch (ClassCastException cce) {
51+
// when types are not compatible, cce is thrown
52+
// fall back to null
53+
return null;
54+
}
55+
}
56+
4157
return null;
4258
}
59+
60+
static Integer compare(Number l, Number r) {
61+
if (l instanceof Double || r instanceof Double) {
62+
return Double.compare(l.doubleValue(), r.doubleValue());
63+
}
64+
if (l instanceof Float || r instanceof Float) {
65+
return Float.compare(l.floatValue(), r.floatValue());
66+
}
67+
if (l instanceof Long || r instanceof Long) {
68+
return Long.compare(l.longValue(), r.longValue());
69+
}
70+
71+
return Integer.valueOf(Integer.compare(l.intValue(), r.intValue()));
72+
}
4373
}

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

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@
99
import org.elasticsearch.xpack.sql.plan.logical.LogicalPlan;
1010

1111
import java.util.ArrayList;
12-
import java.util.Collections;
1312
import java.util.List;
13+
import java.util.function.BiFunction;
14+
15+
import static java.util.Collections.emptyList;
16+
import static java.util.Collections.singletonList;
1417

1518
public abstract class Predicates {
1619

@@ -22,7 +25,7 @@ public static List<Expression> splitAnd(Expression exp) {
2225
list.addAll(splitAnd(and.right()));
2326
return list;
2427
}
25-
return Collections.singletonList(exp);
28+
return singletonList(exp);
2629
}
2730

2831
public static List<Expression> splitOr(Expression exp) {
@@ -33,15 +36,51 @@ public static List<Expression> splitOr(Expression exp) {
3336
list.addAll(splitOr(or.right()));
3437
return list;
3538
}
36-
return Collections.singletonList(exp);
39+
return singletonList(exp);
3740
}
3841

3942
public static Expression combineOr(List<Expression> exps) {
40-
return exps.stream().reduce((l, r) -> new Or(l.location(), l, r)).orElse(null);
43+
return combine(exps, (l, r) -> new Or(l.location(), l, r));
4144
}
4245

4346
public static Expression combineAnd(List<Expression> exps) {
44-
return exps.stream().reduce((l, r) -> new And(l.location(), l, r)).orElse(null);
47+
return combine(exps, (l, r) -> new And(l.location(), l, r));
48+
}
49+
50+
/**
51+
* Build a binary 'pyramid' from the given list:
52+
* <pre>
53+
* AND
54+
* / \
55+
* AND AND
56+
* / \ / \
57+
* A B C D
58+
* </pre>
59+
*
60+
* using the given combiner.
61+
*
62+
* While a bit longer, this method creates a balanced tree as oppose to a plain
63+
* recursive approach which creates an unbalanced one (either to the left or right).
64+
*/
65+
private static Expression combine(List<Expression> exps, BiFunction<Expression, Expression, Expression> combiner) {
66+
if (exps.isEmpty()) {
67+
return null;
68+
}
69+
70+
// clone the list (to modify it)
71+
List<Expression> result = new ArrayList<>(exps);
72+
73+
while (result.size() > 1) {
74+
// combine (in place) expressions in pairs
75+
// NB: this loop modifies the list (just like an array)
76+
for (int i = 0; i < result.size() - 1; i++) {
77+
Expression l = result.remove(i);
78+
Expression r = result.remove(i);
79+
result.add(i, combiner.apply(l, r));
80+
}
81+
}
82+
83+
return result.get(0);
4584
}
4685

4786
public static List<Expression> inCommon(List<Expression> l, List<Expression> r) {
@@ -53,7 +92,7 @@ public static List<Expression> inCommon(List<Expression> l, List<Expression> r)
5392
}
5493
}
5594
}
56-
return common.isEmpty() ? Collections.emptyList() : common;
95+
return common.isEmpty() ? emptyList() : common;
5796
}
5897

5998
public static List<Expression> subtract(List<Expression> from, List<Expression> r) {
@@ -65,7 +104,7 @@ public static List<Expression> subtract(List<Expression> from, List<Expression>
65104
}
66105
}
67106
}
68-
return diff.isEmpty() ? Collections.emptyList() : diff;
107+
return diff.isEmpty() ? emptyList() : diff;
69108
}
70109

71110

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

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import org.elasticsearch.xpack.sql.tree.Location;
1010
import org.elasticsearch.xpack.sql.tree.NodeInfo;
1111
import org.elasticsearch.xpack.sql.type.DataType;
12-
import org.elasticsearch.xpack.sql.type.DataTypes;
1312

1413
import java.util.Arrays;
1514
import java.util.List;
@@ -66,11 +65,19 @@ public boolean includeUpper() {
6665

6766
@Override
6867
public boolean foldable() {
69-
return value.foldable() && lower.foldable() && upper.foldable();
68+
if (lower.foldable() && upper.foldable()) {
69+
return areBoundariesInvalid() || value.foldable();
70+
}
71+
72+
return false;
7073
}
7174

7275
@Override
7376
public Object fold() {
77+
if (areBoundariesInvalid()) {
78+
return Boolean.FALSE;
79+
}
80+
7481
Object val = value.fold();
7582
Integer lowerCompare = BinaryComparison.compare(lower.fold(), val);
7683
Integer upperCompare = BinaryComparison.compare(val, upper().fold());
@@ -79,6 +86,16 @@ public Object fold() {
7986
return lowerComparsion && upperComparsion;
8087
}
8188

89+
/**
90+
* Check whether the boundaries are invalid ( upper < lower) or not.
91+
* If they do, the value does not have to be evaluate.
92+
*/
93+
private boolean areBoundariesInvalid() {
94+
Integer compare = BinaryComparison.compare(lower.fold(), upper.fold());
95+
// upper < lower OR upper == lower and the range doesn't contain any equals
96+
return compare != null && (compare > 0 || (compare == 0 && (!includeLower || !includeUpper)));
97+
}
98+
8299
@Override
83100
public boolean nullable() {
84101
return value.nullable() && lower.nullable() && upper.nullable();
@@ -122,4 +139,4 @@ public String toString() {
122139
sb.append(upper);
123140
return sb.toString();
124141
}
125-
}
142+
}

0 commit comments

Comments
 (0)