[Fix](Nereids) Check bound status in analyze straight after bounding (#18581)

Probleam:
Dead loop cause of keep pushing analyze tasks into job stack. When doing analyze process and generate new operators, the same analyze rule would be pushed again, so it cause dead loop. And analyze process generate new operators when trying to bound order by key and aggregate function.

Solve:
We need to make it throw exception before complex analyze and rewrite process, so checking whether all expressions being bound should be done twice. One is done after bounding all expression, another is done after all analyze process in case of generate new expressions and new operators.

Example:
Cases were put in file: regression-test/suites/nereids_p0/except/test_bound_exception.groovy
This commit is contained in:
LiBinfeng
2023-04-20 18:50:13 +08:00
committed by GitHub
parent 8e2146f48c
commit 668c681fbc
6 changed files with 148 additions and 12 deletions

View File

@ -24,6 +24,7 @@ import org.apache.doris.nereids.rules.analysis.AdjustAggregateNullableForEmptySe
import org.apache.doris.nereids.rules.analysis.BindExpression;
import org.apache.doris.nereids.rules.analysis.BindRelation;
import org.apache.doris.nereids.rules.analysis.CheckAnalysis;
import org.apache.doris.nereids.rules.analysis.CheckBound;
import org.apache.doris.nereids.rules.analysis.CheckPolicy;
import org.apache.doris.nereids.rules.analysis.FillUpMissingSlots;
import org.apache.doris.nereids.rules.analysis.NormalizeRepeat;
@ -53,6 +54,9 @@ public class NereidsAnalyzer extends BatchRewriteJob {
new UserAuthentication(),
new BindExpression()
),
bottomUp(
new CheckBound()
),
bottomUp(
new ProjectToGlobalAggregate(),
// this rule check's the logicalProject node's isDisinct property

View File

@ -85,6 +85,7 @@ public enum RuleType {
// check analysis rule
CHECK_AGGREGATE_ANALYSIS(RuleTypeClass.CHECK),
CHECK_ANALYSIS(RuleTypeClass.CHECK),
CHECK_BOUND(RuleTypeClass.CHECK),
CHECK_DATATYPES(RuleTypeClass.CHECK),
// rewrite rules

View File

@ -78,17 +78,19 @@ public class CheckAnalysis implements AnalysisRuleFactory {
.flatMap(Set::stream)
.collect(Collectors.toSet());
if (!unbounds.isEmpty()) {
throw new AnalysisException(String.format("unbounded object %s.",
StringUtils.join(unbounds.stream()
.map(unbound -> {
if (unbound instanceof UnboundSlot) {
return ((UnboundSlot) unbound).toSql();
} else if (unbound instanceof UnboundFunction) {
return ((UnboundFunction) unbound).toSql();
}
return unbound.toString();
})
.collect(Collectors.toSet()), ", ")));
throw new AnalysisException(String.format("unbounded object %s in %s clause.",
StringUtils.join(unbounds.stream()
.map(unbound -> {
if (unbound instanceof UnboundSlot) {
return ((UnboundSlot) unbound).toSql();
} else if (unbound instanceof UnboundFunction) {
return ((UnboundFunction) unbound).toSql();
}
return unbound.toString();
})
.collect(Collectors.toSet()), ", "),
plan.getType().toString().substring("LOGICAL_".length())
));
}
}

View File

@ -0,0 +1,74 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.doris.nereids.rules.analysis;
import org.apache.doris.nereids.analyzer.Unbound;
import org.apache.doris.nereids.analyzer.UnboundFunction;
import org.apache.doris.nereids.analyzer.UnboundSlot;
import org.apache.doris.nereids.exceptions.AnalysisException;
import org.apache.doris.nereids.rules.Rule;
import org.apache.doris.nereids.rules.RuleType;
import org.apache.doris.nereids.trees.plans.Plan;
import com.google.common.collect.ImmutableList;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Check bound rule to check semantic correct after bounding of expression by Nereids.
* Also give operator information without LOGICAL_
*/
public class CheckBound implements AnalysisRuleFactory {
@Override
public List<Rule> buildRules() {
return ImmutableList.of(
RuleType.CHECK_BOUND.build(
any().then(plan -> {
checkBound(plan);
return null;
})
)
);
}
private void checkBound(Plan plan) {
Set<Unbound> unbounds = plan.getExpressions().stream()
.<Set<Unbound>>map(e -> e.collect(Unbound.class::isInstance))
.flatMap(Set::stream)
.collect(Collectors.toSet());
if (!unbounds.isEmpty()) {
throw new AnalysisException(String.format("unbounded object %s in %s clause.",
StringUtils.join(unbounds.stream()
.map(unbound -> {
if (unbound instanceof UnboundSlot) {
return ((UnboundSlot) unbound).toSql();
} else if (unbound instanceof UnboundFunction) {
return ((UnboundFunction) unbound).toSql();
}
return unbound.toString();
})
.collect(Collectors.toSet()), ", "),
plan.getType().toString().substring("LOGICAL_".length())
));
}
}
}