Skip to content

Commit 1a02445

Browse files
authored
SQL: Allow look-ahead resolution of aliases for WHERE clause (#38450)
Aliases defined in SELECT (Project or Aggregate) are now resolved in the following WHERE clause. The Analyzer has been enhanced to identify this rule and replace the field accordingly. Close #29983
1 parent 6ff4a8c commit 1a02445

File tree

3 files changed

+80
-2
lines changed

3 files changed

+80
-2
lines changed

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

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ protected Iterable<RuleExecutor<LogicalPlan>.Batch> batches() {
105105
new ResolveRefs(),
106106
new ResolveOrdinalInOrderByAndGroupBy(),
107107
new ResolveMissingRefs(),
108+
new ResolveFilterRefs(),
108109
new ResolveFunctions(),
109110
new ResolveAliases(),
110111
new ProjectedAggregations(),
@@ -762,6 +763,68 @@ private static UnresolvedAttribute resolveMetadataToMessage(UnresolvedAttribute
762763
}
763764
}
764765

766+
//
767+
// Resolve aliases defined in SELECT that are referred inside the WHERE clause:
768+
// SELECT int AS i FROM t WHERE i > 10
769+
//
770+
// As such, identify all project and aggregates that have a Filter child
771+
// and look at any resoled aliases that match and replace them.
772+
private class ResolveFilterRefs extends AnalyzeRule<LogicalPlan> {
773+
774+
@Override
775+
protected LogicalPlan rule(LogicalPlan plan) {
776+
if (plan instanceof Project) {
777+
Project p = (Project) plan;
778+
if (p.child() instanceof Filter) {
779+
Filter f = (Filter) p.child();
780+
Expression condition = f.condition();
781+
if (condition.resolved() == false && f.childrenResolved() == true) {
782+
Expression newCondition = replaceAliases(condition, p.projections());
783+
if (newCondition != condition) {
784+
return new Project(p.source(), new Filter(f.source(), f.child(), newCondition), p.projections());
785+
}
786+
}
787+
}
788+
}
789+
790+
if (plan instanceof Aggregate) {
791+
Aggregate a = (Aggregate) plan;
792+
if (a.child() instanceof Filter) {
793+
Filter f = (Filter) a.child();
794+
Expression condition = f.condition();
795+
if (condition.resolved() == false && f.childrenResolved() == true) {
796+
Expression newCondition = replaceAliases(condition, a.aggregates());
797+
if (newCondition != condition) {
798+
return new Aggregate(a.source(), new Filter(f.source(), f.child(), newCondition), a.groupings(),
799+
a.aggregates());
800+
}
801+
}
802+
}
803+
}
804+
805+
return plan;
806+
}
807+
808+
private Expression replaceAliases(Expression condition, List<? extends NamedExpression> named) {
809+
List<Alias> aliases = new ArrayList<>();
810+
named.forEach(n -> {
811+
if (n instanceof Alias) {
812+
aliases.add((Alias) n);
813+
}
814+
});
815+
816+
return condition.transformDown(u -> {
817+
boolean qualified = u.qualifier() != null;
818+
for (Alias alias : aliases) {
819+
if (qualified ? Objects.equals(alias.qualifiedName(), u.qualifiedName()) : Objects.equals(alias.name(), u.name())) {
820+
return alias;
821+
}
822+
}
823+
return u;
824+
}, UnresolvedAttribute.class);
825+
}
826+
}
827+
765828
// to avoid creating duplicate functions
766829
// this rule does two iterations
767830
// 1. collect all functions

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77

88
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
99
import org.elasticsearch.xpack.sql.expression.gen.script.ScriptTemplate;
10-
import org.elasticsearch.xpack.sql.tree.Source;
1110
import org.elasticsearch.xpack.sql.tree.NodeInfo;
11+
import org.elasticsearch.xpack.sql.tree.Source;
1212
import org.elasticsearch.xpack.sql.type.DataType;
1313
import org.elasticsearch.xpack.sql.type.EsField;
1414

@@ -75,6 +75,10 @@ public String qualifier() {
7575
return qualifier;
7676
}
7777

78+
public String qualifiedName() {
79+
return qualifier == null ? name() : qualifier + "." + name();
80+
}
81+
7882
@Override
7983
public Nullability nullable() {
8084
return child.nullable();

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -638,5 +638,16 @@ public void testMaxOnKeywordGroupByHavingUnsupported() {
638638
assertEquals("1:52: HAVING filter is unsupported for function [MAX(keyword)]",
639639
error("SELECT MAX(keyword) FROM test GROUP BY text HAVING MAX(keyword) > 10"));
640640
}
641-
}
642641

642+
public void testProjectAliasInFilter() {
643+
accept("SELECT int AS i FROM test WHERE i > 10");
644+
}
645+
646+
public void testAggregateAliasInFilter() {
647+
accept("SELECT int AS i FROM test WHERE i > 10 GROUP BY i HAVING MAX(i) > 10");
648+
}
649+
650+
public void testProjectUnresolvedAliasInFilter() {
651+
assertEquals("1:8: Unknown column [tni]", error("SELECT tni AS i FROM test WHERE i > 10 GROUP BY i"));
652+
}
653+
}

0 commit comments

Comments
 (0)