This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new 60c47654435 [fix](struct)fix fe struct functions (#54978)
60c47654435 is described below
commit 60c47654435c50195224c7af37045d4c1406e57f
Author: amory <[email protected]>
AuthorDate: Mon Sep 8 15:26:23 2025 +0800
[fix](struct)fix fe struct functions (#54978)
### What problem does this PR solve?
fix some struct functions behavior
doc: https://github.com/apache/doris-website/pull/2765
Issue Number: close #xxx
、
---
.../trees/expressions/functions/scalar/Array.java | 12 +++++
.../expressions/functions/scalar/CreateMap.java | 5 ++
.../functions/scalar/CreateNamedStruct.java | 9 +++-
.../expressions/functions/scalar/CreateStruct.java | 14 ++++++
.../functions/scalar/StructElement.java | 14 ++++--
.../org/apache/doris/nereids/types/StructType.java | 24 +++++-----
.../data/doc/sql-manual/StructNullsafe.out | Bin 0 -> 379 bytes
.../suites/doc/sql-manual/StructNullsafe.groovy | 53 +++++++++++++++++++++
8 files changed, 116 insertions(+), 15 deletions(-)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Array.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Array.java
index dfed9b75df6..4a1c4911e64 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Array.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Array.java
@@ -18,6 +18,7 @@
package org.apache.doris.nereids.trees.expressions.functions.scalar;
import org.apache.doris.catalog.FunctionSignature;
+import org.apache.doris.nereids.exceptions.AnalysisException;
import org.apache.doris.nereids.trees.expressions.Expression;
import org.apache.doris.nereids.trees.expressions.functions.AlwaysNotNullable;
import
org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
@@ -63,6 +64,17 @@ public class Array extends ScalarFunction
super(functionParams);
}
+ @Override
+ public void checkLegalityBeforeTypeCoercion() {
+ if (children.isEmpty()) {
+ return;
+ }
+ DataType firstChildType = children.get(0).getDataType();
+ if (firstChildType.isJsonType() || firstChildType.isVariantType()) {
+ throw new AnalysisException("array does not support jsonb/variant
type");
+ }
+ }
+
/**
* withChildren.
*/
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/CreateMap.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/CreateMap.java
index d44e28f473b..eed1dbebce9 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/CreateMap.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/CreateMap.java
@@ -84,6 +84,11 @@ public class CreateMap extends ScalarFunction
if (arity() % 2 != 0) {
throw new AnalysisException("map can't be odd parameters, need
even parameters " + this.toSql());
}
+ children.forEach(child -> {
+ if (child.getDataType().isJsonType() ||
child.getDataType().isVariantType()) {
+ throw new AnalysisException("map does not support
jsonb/variant type");
+ }
+ });
}
/**
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/CreateNamedStruct.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/CreateNamedStruct.java
index 02ea9dc240f..3b86d5a4749 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/CreateNamedStruct.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/CreateNamedStruct.java
@@ -57,6 +57,9 @@ public class CreateNamedStruct extends ScalarFunction
implements CustomSignature
@Override
public void checkLegalityBeforeTypeCoercion() {
+ if (arity() < 2) {
+ throw new AnalysisException("named_struct requires at least two
arguments, like: named_struct('a', 1)");
+ }
if (arity() % 2 != 0) {
throw new AnalysisException("named_struct can't be odd parameters,
need even parameters " + this.toSql());
}
@@ -66,7 +69,7 @@ public class CreateNamedStruct extends ScalarFunction
implements CustomSignature
throw new AnalysisException("named_struct only allows"
+ " constant string parameter in odd position: " +
this);
} else {
- String name = ((StringLikeLiteral) child(i)).getStringValue();
+ String name = ((StringLikeLiteral)
child(i)).getStringValue().toLowerCase();
if (names.contains(name)) {
throw new AnalysisException("The name of the struct field
cannot be repeated."
+ " same name fields are " + name);
@@ -74,6 +77,10 @@ public class CreateNamedStruct extends ScalarFunction
implements CustomSignature
names.add(name);
}
}
+ // i+1 is value, check if it is not jsonb/variant type
+ if (child(i + 1).getDataType().isJsonType() || child(i +
1).getDataType().isVariantType()) {
+ throw new AnalysisException("named_struct does not support
jsonb/variant type");
+ }
}
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/CreateStruct.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/CreateStruct.java
index 8e94dc690cd..9e89da0fd87 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/CreateStruct.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/CreateStruct.java
@@ -18,6 +18,7 @@
package org.apache.doris.nereids.trees.expressions.functions.scalar;
import org.apache.doris.catalog.FunctionSignature;
+import org.apache.doris.nereids.exceptions.AnalysisException;
import org.apache.doris.nereids.trees.expressions.Expression;
import org.apache.doris.nereids.trees.expressions.functions.AlwaysNotNullable;
import
org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
@@ -53,6 +54,19 @@ public class CreateStruct extends ScalarFunction
super(functionParams);
}
+ @Override
+ public void checkLegalityBeforeTypeCoercion() {
+ if (arity() == 0) {
+ throw new AnalysisException("struct requires at least one
argument, like: struct(1)");
+ }
+ // for all field we do not support struct field with jsonb/variant type
+ children.forEach(child -> {
+ if (child.getDataType().isJsonType() ||
child.getDataType().isVariantType()) {
+ throw new AnalysisException("struct does not support
jsonb/variant type");
+ }
+ });
+ }
+
/**
* withChildren.
*/
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StructElement.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StructElement.java
index 39af519a617..85bb424de19 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StructElement.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StructElement.java
@@ -22,11 +22,13 @@ import
org.apache.doris.nereids.exceptions.AnalysisException;
import org.apache.doris.nereids.trees.expressions.Expression;
import org.apache.doris.nereids.trees.expressions.functions.AlwaysNullable;
import
org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
+import
org.apache.doris.nereids.trees.expressions.functions.PropagateNullLiteral;
import org.apache.doris.nereids.trees.expressions.functions.SearchSignature;
import org.apache.doris.nereids.trees.expressions.literal.IntegerLikeLiteral;
import org.apache.doris.nereids.trees.expressions.literal.StringLikeLiteral;
import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
import org.apache.doris.nereids.types.DataType;
+import org.apache.doris.nereids.types.NullType;
import org.apache.doris.nereids.types.StructType;
import com.google.common.base.Preconditions;
@@ -38,7 +40,7 @@ import java.util.List;
* ScalarFunction 'struct_element'.
*/
public class StructElement extends ScalarFunction
- implements ExplicitlyCastableSignature, AlwaysNullable {
+ implements ExplicitlyCastableSignature, AlwaysNullable,
PropagateNullLiteral {
/**
* constructor with 0 or more arguments.
@@ -54,7 +56,8 @@ public class StructElement extends ScalarFunction
@Override
public void checkLegalityBeforeTypeCoercion() {
- if (!(child(0).getDataType() instanceof StructType)) {
+ if (!(child(0).getDataType() instanceof StructType
+ || child(0).getDataType() instanceof NullType)) {
SearchSignature.throwCanNotFoundFunctionException(this.getName(),
this.getArguments());
}
if (!(child(1) instanceof StringLikeLiteral || child(1) instanceof
IntegerLikeLiteral)) {
@@ -79,6 +82,11 @@ public class StructElement extends ScalarFunction
@Override
public List<FunctionSignature> getSignatures() {
+ if (child(0).getDataType() instanceof NullType) {
+ return ImmutableList.of(
+
FunctionSignature.ret(NullType.INSTANCE).args(NullType.INSTANCE,
child(1).getDataType())
+ );
+ }
StructType structArgType = (StructType) child(0).getDataType();
DataType retType;
if (child(1) instanceof IntegerLikeLiteral) {
@@ -90,7 +98,7 @@ public class StructElement extends ScalarFunction
}
} else if (child(1) instanceof StringLikeLiteral) {
String name = ((StringLikeLiteral) child(1)).getStringValue();
- if (!structArgType.getNameToFields().containsKey(name)) {
+ if
(!structArgType.getNameToFields().containsKey(name.toLowerCase())) {
throw new AnalysisException("the specified field name " + name
+ " was not found: " + this.toSql());
} else {
retType =
structArgType.getNameToFields().get(name).getDataType();
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/types/StructType.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/types/StructType.java
index 20f0947b321..ffbff7c61e1 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/types/StructType.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/types/StructType.java
@@ -22,15 +22,13 @@ import org.apache.doris.nereids.annotation.Developing;
import org.apache.doris.nereids.exceptions.AnalysisException;
import org.apache.doris.nereids.types.coercion.ComplexDataType;
-import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
-import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
@@ -44,10 +42,10 @@ public class StructType extends DataType implements
ComplexDataType {
public static final int WIDTH = 24;
private final List<StructField> fields;
- private final Supplier<Map<String, StructField>> nameToFields;
+ private final Map<String, StructField> nameToFields;
private StructType() {
- nameToFields = Suppliers.memoize(ImmutableMap::of);
+ nameToFields = new HashMap<>();
fields = ImmutableList.of();
}
@@ -56,11 +54,15 @@ public class StructType extends DataType implements
ComplexDataType {
*/
public StructType(List<StructField> fields) {
this.fields = ImmutableList.copyOf(Objects.requireNonNull(fields,
"fields should not be null"));
- this.nameToFields = Suppliers.memoize(() ->
this.fields.stream().collect(ImmutableMap.toImmutableMap(
- StructField::getName, f -> f, (f1, f2) -> {
- throw new AnalysisException("The name of the struct field
cannot be repeated."
- + " same name fields are " + f1 + " and " + f2);
- })));
+ // field name should be lowercase and check the same or not
+ this.nameToFields = new HashMap<>();
+ for (StructField field : this.fields) {
+ String fieldName = field.getName().toLowerCase();
+ StructField existingField = this.nameToFields.put(fieldName,
field);
+ if (existingField != null) {
+ throw new AnalysisException("Duplicate field name found: " +
fieldName);
+ }
+ }
}
public List<StructField> getFields() {
@@ -68,7 +70,7 @@ public class StructType extends DataType implements
ComplexDataType {
}
public Map<String, StructField> getNameToFields() {
- return nameToFields.get();
+ return nameToFields;
}
@Override
diff --git a/regression-test/data/doc/sql-manual/StructNullsafe.out
b/regression-test/data/doc/sql-manual/StructNullsafe.out
new file mode 100644
index 00000000000..397ca313cef
Binary files /dev/null and
b/regression-test/data/doc/sql-manual/StructNullsafe.out differ
diff --git a/regression-test/suites/doc/sql-manual/StructNullsafe.groovy
b/regression-test/suites/doc/sql-manual/StructNullsafe.groovy
new file mode 100644
index 00000000000..1cac0915a78
--- /dev/null
+++ b/regression-test/suites/doc/sql-manual/StructNullsafe.groovy
@@ -0,0 +1,53 @@
+// 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("nereids_scalar_fn_StructNullsafe", "p0") {
+ sql 'set enable_nereids_planner=true'
+ sql 'set enable_fallback_to_original_planner=false'
+
+ // table nullsafe array
+ sql """DROP TABLE IF EXISTS fn_test_nullsafe_struct"""
+ sql """
+ CREATE TABLE IF NOT EXISTS `fn_test_nullsafe_struct` (
+ `id` int not null,
+ `struct` struct<id: int, name: string>
+ ) engine=olap
+ DISTRIBUTED BY HASH(`id`) BUCKETS 1
+ properties("replication_num" = "1")
+ """
+
+ // insert into fn_test_nullsafe_array with null element
+ sql """
+ INSERT INTO fn_test_nullsafe_struct VALUES
+ (1, STRUCT(1, 'Alice')),
+ (2, STRUCT(null, 'Bob')),
+ (3, STRUCT(3, null)),
+ (4, NULL),
+ (5, STRUCT(null, null))
+ """
+
+ // test function struct
+ qt_sql_struct_element_by_index "SELECT struct_element(struct, 1),
struct_element(struct, 2) FROM fn_test_nullsafe_struct ORDER BY id"
+ qt_sql_struct_element_by_name "SELECT struct_element(struct, 'id'),
struct_element(struct, 'name') FROM fn_test_nullsafe_struct ORDER BY id"
+
+ // test function named_struct
+ qt_sql_literal_named_struct "SELECT named_struct('id', 1, 'name', NULL)"
+ qt_sql_literal_struct_element "SELECT struct_element(named_struct('id',
NULL, 'name', 'Tom'), 'id'), struct_element(named_struct('id', 2, 'name',
NULL), 'name')"
+
+ // test function struct_element
+ qt_sql_literal_struct "SELECT STRUCT(NULL, 'X')"
+}
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]