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:
Costin Leau 2019-02-06 12:08:32 +02:00 committed by GitHub
parent 6ff4a8cfd5
commit 1a02445ae1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 80 additions and 2 deletions

View File

@ -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

View File

@ -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();

View File

@ -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"));
}
}