This is an automated email from the ASF dual-hosted git repository.
jackietien pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/master by this push:
new 3931c05c22e Resolve type mismatch when WHEN result type differs from
ELSE (INT32 vs INT64) (#17415)
3931c05c22e is described below
commit 3931c05c22e81851352c3408c1abf539107b2732
Author: shuwenwei <[email protected]>
AuthorDate: Fri Apr 3 14:47:53 2026 +0800
Resolve type mismatch when WHEN result type differs from ELSE (INT32 vs
INT64) (#17415)
---
.../it/db/it/IoTDBCaseWhenThenTableIT.java | 12 ++-
.../plan/relational/planner/IrTypeAnalyzer.java | 117 ++++++++++++++++-----
.../plan/relational/type/TypeCoercionUtils.java | 46 ++++++++
3 files changed, 146 insertions(+), 29 deletions(-)
diff --git
a/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBCaseWhenThenTableIT.java
b/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBCaseWhenThenTableIT.java
index cda57e726ea..50053867636 100644
---
a/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBCaseWhenThenTableIT.java
+++
b/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBCaseWhenThenTableIT.java
@@ -88,6 +88,16 @@ public class IoTDBCaseWhenThenTableIT {
EnvFactory.getEnv().cleanClusterEnvironment();
}
+ @Test
+ public void testIfWithCastedDefaultType() {
+ String[] retArray = new String[] {"0,", "0,", "2,", "3,"};
+ tableResultSetEqualTest(
+ "select if(s2 > 1, s2, cast(0 as int64)) from table3 limit 4",
+ expectedHeader,
+ retArray,
+ DATABASE);
+ }
+
@Test
public void testKind1Basic() {
String[] retArray = new String[] {"99,", "9999,", "9999,", "999,"};
@@ -161,7 +171,7 @@ public class IoTDBCaseWhenThenTableIT {
DATABASE);
tableAssertTestFail(
"select case when s1<=0 then 0 when s1>1 then null end from table1",
- "701: All result types must be the same:",
+ "701: All result types and default result type must be the same:",
DATABASE);
// TEXT and other types cannot exist at the same time
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/IrTypeAnalyzer.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/IrTypeAnalyzer.java
index 61d3ec3c710..442124b9b8d 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/IrTypeAnalyzer.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/IrTypeAnalyzer.java
@@ -61,9 +61,12 @@ import
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SearchedCaseExpre
import
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SimpleCaseExpression;
import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.StringLiteral;
import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SymbolReference;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.WhenClause;
+import org.apache.iotdb.db.queryengine.plan.relational.type.TypeCoercionUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.LinkedHashMultimap;
import org.apache.tsfile.read.common.type.BlobType;
import org.apache.tsfile.read.common.type.DateType;
import org.apache.tsfile.read.common.type.RowType;
@@ -72,10 +75,14 @@ import org.apache.tsfile.read.common.type.TimestampType;
import org.apache.tsfile.read.common.type.Type;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkArgument;
@@ -230,37 +237,31 @@ public class IrTypeAnalyzer {
@Override
protected Type visitSearchedCaseExpression(SearchedCaseExpression node,
Context context) {
- LinkedHashSet<Type> resultTypes =
- node.getWhenClauses().stream()
- .map(
- clause -> {
- Type operandType = process(clause.getOperand(), context);
- if (!operandType.equals(BOOLEAN)) {
- throw new SemanticException(
- String.format("When clause operand must be boolean:
%s", operandType));
- }
- return setExpressionType(clause,
process(clause.getResult(), context));
- })
- .collect(Collectors.toCollection(LinkedHashSet::new));
+ for (WhenClause whenClause : node.getWhenClauses()) {
+ coerceType(
+ context,
+ whenClause.getOperand(),
+ BOOLEAN,
+ (actualType) -> String.format("When clause operand must be
boolean: %s", actualType));
+ }
- if (resultTypes.size() != 1) {
- throw new SemanticException(
- String.format("All result types must be the same: %s",
resultTypes));
+ List<Expression> expressions = new ArrayList<>();
+ for (WhenClause whenClause : node.getWhenClauses()) {
+ expressions.add(whenClause.getResult());
}
- Type resultType = resultTypes.iterator().next();
- node.getDefaultValue()
- .ifPresent(
- defaultValue -> {
- Type defaultType = process(defaultValue, context);
- if (!defaultType.equals(resultType)) {
- throw new SemanticException(
- String.format(
- "Default result type must be the same as WHEN result
types: %s vs %s",
- defaultType, resultType));
- }
- });
+ node.getDefaultValue().ifPresent(expressions::add);
- return setExpressionType(node, resultType);
+ Type type =
+ coerceToSingleType(
+ context, expressions, "All result types and default result type
must be the same");
+ setExpressionType(node, type);
+
+ for (WhenClause whenClause : node.getWhenClauses()) {
+ Type whenClauseType = process(whenClause.getResult(), context);
+ setExpressionType(whenClause, whenClauseType);
+ }
+
+ return type;
}
@Override
@@ -485,6 +486,66 @@ public class IrTypeAnalyzer {
throw new UnsupportedOperationException(
"Not a valid IR expression: " + node.getClass().getName());
}
+
+ // Only allow INT32 -> INT64 coercion to suppress some related bugs for now
+ private void coerceType(
+ Context context, Expression expression, Type expectedType,
Function<Type, String> message) {
+ Type actualType = process(expression, context);
+ coerceType(expression, expectedType, actualType, message);
+ }
+
+ private Type coerceToSingleType(
+ Context context, List<Expression> expressions, String description) {
+ LinkedHashMultimap<Type, NodeRef<Expression>> typeExpressions =
LinkedHashMultimap.create();
+
+ for (Expression expression : expressions) {
+ Type type = process(expression, context);
+ typeExpressions.put(type, NodeRef.of(expression));
+ }
+ Set<Type> types = typeExpressions.keySet();
+ Iterator<Type> iterator = types.iterator();
+ Type superType = iterator.next();
+ if (types.size() == 1) {
+ return superType;
+ }
+ while (iterator.hasNext()) {
+ Type current = iterator.next();
+ if (TypeCoercionUtils.canCoerceTo(current, superType)) {
+ continue;
+ }
+ if (TypeCoercionUtils.canCoerceTo(superType, current)) {
+ superType = current;
+ }
+ throw new SemanticException(String.format(description + ": %s vs %s",
superType, current));
+ }
+ for (Type type : types) {
+ Set<NodeRef<Expression>> nodeRefs = typeExpressions.get(type);
+ if (type.equals(superType)) {
+ continue;
+ }
+ if (!TypeCoercionUtils.canCoerceTo(type, superType)) {
+ throw new SemanticException("Cannot coerce type " + type + " to " +
superType);
+ }
+ addOrReplaceExpressionType(nodeRefs, superType);
+ }
+ return superType;
+ }
+
+ private void coerceType(
+ Expression expression,
+ Type actualType,
+ Type expectedType,
+ Function<Type, String> errorMsg) {
+ if (!TypeCoercionUtils.canCoerceTo(actualType, expectedType)) {
+ throw new SemanticException(errorMsg.apply(actualType));
+ }
+ setExpressionType(expression, actualType);
+ }
+
+ private void addOrReplaceExpressionType(
+ Collection<NodeRef<Expression>> expressions, Type superType) {
+ expressions.forEach(expression -> expressionTypes.put(expression,
superType));
+ }
}
private static class Context {
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/TypeCoercionUtils.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/TypeCoercionUtils.java
new file mode 100644
index 00000000000..8d3532d93c4
--- /dev/null
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/TypeCoercionUtils.java
@@ -0,0 +1,46 @@
+/*
+ * 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.iotdb.db.queryengine.plan.relational.type;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import org.apache.tsfile.read.common.type.IntType;
+import org.apache.tsfile.read.common.type.LongType;
+import org.apache.tsfile.read.common.type.Type;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+public class TypeCoercionUtils {
+
+ private static final Map<Type, Set<Type>> typeCoercionMap;
+
+ static {
+ typeCoercionMap = ImmutableMap.of(IntType.INT32,
ImmutableSet.of(LongType.INT64));
+ }
+
+ public static boolean canCoerceTo(Type from, Type to) {
+ if (from.equals(to)) {
+ return true;
+ }
+ return typeCoercionMap.getOrDefault(from,
Collections.emptySet()).contains(to);
+ }
+}