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
This commit is contained in:
parent
6ff4a8cfd5
commit
1a02445ae1
|
@ -105,6 +105,7 @@ public class Analyzer extends RuleExecutor<LogicalPlan> {
|
||||||
new ResolveRefs(),
|
new ResolveRefs(),
|
||||||
new ResolveOrdinalInOrderByAndGroupBy(),
|
new ResolveOrdinalInOrderByAndGroupBy(),
|
||||||
new ResolveMissingRefs(),
|
new ResolveMissingRefs(),
|
||||||
|
new ResolveFilterRefs(),
|
||||||
new ResolveFunctions(),
|
new ResolveFunctions(),
|
||||||
new ResolveAliases(),
|
new ResolveAliases(),
|
||||||
new ProjectedAggregations(),
|
new ProjectedAggregations(),
|
||||||
|
@ -762,6 +763,68 @@ public class Analyzer extends RuleExecutor<LogicalPlan> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Resolve aliases defined in SELECT that are referred inside the WHERE clause:
|
||||||
|
// SELECT int AS i FROM t WHERE i > 10
|
||||||
|
//
|
||||||
|
// As such, identify all project and aggregates that have a Filter child
|
||||||
|
// and look at any resoled aliases that match and replace them.
|
||||||
|
private class ResolveFilterRefs extends AnalyzeRule<LogicalPlan> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected LogicalPlan rule(LogicalPlan plan) {
|
||||||
|
if (plan instanceof Project) {
|
||||||
|
Project p = (Project) plan;
|
||||||
|
if (p.child() instanceof Filter) {
|
||||||
|
Filter f = (Filter) p.child();
|
||||||
|
Expression condition = f.condition();
|
||||||
|
if (condition.resolved() == false && f.childrenResolved() == true) {
|
||||||
|
Expression newCondition = replaceAliases(condition, p.projections());
|
||||||
|
if (newCondition != condition) {
|
||||||
|
return new Project(p.source(), new Filter(f.source(), f.child(), newCondition), p.projections());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plan instanceof Aggregate) {
|
||||||
|
Aggregate a = (Aggregate) plan;
|
||||||
|
if (a.child() instanceof Filter) {
|
||||||
|
Filter f = (Filter) a.child();
|
||||||
|
Expression condition = f.condition();
|
||||||
|
if (condition.resolved() == false && f.childrenResolved() == true) {
|
||||||
|
Expression newCondition = replaceAliases(condition, a.aggregates());
|
||||||
|
if (newCondition != condition) {
|
||||||
|
return new Aggregate(a.source(), new Filter(f.source(), f.child(), newCondition), a.groupings(),
|
||||||
|
a.aggregates());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return plan;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Expression replaceAliases(Expression condition, List<? extends NamedExpression> named) {
|
||||||
|
List<Alias> aliases = new ArrayList<>();
|
||||||
|
named.forEach(n -> {
|
||||||
|
if (n instanceof Alias) {
|
||||||
|
aliases.add((Alias) n);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return condition.transformDown(u -> {
|
||||||
|
boolean qualified = u.qualifier() != null;
|
||||||
|
for (Alias alias : aliases) {
|
||||||
|
if (qualified ? Objects.equals(alias.qualifiedName(), u.qualifiedName()) : Objects.equals(alias.name(), u.name())) {
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return u;
|
||||||
|
}, UnresolvedAttribute.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// to avoid creating duplicate functions
|
// to avoid creating duplicate functions
|
||||||
// this rule does two iterations
|
// this rule does two iterations
|
||||||
// 1. collect all functions
|
// 1. collect all functions
|
||||||
|
|
|
@ -7,8 +7,8 @@ package org.elasticsearch.xpack.sql.expression;
|
||||||
|
|
||||||
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
|
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
|
||||||
import org.elasticsearch.xpack.sql.expression.gen.script.ScriptTemplate;
|
import org.elasticsearch.xpack.sql.expression.gen.script.ScriptTemplate;
|
||||||
import org.elasticsearch.xpack.sql.tree.Source;
|
|
||||||
import org.elasticsearch.xpack.sql.tree.NodeInfo;
|
import org.elasticsearch.xpack.sql.tree.NodeInfo;
|
||||||
|
import org.elasticsearch.xpack.sql.tree.Source;
|
||||||
import org.elasticsearch.xpack.sql.type.DataType;
|
import org.elasticsearch.xpack.sql.type.DataType;
|
||||||
import org.elasticsearch.xpack.sql.type.EsField;
|
import org.elasticsearch.xpack.sql.type.EsField;
|
||||||
|
|
||||||
|
@ -75,6 +75,10 @@ public class Alias extends NamedExpression {
|
||||||
return qualifier;
|
return qualifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String qualifiedName() {
|
||||||
|
return qualifier == null ? name() : qualifier + "." + name();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Nullability nullable() {
|
public Nullability nullable() {
|
||||||
return child.nullable();
|
return child.nullable();
|
||||||
|
|
|
@ -638,5 +638,16 @@ public class VerifierErrorMessagesTests extends ESTestCase {
|
||||||
assertEquals("1:52: HAVING filter is unsupported for function [MAX(keyword)]",
|
assertEquals("1:52: HAVING filter is unsupported for function [MAX(keyword)]",
|
||||||
error("SELECT MAX(keyword) FROM test GROUP BY text HAVING MAX(keyword) > 10"));
|
error("SELECT MAX(keyword) FROM test GROUP BY text HAVING MAX(keyword) > 10"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testProjectAliasInFilter() {
|
||||||
|
accept("SELECT int AS i FROM test WHERE i > 10");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testAggregateAliasInFilter() {
|
||||||
|
accept("SELECT int AS i FROM test WHERE i > 10 GROUP BY i HAVING MAX(i) > 10");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testProjectUnresolvedAliasInFilter() {
|
||||||
|
assertEquals("1:8: Unknown column [tni]", error("SELECT tni AS i FROM test WHERE i > 10 GROUP BY i"));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue