From 96db74fc5ba35040bd0c3720e94c1fdd6e304cd9 Mon Sep 17 00:00:00 2001 From: vajaw Date: Tue, 1 Sep 2026 15:46:26 +0800 Subject: [PATCH] Support trim_array function Implement trim_array in BE and register its Nereids signature. Add constant folding, BE tests for const-column combinations, and regression coverage for nulls, nested arrays, boundary sizes, and types. Related to #48203 --- .../function/array/function_array_pop.cpp | 67 +++++++++++++++++ .../function/function_array_trim_test.cpp | 50 +++++++++++++ be/test/exprs/function/function_test_util.h | 60 ++++++++++++++-- .../doris/catalog/BuiltinScalarFunctions.java | 2 + .../functions/executable/ArrayArithmetic.java | 16 +++++ .../functions/scalar/TrimArray.java | 70 ++++++++++++++++++ .../visitor/ScalarFunctionVisitor.java | 5 ++ .../executable/ArrayArithmeticTest.java | 58 +++++++++++++++ .../array_functions/test_trim_array.out | 55 ++++++++++++++ .../array_functions/test_trim_array.groovy | 71 +++++++++++++++++++ 10 files changed, 450 insertions(+), 4 deletions(-) create mode 100644 be/test/exprs/function/function_array_trim_test.cpp create mode 100644 fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/TrimArray.java create mode 100644 fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/executable/ArrayArithmeticTest.java create mode 100644 regression-test/data/query_p0/sql_functions/array_functions/test_trim_array.out create mode 100644 regression-test/suites/query_p0/sql_functions/array_functions/test_trim_array.groovy diff --git a/be/src/exprs/function/array/function_array_pop.cpp b/be/src/exprs/function/array/function_array_pop.cpp index aa415a59449327..69041a3b724dca 100644 --- a/be/src/exprs/function/array/function_array_pop.cpp +++ b/be/src/exprs/function/array/function_array_pop.cpp @@ -23,7 +23,9 @@ #include #include +#include "common/compiler_util.h" #include "common/status.h" +#include "core/assert_cast.h" #include "core/block/block.h" #include "core/block/column_numbers.h" #include "core/block/column_with_type_and_name.h" @@ -104,9 +106,74 @@ class FunctionArrayPopfront : public FunctionArrayPop { static constexpr int start_offset = 2; }; +class FunctionArrayTrim : public IFunction { +public: + static constexpr auto name = "trim_array"; + static FunctionPtr create() { return std::make_shared(); } + + String get_name() const override { return name; } + + bool is_variadic() const override { return false; } + + size_t get_number_of_arguments() const override { return 2; } + + DataTypePtr get_return_type_impl(const DataTypes& arguments) const override { + DCHECK(arguments[0]->get_primitive_type() == TYPE_ARRAY) + << "First argument for function: " << name + << " should be DataTypeArray but it has type " << arguments[0]->get_name() << "."; + DCHECK(arguments[1]->get_primitive_type() == TYPE_BIGINT) + << "Second argument for function: " << name << " should be BigInt but it has type " + << arguments[1]->get_name() << "."; + return arguments[0]; + } + + Status execute_impl(FunctionContext* context, Block& block, const ColumnNumbers& arguments, + uint32_t result, size_t input_rows_count) const override { + auto array_column = + block.get_by_position(arguments[0]).column->convert_to_full_column_if_const(); + auto size_column = + block.get_by_position(arguments[1]).column->convert_to_full_column_if_const(); + + ColumnArrayExecutionData src; + if (!extract_column_array_info(*array_column, src)) { + return Status::RuntimeError( + fmt::format("execute failed, unsupported types for function {}({}, {})", + get_name(), block.get_by_position(arguments[0]).type->get_name(), + block.get_by_position(arguments[1]).type->get_name())); + } + + auto length_column = ColumnInt64::create(); + length_column->reserve(input_rows_count); + const auto& sizes = assert_cast(*size_column).get_data(); + for (size_t row = 0; row < input_rows_count; ++row) { + const auto size = sizes[row]; + const size_t offset = (*src.offsets_ptr)[row - 1]; + const size_t cardinality = (*src.offsets_ptr)[row] - offset; + if (UNLIKELY(size < 0)) { + return Status::InvalidArgument("size must not be negative: {}", size); + } + if (UNLIKELY(static_cast(size) > cardinality)) { + return Status::InvalidArgument("size must not exceed array cardinality {}: {}", + cardinality, size); + } + length_column->insert_value(static_cast(cardinality) - size); + } + + const bool nested_is_nullable = src.nested_nullmap_data != nullptr; + ColumnArrayMutableData dst = create_mutable_data(src.nested_col.get(), nested_is_nullable); + dst.offsets_ptr->reserve(input_rows_count); + auto offset_column = ColumnInt64::create(input_rows_count, 1); + slice_array(dst, src, *offset_column, length_column.get()); + + block.replace_by_position(result, assemble_column_array(dst)); + return Status::OK(); + } +}; + void register_function_array_pop(SimpleFunctionFactory& factory) { factory.register_function(); factory.register_function(); + factory.register_function(); } } // namespace doris diff --git a/be/test/exprs/function/function_array_trim_test.cpp b/be/test/exprs/function/function_array_trim_test.cpp new file mode 100644 index 00000000000000..51bcf48d46b200 --- /dev/null +++ b/be/test/exprs/function/function_array_trim_test.cpp @@ -0,0 +1,50 @@ +// 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. + +#include +#include + +#include "core/data_type/data_type_array.h" +#include "core/data_type/data_type_nullable.h" +#include "core/data_type/data_type_number.h" +#include "exprs/function/function_test_util.h" + +namespace doris { + +TEST(FunctionArrayTrimTest, all_argument_combinations) { + const std::string function_name = "trim_array"; + const InputArgTypeSet input_types = {{PrimitiveType::TYPE_ARRAY, PrimitiveType::TYPE_INT}, + {PrimitiveType::TYPE_BIGINT}}; + + TestArray empty; + TestArray values = {Int32(1), Int32(2), Int32(3), Int32(4)}; + TestArray values_with_null = {Int32(1), Null(), Int32(3)}; + DataSet data_set = { + {{AnyType(values), Int64(0)}, AnyType(values)}, + {{AnyType(values), Int64(2)}, AnyType(TestArray {Int32(1), Int32(2)})}, + {{AnyType(values), Int64(4)}, AnyType(empty)}, + {{AnyType(empty), Int64(0)}, AnyType(empty)}, + {{AnyType(values_with_null), Int64(1)}, AnyType(TestArray {Int32(1), Null()})}, + {{Null(), Int64(0)}, Null()}, + {{AnyType(values), Null()}, Null()}}; + + auto result_nested_type = make_nullable(std::make_shared()); + check_function_all_arg_comb(function_name, input_types, data_set, + result_nested_type); +} + +} // namespace doris diff --git a/be/test/exprs/function/function_test_util.h b/be/test/exprs/function/function_test_util.h index ebbb9c2f77c486..1c81d7f01742d2 100644 --- a/be/test/exprs/function/function_test_util.h +++ b/be/test/exprs/function/function_test_util.h @@ -79,6 +79,9 @@ using Row = std::pair; using DataSet = std::vector; // to represent Array: {PrimitiveType::TYPE_ARRAY, PrimitiveType::TYPE_BIGINT} using InputTypeSet = std::vector; +// Each entry represents one logical argument. This form supports parametric types, for example: +// {{PrimitiveType::TYPE_ARRAY, PrimitiveType::TYPE_INT}, {PrimitiveType::TYPE_BIGINT}}. +using InputArgTypeSet = std::vector; struct Nullable { PrimitiveType tp; @@ -322,7 +325,7 @@ template (input_types.size()); TestCaseInfo::func_call_index++; // 1.0 create data type @@ -384,6 +387,10 @@ Status check_function(const std::string& func_name, const InputTypeSet& input_ty } return ResultNullable ? make_nullable(std::make_shared(real_scale)) : std::make_shared(real_scale); + } else if constexpr (std::is_same_v) { + EXPECT_NE(result_nested_type, nullptr); + DataTypePtr array_type = std::make_shared(result_nested_type); + return ResultNullable ? make_nullable(array_type) : array_type; } else { return ResultNullable ? make_nullable(std::make_shared()) : std::make_shared(); @@ -394,8 +401,14 @@ Status check_function(const std::string& func_name, const InputTypeSet& input_ty assert(func.get() != nullptr); // this may be useless now. for some type like array, it's wrong. TODO: need more details explainations - auto fn_ctx_return = get_return_type_descriptor(std::max(0, result_scale), - std::max(0, result_precision)); + auto fn_ctx_return = [&]() { + if constexpr (std::is_same_v) { + return remove_nullable(return_type); + } else { + return get_return_type_descriptor(std::max(0, result_scale), + std::max(0, result_precision)); + } + }(); FunctionUtils fn_utils(fn_ctx_return, arg_types, is_strict_mode); auto* fn_ctx = fn_utils.get_fn_ctx(); @@ -423,7 +436,9 @@ Status check_function(const std::string& func_name, const InputTypeSet& input_ty // 3.0. create expected result column in block DataTypePtr result_type_ptr; - if constexpr (IsDataTypeDecimal) { // decimal + if constexpr (std::is_same_v) { + result_type_ptr = return_type; + } else if constexpr (IsDataTypeDecimal) { // decimal result_type_ptr = ResultNullable ? make_nullable(std::make_shared(result_precision, result_scale)) @@ -532,4 +547,41 @@ void check_function_all_arg_comb(const std::string& func_name, const InputTypeSe } } } + +// Variant for parametric argument or result types. Each entry in base_arg_types is one logical +// argument and may contain multiple type descriptors, such as ARRAY followed by its item type. +template +void check_function_all_arg_comb(const std::string& func_name, + const InputArgTypeSet& base_arg_types, const DataSet& data_set, + DataTypePtr result_nested_type) { + TestCaseInfo::func_call_index++; + const size_t arg_cnt = base_arg_types.size(); + for (int combination = 0; combination < (1 << arg_cnt); ++combination) { + InputTypeSet input_types; + for (size_t arg = 0; arg < arg_cnt; ++arg) { + const bool is_const = (1 << arg) & combination; + const auto& arg_types = base_arg_types[arg]; + DCHECK(!arg_types.empty()); + const auto base_type = any_cast(arg_types.front()); + input_types.emplace_back(is_const ? AnyType(Consted {base_type}) : arg_types.front()); + input_types.insert(input_types.end(), arg_types.begin() + 1, arg_types.end()); + } + + TestCaseInfo::arg_const_info = combination; + TestCaseInfo::error_line_number = -1; + if (combination != 0) { + for (const auto& line : data_set) { + TestCaseInfo::func_call_index--; + static_cast(check_function( + func_name, input_types, DataSet {line}, -1, -1, false, false, false, + result_nested_type)); + } + } else { + TestCaseInfo::func_call_index--; + static_cast(check_function(func_name, input_types, data_set, + -1, -1, false, false, false, + result_nested_type)); + } + } +} } // namespace doris diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinScalarFunctions.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinScalarFunctions.java index e55e1008762e68..6289835dd22d43 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinScalarFunctions.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinScalarFunctions.java @@ -551,6 +551,7 @@ import org.apache.doris.nereids.trees.expressions.functions.scalar.TransformValues; import org.apache.doris.nereids.trees.expressions.functions.scalar.Translate; import org.apache.doris.nereids.trees.expressions.functions.scalar.Trim; +import org.apache.doris.nereids.trees.expressions.functions.scalar.TrimArray; import org.apache.doris.nereids.trees.expressions.functions.scalar.TrimIn; import org.apache.doris.nereids.trees.expressions.functions.scalar.Truncate; import org.apache.doris.nereids.trees.expressions.functions.scalar.TryParseToVariant; @@ -1149,6 +1150,7 @@ public class BuiltinScalarFunctions implements FunctionHelper { scalar(ToSeconds.class, "to_seconds"), scalar(Translate.class, "translate"), scalar(Trim.class, "trim"), + scalar(TrimArray.class, "trim_array"), scalar(TrimIn.class, "trim_in"), scalar(Truncate.class, "truncate"), scalar(Unhex.class, "unhex"), diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/executable/ArrayArithmetic.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/executable/ArrayArithmetic.java index c1201807f487af..38fda5dd233f26 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/executable/ArrayArithmetic.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/executable/ArrayArithmetic.java @@ -50,6 +50,22 @@ public static Expression cardinality(MapLiteral map) { return new BigIntLiteral(map.getValue().size()); } + /** Remove a number of elements from the end of an array. */ + @ExecFunction(name = "trim_array") + public static Expression trimArray(ArrayLiteral array, BigIntLiteral size) { + long trimSize = size.getValue(); + int cardinality = array.getValue().size(); + if (trimSize < 0) { + throw new AnalysisException("size must not be negative: " + trimSize); + } + if (trimSize > cardinality) { + throw new AnalysisException("size must not exceed array cardinality " + + cardinality + ": " + trimSize); + } + return new ArrayLiteral(array.getValue().subList(0, cardinality - (int) trimSize), + array.getDataType()); + } + /** * Compute the cross product between two 3D float arrays. */ diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/TrimArray.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/TrimArray.java new file mode 100644 index 00000000000000..9f89ba82c37c5a --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/TrimArray.java @@ -0,0 +1,70 @@ +// 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.trees.expressions.functions.scalar; + +import org.apache.doris.catalog.FunctionSignature; +import org.apache.doris.nereids.trees.expressions.Expression; +import org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature; +import org.apache.doris.nereids.trees.expressions.functions.PropagateNullable; +import org.apache.doris.nereids.trees.expressions.shape.BinaryExpression; +import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor; +import org.apache.doris.nereids.types.ArrayType; +import org.apache.doris.nereids.types.BigIntType; +import org.apache.doris.nereids.types.coercion.AnyDataType; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; + +import java.util.List; + +/** + * Remove the specified number of elements from the end of an array. + */ +public class TrimArray extends ScalarFunction + implements BinaryExpression, ExplicitlyCastableSignature, PropagateNullable { + + public static final List SIGNATURES = ImmutableList.of( + FunctionSignature.retArgType(0) + .args(ArrayType.of(AnyDataType.INSTANCE_WITHOUT_INDEX), BigIntType.INSTANCE) + ); + + public TrimArray(Expression array, Expression size) { + super("trim_array", array, size); + } + + private TrimArray(ScalarFunctionParams functionParams) { + super(functionParams); + } + + @Override + public TrimArray withChildren(List children) { + Preconditions.checkArgument(children.size() == 2, + "trim_array accepts 2 arguments, but got %s (%s)", children.size(), children); + return new TrimArray(getFunctionParams(children)); + } + + @Override + public R accept(ExpressionVisitor visitor, C context) { + return visitor.visitTrimArray(this, context); + } + + @Override + public List getSignatures() { + return SIGNATURES; + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ScalarFunctionVisitor.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ScalarFunctionVisitor.java index ec86766df02f2e..a130ee7be37f73 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ScalarFunctionVisitor.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ScalarFunctionVisitor.java @@ -568,6 +568,7 @@ import org.apache.doris.nereids.trees.expressions.functions.scalar.TransformValues; import org.apache.doris.nereids.trees.expressions.functions.scalar.Translate; import org.apache.doris.nereids.trees.expressions.functions.scalar.Trim; +import org.apache.doris.nereids.trees.expressions.functions.scalar.TrimArray; import org.apache.doris.nereids.trees.expressions.functions.scalar.TrimIn; import org.apache.doris.nereids.trees.expressions.functions.scalar.Truncate; import org.apache.doris.nereids.trees.expressions.functions.scalar.Uncompress; @@ -2660,6 +2661,10 @@ default R visitTrim(Trim trim, C context) { return visitScalarFunction(trim, context); } + default R visitTrimArray(TrimArray trimArray, C context) { + return visitScalarFunction(trimArray, context); + } + default R visitTrimIn(TrimIn trimIn, C context) { return visitScalarFunction(trimIn, context); } diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/executable/ArrayArithmeticTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/executable/ArrayArithmeticTest.java new file mode 100644 index 00000000000000..386c9c11572682 --- /dev/null +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/executable/ArrayArithmeticTest.java @@ -0,0 +1,58 @@ +// 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.trees.expressions.functions.executable; + +import org.apache.doris.nereids.exceptions.AnalysisException; +import org.apache.doris.nereids.trees.expressions.literal.ArrayLiteral; +import org.apache.doris.nereids.trees.expressions.literal.BigIntLiteral; +import org.apache.doris.nereids.trees.expressions.literal.IntegerLiteral; + +import com.google.common.collect.ImmutableList; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class ArrayArithmeticTest { + + @Test + void testTrimArray() { + ArrayLiteral input = new ArrayLiteral(ImmutableList.of( + new IntegerLiteral(1), new IntegerLiteral(2), new IntegerLiteral(3))); + + ArrayLiteral trimmed = (ArrayLiteral) ArrayArithmetic.trimArray(input, new BigIntLiteral(1)); + Assertions.assertEquals(ImmutableList.of(new IntegerLiteral(1), new IntegerLiteral(2)), + trimmed.getValue()); + Assertions.assertEquals(input.getDataType(), trimmed.getDataType()); + + ArrayLiteral unchanged = (ArrayLiteral) ArrayArithmetic.trimArray(input, new BigIntLiteral(0)); + Assertions.assertEquals(input.getValue(), unchanged.getValue()); + + ArrayLiteral empty = (ArrayLiteral) ArrayArithmetic.trimArray(input, new BigIntLiteral(3)); + Assertions.assertTrue(empty.getValue().isEmpty()); + Assertions.assertEquals(input.getDataType(), empty.getDataType()); + } + + @Test + void testTrimArrayRejectsInvalidSize() { + ArrayLiteral input = new ArrayLiteral(ImmutableList.of(new IntegerLiteral(1))); + + Assertions.assertThrows(AnalysisException.class, + () -> ArrayArithmetic.trimArray(input, new BigIntLiteral(-1))); + Assertions.assertThrows(AnalysisException.class, + () -> ArrayArithmetic.trimArray(input, new BigIntLiteral(2))); + } +} diff --git a/regression-test/data/query_p0/sql_functions/array_functions/test_trim_array.out b/regression-test/data/query_p0/sql_functions/array_functions/test_trim_array.out new file mode 100644 index 00000000000000..549e0150fd8159 --- /dev/null +++ b/regression-test/data/query_p0/sql_functions/array_functions/test_trim_array.out @@ -0,0 +1,55 @@ +-- This file is automatically generated. You should know what you did if you want to edit this +-- !trim_two -- +[1, 2] + +-- !trim_zero -- +[1, 2, 3, 4] + +-- !trim_one -- +[1, 2, 3] + +-- !trim_all -- +[] + +-- !trim_string -- +["a", "b", "c"] + +-- !trim_null_element -- +["a", "b", null] + +-- !trim_nested -- +[[1, 2, 3]] + +-- !trim_empty -- +[] + +-- !trim_boolean -- +[1, 0] + +-- !trim_tinyint -- +[-128, 0] + +-- !trim_bigint -- +[-9223372036854775808, 0] + +-- !trim_double -- +[-1.7976931348623157e+308, 0] + +-- !trim_decimal -- +[-99999999.99, 0.00] + +-- !trim_date -- +["0000-01-01", "2024-02-29"] + +-- !trim_null_array -- +\N + +-- !trim_null_size -- +\N + +-- !trim_columns -- +1 [1, 2] +2 [5, 6] +3 [] +4 \N +5 \N diff --git a/regression-test/suites/query_p0/sql_functions/array_functions/test_trim_array.groovy b/regression-test/suites/query_p0/sql_functions/array_functions/test_trim_array.groovy new file mode 100644 index 00000000000000..9587d9f40e9cda --- /dev/null +++ b/regression-test/suites/query_p0/sql_functions/array_functions/test_trim_array.groovy @@ -0,0 +1,71 @@ +// 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_trim_array") { + qt_trim_two "select trim_array([1, 2, 3, 4], 2)" + qt_trim_zero "select trim_array([1, 2, 3, 4], 0)" + qt_trim_one "select trim_array([1, 2, 3, 4], 1)" + qt_trim_all "select trim_array([1, 2, 3, 4], 4)" + qt_trim_string "select trim_array(['a', 'b', 'c', 'd'], 1)" + qt_trim_null_element "select trim_array(['a', 'b', null, 'd'], 1)" + qt_trim_nested "select trim_array([[1, 2, 3], [4, 5, 6]], 1)" + qt_trim_empty "select trim_array(cast([] as array), 0)" + qt_trim_boolean "select trim_array(cast([true, false, true] as array), 1)" + qt_trim_tinyint "select trim_array(cast([-128, 0, 127] as array), 1)" + qt_trim_bigint "select trim_array(cast([-9223372036854775808, 0, 9223372036854775807] as array), 1)" + qt_trim_double "select trim_array(cast([-1.7976931348623157E308, 0.0, 1.7976931348623157E308] as array), 1)" + qt_trim_decimal "select trim_array(cast([-99999999.99, 0.00, 99999999.99] as array), 1)" + qt_trim_date "select trim_array(cast(['0000-01-01', '2024-02-29', '9999-12-31'] as array), 1)" + qt_trim_null_array "select trim_array(cast(null as array), 0)" + qt_trim_null_size "select trim_array([1, 2, 3], cast(null as bigint))" + + test { + sql "select trim_array([1, 2, 3, 4], 5)" + exception "size must not exceed array cardinality 4: 5" + } + test { + sql "select trim_array([1, 2, 3, 4], -1)" + exception "size must not be negative: -1" + } + test { + sql "select trim_array([1, 2, 3, 4], 9223372036854775807)" + exception "size must not exceed array cardinality 4: 9223372036854775807" + } + test { + sql "select trim_array([1, 2, 3, 4], -9223372036854775808)" + exception "size must not be negative: -9223372036854775808" + } + + sql "drop table if exists trim_array_test" + sql """ + create table trim_array_test ( + id int, + items array, + trim_size bigint + ) distributed by hash(id) buckets 1 + properties('replication_num' = '1') + """ + sql """ + insert into trim_array_test values + (1, [1, 2, 3, 4], 2), + (2, [5, 6], 0), + (3, [], 0), + (4, null, 0), + (5, [7, 8], null) + """ + order_qt_trim_columns "select id, trim_array(items, trim_size) from trim_array_test" +}