This is an automated email from the ASF dual-hosted git repository. morningman pushed a commit to branch dev-1.0.1 in repository https://gitbox.apache.org/repos/asf/incubator-doris.git
commit fd2375a59bea116098604912a6605423559b0966 Author: morrySnow <101034200+morrys...@users.noreply.github.com> AuthorDate: Fri Jun 3 17:50:10 2022 +0800 [fix] (planner) slot nullable does not set correctly when plan outer join with inline view (#9927) - set inline view's slot descriptor to nullable in register column ref - propagate slot nullable when generate inline view's query node in SingleNodePlanner --- .../java/org/apache/doris/analysis/Analyzer.java | 9 ++- .../org/apache/doris/analysis/InlineViewRef.java | 8 ++- .../apache/doris/planner/SingleNodePlanner.java | 9 +++ .../test_outer_join_with_inline_view.out | 13 ++++ .../test_outer_join_with_inline_view.groovy | 72 ++++++++++++++++++++++ 5 files changed, 108 insertions(+), 3 deletions(-) diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/Analyzer.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/Analyzer.java index b7da3f004b..c3e4e66984 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/Analyzer.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/Analyzer.java @@ -737,12 +737,19 @@ public class Analyzer { String key = d.getAlias() + "." + col.getName(); SlotDescriptor result = slotRefMap.get(key); if (result != null) { + // this is a trick to set slot as nullable when slot is on inline view + // When analyze InlineViewRef, we first generate sMap and baseTblSmap and then analyze join. + // We have already registered column ref at that time, but we did not know + // whether inline view is outer joined. So we have to check it and set slot as nullable here. + if (isOuterJoined(d.getId())) { + result.setIsNullable(true); + } result.setMultiRef(true); return result; } result = globalState.descTbl.addSlotDescriptor(d); result.setColumn(col); - if (col.isAllowNull() || globalState.outerJoinedTupleIds.containsKey(d.getId())) { + if (col.isAllowNull() || isOuterJoined(d.getId())) { result.setIsNullable(true); } else { result.setIsNullable(false); diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/InlineViewRef.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/InlineViewRef.java index a8ae56b61c..7bf43b78b6 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/InlineViewRef.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/InlineViewRef.java @@ -199,7 +199,7 @@ public class InlineViewRef extends TableRef { materializedTupleIds.add(desc.getId()); } - // create smap_ and baseTblSmap_ and register auxiliary eq predicates between our + // create sMap and baseTblSmap and register auxiliary eq predicates between our // tuple descriptor's slots and our *unresolved* select list exprs; // we create these auxiliary predicates so that the analyzer can compute the value // transfer graph through this inline view correctly (ie, predicates can get @@ -226,10 +226,14 @@ public class InlineViewRef extends TableRef { LOG.debug("inline view " + getUniqueAlias() + " baseTblSmap: " + baseTblSmap.debugString()); } - // anlayzeLateralViewRefs + // analyzeLateralViewRefs analyzeLateralViewRef(analyzer); // Now do the remaining join analysis + // In general, we should do analyze join before do RegisterColumnRef. However, We cannot move analyze join + // before generate sMap and baseTblSmap, because generate sMap and baseTblSmap will register all column refs + // in the inline view. If inline view is on right side of left semi join, exception will be thrown. + // Instead, we do a little trick in RegisterColumnRef to avoid this problem. analyzeJoin(analyzer); } diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/SingleNodePlanner.java b/fe/fe-core/src/main/java/org/apache/doris/planner/SingleNodePlanner.java index 5236776f51..a3fe3345c5 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/planner/SingleNodePlanner.java +++ b/fe/fe-core/src/main/java/org/apache/doris/planner/SingleNodePlanner.java @@ -1375,6 +1375,15 @@ public class SingleNodePlanner { List<Expr> nullableRhs = TupleIsNullPredicate.wrapExprs( outputSmap.getRhs(), rootNode.getTupleIds(), analyzer); outputSmap = new ExprSubstitutionMap(outputSmap.getLhs(), nullableRhs); + // When we process outer join with inline views, we set slot descriptor of inline view to nullable firstly. + // When we generate plan, we remove inline view, so the upper node's input is inline view's child. + // So we need to set slot descriptor of inline view's child to nullable to ensure consistent behavior + // with BaseTable. + for (TupleId tupleId : rootNode.getTupleIds()) { + for (SlotDescriptor slotDescriptor : analyzer.getTupleDesc(tupleId).getMaterializedSlots()) { + slotDescriptor.setIsNullable(true); + } + } } // Set output smap of rootNode *before* creating a SelectNode for proper resolution. rootNode.setOutputSmap(outputSmap); diff --git a/regression-test/data/correctness/test_outer_join_with_inline_view.out b/regression-test/data/correctness/test_outer_join_with_inline_view.out new file mode 100644 index 0000000000..52a9fc4158 --- /dev/null +++ b/regression-test/data/correctness/test_outer_join_with_inline_view.out @@ -0,0 +1,13 @@ +-- This file is automatically generated. You should know what you did if you want to edit this +-- !select_with_order_by -- +1 1 1 1 +2 2 2 2 +3 3 \N \N +4 4 \N \N + +-- !select_with_agg_in_inline_view -- +1 1 1 1 +2 2 2 1 +3 3 \N \N +4 4 \N \N + diff --git a/regression-test/suites/correctness/test_outer_join_with_inline_view.groovy b/regression-test/suites/correctness/test_outer_join_with_inline_view.groovy new file mode 100644 index 0000000000..1a354ab0e3 --- /dev/null +++ b/regression-test/suites/correctness/test_outer_join_with_inline_view.groovy @@ -0,0 +1,72 @@ +// 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. + +suite("test_outer_join_with_inline_view") { + sql """ + drop table if exists ojwiv_t1; + """ + + sql """ + drop table if exists ojwiv_t2; + """ + + sql """ + create table if not exists ojwiv_t1( + k1 int not null, + v1 int not null + ) + distributed by hash(k1) + properties( + 'replication_num' = '1' + ); + """ + + sql """ + create table if not exists ojwiv_t2( + k1 int not null, + c1 varchar(255) not null + ) + distributed by hash(k1) + properties('replication_num' = '1'); + """ + + sql """ + insert into ojwiv_t1 values(1, 1), (2, 2), (3, 3), (4, 4); + """ + + sql """ + insert into ojwiv_t2 values(1, '1'), (2, '2'); + """ + + qt_select_with_order_by """ + select * from + (select * from ojwiv_t1) a + left outer join + (select * from ojwiv_t2) b + on a.k1 = b.k1 + order by a.v1; + """ + + qt_select_with_agg_in_inline_view """ + select * from + (select * from ojwiv_t1) a + left outer join + (select k1, count(distinct c1) from ojwiv_t2 group by k1) b + on a.k1 = b.k1 + order by a.v1; + """ +} --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org For additional commands, e-mail: commits-h...@doris.apache.org