Skip to content

Commit b262f98

Browse files
authored
[fix](Nereids) null type in result set will be cast to tinyint (#37019)
1 parent 1f45e99 commit b262f98

File tree

4 files changed

+100
-8
lines changed

4 files changed

+100
-8
lines changed

fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindExpression.java

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.apache.doris.nereids.rules.expression.ExpressionRewriteContext;
4343
import org.apache.doris.nereids.trees.expressions.Alias;
4444
import org.apache.doris.nereids.trees.expressions.BoundStar;
45+
import org.apache.doris.nereids.trees.expressions.Cast;
4546
import org.apache.doris.nereids.trees.expressions.DefaultValueSlot;
4647
import org.apache.doris.nereids.trees.expressions.EqualTo;
4748
import org.apache.doris.nereids.trees.expressions.ExprId;
@@ -89,8 +90,11 @@
8990
import org.apache.doris.nereids.trees.plans.logical.UsingJoin;
9091
import org.apache.doris.nereids.trees.plans.visitor.InferPlanOutputAlias;
9192
import org.apache.doris.nereids.types.BooleanType;
93+
import org.apache.doris.nereids.types.DataType;
94+
import org.apache.doris.nereids.types.NullType;
9295
import org.apache.doris.nereids.types.StructField;
9396
import org.apache.doris.nereids.types.StructType;
97+
import org.apache.doris.nereids.types.TinyIntType;
9498
import org.apache.doris.nereids.util.ExpressionUtils;
9599
import org.apache.doris.nereids.util.PlanUtils;
96100
import org.apache.doris.nereids.util.TypeCoercionUtils;
@@ -208,22 +212,38 @@ protected boolean condition(Rule rule, Plan plan) {
208212

209213
private LogicalResultSink<Plan> bindResultSink(MatchingContext<UnboundResultSink<Plan>> ctx) {
210214
LogicalSink<Plan> sink = ctx.root;
215+
Plan child = sink.child();
216+
List<Slot> output = child.getOutput();
217+
List<NamedExpression> castNullToTinyInt = Lists.newArrayListWithCapacity(output.size());
218+
boolean needProject = false;
219+
for (Slot slot : output) {
220+
DataType newType = TypeCoercionUtils.replaceSpecifiedType(
221+
slot.getDataType(), NullType.class, TinyIntType.INSTANCE);
222+
if (!newType.equals(slot.getDataType())) {
223+
needProject = true;
224+
castNullToTinyInt.add(new Alias(new Cast(slot, newType)));
225+
} else {
226+
castNullToTinyInt.add(slot);
227+
}
228+
}
229+
if (needProject) {
230+
child = new LogicalProject<>(castNullToTinyInt, child);
231+
}
211232
if (ctx.connectContext.getState().isQuery()) {
212-
List<NamedExpression> outputExprs = sink.child().getOutput().stream()
233+
List<NamedExpression> outputExprs = child.getOutput().stream()
213234
.map(NamedExpression.class::cast)
214235
.collect(ImmutableList.toImmutableList());
215-
return new LogicalResultSink<>(outputExprs, sink.child());
236+
return new LogicalResultSink<>(outputExprs, child);
216237
}
217238
// Should infer column name for expression when query command
218-
final ImmutableListMultimap.Builder<ExprId, Integer> exprIdToIndexMapBuilder =
219-
ImmutableListMultimap.builder();
220-
List<Slot> childOutput = sink.child().getOutput();
239+
final ImmutableListMultimap.Builder<ExprId, Integer> exprIdToIndexMapBuilder = ImmutableListMultimap.builder();
240+
List<Slot> childOutput = child.getOutput();
221241
for (int index = 0; index < childOutput.size(); index++) {
222242
exprIdToIndexMapBuilder.put(childOutput.get(index).getExprId(), index);
223243
}
224244
InferPlanOutputAlias aliasInfer = new InferPlanOutputAlias(childOutput);
225-
List<NamedExpression> output = aliasInfer.infer(sink.child(), exprIdToIndexMapBuilder.build());
226-
return new LogicalResultSink<>(output, sink.child());
245+
List<NamedExpression> sinkExpr = aliasInfer.infer(child, exprIdToIndexMapBuilder.build());
246+
return new LogicalResultSink<>(sinkExpr, child);
227247
}
228248

229249
private LogicalSubQueryAlias<Plan> bindSubqueryAlias(MatchingContext<LogicalSubQueryAlias<Plan>> ctx) {

fe/fe-core/src/main/java/org/apache/doris/nereids/util/TypeCoercionUtils.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,8 @@ public static DataType replaceDateTimeV2WithMax(DataType dataType) {
341341
public static DataType replaceSpecifiedType(DataType dataType,
342342
Class<? extends DataType> specifiedType, DataType newType) {
343343
if (dataType instanceof ArrayType) {
344-
return ArrayType.of(replaceSpecifiedType(((ArrayType) dataType).getItemType(), specifiedType, newType));
344+
return ArrayType.of(replaceSpecifiedType(((ArrayType) dataType).getItemType(), specifiedType, newType),
345+
((ArrayType) dataType).containsNull());
345346
} else if (dataType instanceof MapType) {
346347
return MapType.of(replaceSpecifiedType(((MapType) dataType).getKeyType(), specifiedType, newType),
347348
replaceSpecifiedType(((MapType) dataType).getValueType(), specifiedType, newType));
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- This file is automatically generated. You should know what you did if you want to edit this
2+
-- !select --
3+
\N
4+
5+
-- !desc --
6+
test_create_with_null_type DUP_KEYS __cast_0 TINYINT TINYINT Yes true \N true
7+
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
import org.junit.Assert;
19+
20+
suite("test_create_with_null_type") {
21+
def tableName = "t_test_create_with_null_type"
22+
def mvName = "test_create_with_null_type"
23+
def dbName = "regression_test_mtmv_p0"
24+
sql """drop table if exists `${tableName}`"""
25+
sql """drop materialized view if exists ${mvName};"""
26+
27+
sql """
28+
CREATE TABLE `${tableName}` (
29+
`user_id` LARGEINT NOT NULL COMMENT '\"用户id\"',
30+
`num` SMALLINT SUM NOT NULL COMMENT '\"数量\"'
31+
) ENGINE=OLAP
32+
AGGREGATE KEY(`user_id`)
33+
COMMENT 'OLAP'
34+
DISTRIBUTED BY HASH(`user_id`) BUCKETS 2
35+
PROPERTIES ('replication_num' = '1') ;
36+
"""
37+
sql """
38+
insert into ${tableName} values (1,1),(1,2);
39+
"""
40+
41+
sql """
42+
CREATE MATERIALIZED VIEW ${mvName}
43+
BUILD DEFERRED REFRESH AUTO ON MANUAL
44+
DISTRIBUTED BY RANDOM BUCKETS 2
45+
PROPERTIES ('replication_num' = '1')
46+
AS
47+
SELECT null FROM ${tableName};
48+
"""
49+
50+
sql """
51+
REFRESH MATERIALIZED VIEW ${mvName} AUTO;
52+
"""
53+
54+
def jobName = getJobName(dbName, mvName);
55+
log.info(jobName)
56+
waitingMTMVTaskFinished(jobName)
57+
58+
order_qt_select "SELECT * FROM ${mvName}"
59+
60+
qt_desc "desc ${mvName} all"
61+
62+
sql """drop table if exists `${tableName}`"""
63+
sql """drop materialized view if exists ${mvName};"""
64+
}

0 commit comments

Comments
 (0)