Skip to content

Commit aba2f63

Browse files
author
Andras Palinkas
committed
Resolve references recursively
Code simplification in the AttributeMap
1 parent f631008 commit aba2f63

File tree

8 files changed

+127
-22
lines changed

8 files changed

+127
-22
lines changed

x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/expression/AttributeMap.java

+32-10
Original file line numberDiff line numberDiff line change
@@ -238,30 +238,52 @@ public boolean isEmpty() {
238238
@Override
239239
public boolean containsKey(Object key) {
240240
if (key instanceof NamedExpression) {
241-
return delegate.keySet().contains(new AttributeWrapper(((NamedExpression) key).toAttribute()));
241+
return delegate.containsKey(new AttributeWrapper(((NamedExpression) key).toAttribute()));
242242
}
243243
return false;
244244
}
245245

246246
@Override
247247
public boolean containsValue(Object value) {
248-
return delegate.values().contains(value);
248+
return delegate.containsValue(value);
249249
}
250250

251+
/**
252+
* @param key {@link NamedExpression} to look up
253+
* @return Looks up the key in the AttributeMap recursively, meaning if the value for this key
254+
* is another key in the map, it will follow it. It will return the final value or null if the key
255+
* does not exist in the map.
256+
*/
251257
@Override
252258
public E get(Object key) {
253-
if (key instanceof NamedExpression) {
254-
return delegate.get(new AttributeWrapper(((NamedExpression) key).toAttribute()));
255-
}
256-
return null;
259+
return getOrDefault(key, null);
257260
}
258261

262+
/**
263+
* @param key {@link NamedExpression} to look up
264+
* @return Looks up the key in the AttributeMap recursively, meaning if the value for this key
265+
* is another key in the map, it will follow it. It will return the final value or the
266+
* specified default value if the key does not exist in the map.
267+
*/
259268
@Override
260269
public E getOrDefault(Object key, E defaultValue) {
261-
E e;
262-
return (((e = get(key)) != null) || containsKey(key))
263-
? e
264-
: defaultValue;
270+
E candidate = defaultValue;
271+
E value = null;
272+
while (((value = lookup(key)) != null || containsKey(key)) && value != key) {
273+
key = candidate = value;
274+
}
275+
return candidate;
276+
}
277+
278+
/**
279+
* @param key {@link NamedExpression} to look up
280+
* @return Result of the non-recursive lookup of the key in the map.
281+
*/
282+
private E lookup(Object key) {
283+
if (key instanceof NamedExpression) {
284+
return delegate.get(new AttributeWrapper(((NamedExpression) key).toAttribute()));
285+
}
286+
return null;
265287
}
266288

267289
@Override

x-pack/plugin/ql/src/test/java/org/elasticsearch/xpack/ql/expression/AttributeMapTests.java

+31
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import java.util.Set;
1818

1919
import static java.util.stream.Collectors.toList;
20+
import static org.elasticsearch.xpack.ql.TestUtils.fieldAttribute;
21+
import static org.elasticsearch.xpack.ql.TestUtils.of;
2022
import static org.hamcrest.Matchers.arrayContaining;
2123
import static org.hamcrest.Matchers.arrayWithSize;
2224
import static org.hamcrest.Matchers.contains;
@@ -61,6 +63,35 @@ public void testAttributeMapWithSameAliasesCanResolveAttributes() {
6163
assertTrue(newAttributeMap.get(param2.toAttribute()) == param2.child());
6264
}
6365

66+
public void testResolveRecursively() {
67+
AttributeMap.Builder<Object> builder = AttributeMap.builder();
68+
Attribute one = a("one");
69+
Attribute two = fieldAttribute("two", DataTypes.INTEGER);
70+
Attribute three = fieldAttribute("three", DataTypes.INTEGER);
71+
Alias threeAlias = new Alias(Source.EMPTY, "three_alias", three);
72+
Alias threeAliasAlias = new Alias(Source.EMPTY, "three_alias_alias", threeAlias);
73+
builder.put(one, of("one"));
74+
builder.put(two, "two");
75+
builder.put(three, of("three"));
76+
builder.put(threeAlias.toAttribute(), threeAlias.child());
77+
builder.put(threeAliasAlias.toAttribute(), threeAliasAlias.child());
78+
AttributeMap<Object> map = builder.build();
79+
80+
assertEquals(of("one"), map.get(one));
81+
assertEquals(map.get(one), map.getOrDefault(one, null));
82+
assertEquals("two", map.get(two));
83+
assertEquals(map.get(two), map.getOrDefault(two, null));
84+
assertEquals(of("three"), map.get(three));
85+
assertEquals(map.get(three), map.getOrDefault(three, null));
86+
assertEquals(map.get(three), map.getOrDefault(threeAlias, null));
87+
assertEquals(map.get(three), map.get(threeAlias));
88+
assertEquals(map.get(three), map.getOrDefault(threeAliasAlias, null));
89+
assertEquals(map.get(three), map.get(threeAliasAlias));
90+
Attribute four = a("four");
91+
assertEquals("not found", map.getOrDefault(four, "not found"));
92+
assertNull(map.get(four));
93+
}
94+
6495
private Alias createIntParameterAlias(int index, int value) {
6596
Source source = new Source(1, index * 5, "?");
6697
Literal literal = new Literal(source, value, DataTypes.INTEGER);

x-pack/plugin/sql/qa/server/src/main/resources/subselect.sql-spec

+20-2
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,28 @@ basicGroupBy
1616
SELECT gender FROM (SELECT first_name AS f, last_name, gender FROM test_emp) GROUP BY gender ORDER BY gender ASC;
1717
basicGroupByAlias
1818
SELECT g FROM (SELECT first_name AS f, last_name, gender AS g FROM test_emp) GROUP BY g ORDER BY g ASC;
19-
// @AwaitsFix(bugUrl = "follow-up to https://github.com/elastic/elasticsearch/pull/67216")
20-
basicGroupByWithFilterByAlias-Ignore
19+
basicGroupByWithFilterByAlias
2120
SELECT g FROM (SELECT first_name AS f, last_name, gender AS g FROM test_emp) WHERE g IS NOT NULL GROUP BY g ORDER BY g ASC;
21+
basicGroupByRealiased
22+
SELECT g AS h FROM (SELECT first_name AS f, last_name, gender AS g FROM test_emp) GROUP BY g ORDER BY g DESC NULLS last;
23+
basicGroupByRealiasedTwice
24+
SELECT g AS h FROM (SELECT first_name AS f, last_name, gender AS g FROM test_emp) GROUP BY h ORDER BY h DESC NULLS last;
25+
basicOrderByRealiasedField
26+
SELECT g AS h FROM (SELECT first_name AS f, last_name, gender AS g FROM test_emp) ORDER BY g DESC NULLS first;
27+
28+
groupAndOrderByRealiasedExpression
29+
SELECT emp_group AS e, min_high_salary AS s
30+
FROM (
31+
SELECT emp_no % 2 AS emp_group, MIN(salary) AS min_high_salary
32+
FROM test_emp
33+
WHERE salary > 50000
34+
GROUP BY emp_group
35+
)
36+
ORDER BY e DESC;
2237

38+
// AwaitsFix: https://github.com/elastic/elasticsearch/issues/69758
39+
filterAfterGroupBy-Ignore
40+
SELECT s2 AS s3 FROM (SELECT s AS s2 FROM ( SELECT salary AS s FROM test_emp) GROUP BY s2) WHERE s2 < 5 ORDER BY s3 DESC NULLS last;
2341

2442
countAndComplexCondition
2543
SELECT COUNT(*) as c FROM (SELECT * FROM test_emp WHERE gender IS NOT NULL) WHERE ABS(salary) > 0 GROUP BY gender ORDER BY gender;

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/analysis/analyzer/Verifier.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -317,9 +317,10 @@ private static boolean checkGroupByOrder(LogicalPlan p, Set<Failure> localFailur
317317

318318
o.order().forEach(oe -> {
319319
Expression e = oe.child();
320+
e = attributeRefs.getOrDefault(e, e);
320321

321322
// aggregates are allowed
322-
if (Functions.isAggregate(attributeRefs.getOrDefault(e, e))) {
323+
if (Functions.isAggregate(e)) {
323324
return;
324325
}
325326

@@ -341,7 +342,7 @@ private static boolean checkGroupByOrder(LogicalPlan p, Set<Failure> localFailur
341342
//
342343
// Also, make sure to compare attributes directly
343344
if (e.anyMatch(expression -> Expressions.anyMatch(groupingAndMatchingAggregatesAliases,
344-
g -> expression.semanticEquals(expression instanceof Attribute ? Expressions.attribute(g) : g)))) {
345+
g -> expression.semanticEquals(attributeRefs.getOrDefault(Expressions.attribute(g), g))))) {
345346
return;
346347
}
347348

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,8 @@ protected Iterable<RuleExecutor<LogicalPlan>.Batch> batches() {
131131
);
132132

133133
Batch refs = new Batch("Replace References", Limiter.ONCE,
134-
new ReplaceReferenceAttributeWithSource()
135-
);
134+
new ReplaceReferenceAttributeWithSource()
135+
);
136136

137137
Batch operators = new Batch("Operator Optimization",
138138
// combining
@@ -222,7 +222,7 @@ public LogicalPlan apply(LogicalPlan plan) {
222222
AttributeMap.Builder<Expression> builder = AttributeMap.builder();
223223
// collect aliases
224224
plan.forEachExpressionUp(Alias.class, a -> builder.put(a.toAttribute(), a.child()));
225-
final Map<Attribute, Expression> collectRefs = builder.build();
225+
final AttributeMap<Expression> collectRefs = builder.build();
226226
java.util.function.Function<ReferenceAttribute, Expression> replaceReference = r -> collectRefs.getOrDefault(r, r);
227227

228228
plan = plan.transformUp(p -> {

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/planner/QueryFolder.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -600,10 +600,12 @@ else if (target.foldable()) {
600600
else {
601601
GroupByKey matchingGroup = null;
602602
if (groupingContext != null) {
603-
matchingGroup = groupingContext.groupFor(target);
603+
final Expression resolvedTarget = queryC.aliases().getOrDefault(target, target);
604+
matchingGroup = groupingContext.groupFor(resolvedTarget);
604605
Check.notNull(matchingGroup, "Cannot find group [{}]", Expressions.name(ne));
605606

606-
queryC = queryC.addColumn(new GroupByRef(matchingGroup.id(), null, isDateBased(ne.dataType())), id);
607+
queryC = queryC.addColumn(
608+
new GroupByRef(matchingGroup.id(), null, isDateBased(ne.dataType())), Expressions.id(resolvedTarget));
607609
}
608610
// fallback
609611
else {

x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/analysis/analyzer/VerifierErrorMessagesTests.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,14 @@ public void testGroupByOrderByScalarOverNonGrouped_WithHaving() {
517517

518518
public void testGroupByHavingNonGrouped() {
519519
assertEquals("1:48: Cannot use HAVING filter on non-aggregate [int]; use WHERE instead",
520-
error("SELECT AVG(int) FROM test GROUP BY text HAVING int > 10"));
520+
error("SELECT AVG(int) FROM test GROUP BY bool HAVING int > 10"));
521+
accept("SELECT AVG(int) FROM test GROUP BY bool HAVING AVG(int) > 2");
522+
}
523+
524+
@AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/69758")
525+
public void testGroupByWhereSubselect() {
526+
accept("SELECT b, a FROM (SELECT bool as b, AVG(int) as a FROM test GROUP BY bool) WHERE b = false");
527+
accept("SELECT b, a FROM (SELECT bool as b, AVG(int) as a FROM test GROUP BY bool HAVING AVG(int) > 2) WHERE b = false");
521528
}
522529

523530
public void testGroupByAggregate() {

x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/planner/QueryTranslatorTests.java

+26-2
Original file line numberDiff line numberDiff line change
@@ -2599,8 +2599,7 @@ public void testSubqueryFilterOrderByAlias() throws Exception {
25992599
"WHERE i IS NOT NULL " +
26002600
"ORDER BY i");
26012601
}
2602-
2603-
@AwaitsFix(bugUrl = "follow-up to https://github.com/elastic/elasticsearch/pull/67216")
2602+
26042603
public void testSubqueryGroupByFilterAndOrderByByAlias() throws Exception {
26052604
PhysicalPlan p = optimizeAndPlan("SELECT i FROM " +
26062605
"( SELECT int AS i FROM test ) " +
@@ -2658,4 +2657,29 @@ public void testSubqueryWithAliasOrderByAlias() throws Exception {
26582657
"( SELECT int AS i FROM test ) AS s " +
26592658
"ORDER BY s.i > 10");
26602659
}
2660+
2661+
public void testReferenceResolutionInSubqueries() {
2662+
optimizeAndPlan("SELECT i AS j FROM ( SELECT int AS i FROM test) ORDER BY j");
2663+
optimizeAndPlan("SELECT j AS k FROM (SELECT i AS j FROM ( SELECT int AS i FROM test)) ORDER BY k");
2664+
optimizeAndPlan("SELECT int_group AS g, min_date AS d " +
2665+
"FROM (" +
2666+
" SELECT int % 2 AS int_group, MIN(date) AS min_date " +
2667+
" FROM test WHERE date > '1970-01-01'::datetime GROUP BY int_group" +
2668+
") " +
2669+
"ORDER BY d DESC");
2670+
optimizeAndPlan("SELECT int_group AS g, min_date AS d " +
2671+
"FROM (" +
2672+
" SELECT int % 2 AS int_group, MIN(date) AS min_date " +
2673+
" FROM test WHERE date > '1970-01-01'::datetime GROUP BY int_group " +
2674+
")" +
2675+
"ORDER BY g DESC");
2676+
optimizeAndPlan("SELECT i AS j FROM ( SELECT int AS i FROM test) GROUP BY j");
2677+
optimizeAndPlan("SELECT j AS k FROM (SELECT i AS j FROM ( SELECT int AS i FROM test)) GROUP BY k");
2678+
optimizeAndPlan("SELECT g FROM (SELECT date AS f, int AS g FROM test) WHERE g IS NOT NULL GROUP BY g ORDER BY g ASC");
2679+
}
2680+
2681+
@AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/69758")
2682+
public void testFilterAfterGroupBy() {
2683+
optimizeAndPlan("SELECT j AS k FROM (SELECT i AS j FROM ( SELECT int AS i FROM test) GROUP BY j) WHERE j < 5");
2684+
}
26612685
}

0 commit comments

Comments
 (0)