diff --git a/be/src/exprs/function/array/function_array_combinations.cpp b/be/src/exprs/function/array/function_array_combinations.cpp new file mode 100644 index 00000000000000..e3f26e0b1091c5 --- /dev/null +++ b/be/src/exprs/function/array/function_array_combinations.cpp @@ -0,0 +1,187 @@ +// 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 "common/compiler_util.h" +#include "common/status.h" +#include "core/assert_cast.h" +#include "core/column/column_array.h" +#include "core/column/column_const.h" +#include "core/data_type/data_type.h" +#include "core/data_type/data_type_array.h" +#include "core/data_type/data_type_decimal.h" +#include "core/data_type/data_type_nullable.h" +#include "core/types.h" +#include "exprs/function/function.h" +#include "exprs/function/function_helpers.h" +#include "exprs/function/simple_function_factory.h" + +namespace doris { +// array_combinations([1, 2, 3],2) -> [[1,2], [1,3], [2,3]] +// array_combinations([1, NULL, 3, NULL, 5],4) -> [[1,NULL,3,NULL], [1,NULL,3,5], [NULL,3,NULL,5]] + +class FunctionArrayCombinations : public IFunction { +public: + static constexpr auto name = "array_combinations"; + static FunctionPtr create() { return std::make_shared(); } + bool is_variadic() const override { return false; } + String get_name() const override { return name; } + + size_t get_number_of_arguments() const override { return 2; } + + bool use_default_implementation_for_nulls() const override { return true; } + + DataTypePtr get_return_type_impl(const DataTypes& arguments) const override { + const auto* array_type = assert_cast(arguments[0].get()); + auto elem_t = make_nullable(array_type->get_nested_type()); + auto res = std::make_shared( + make_nullable(std::make_shared(elem_t))); + return res; + } + + Status execute_impl(FunctionContext* context, Block& block, const ColumnNumbers& arguments, + uint32_t result, size_t input_rows_count) const override { + auto array = block.get_by_position(arguments[0]).column; + ColumnPtr num = block.get_by_position(arguments[1]).column; + Int64 combination_length = num->get_int(0); + + if (combination_length > MAX_COMBINATION_LENGTH || combination_length < 1) { + return Status::InvalidArgument( + fmt::format("execute failed, function {}'s second argument must be bigger than " + "0 and not bigger than 5", + get_name())); + } + + ColumnPtr res; + const auto* src_arr = assert_cast(remove_nullable(array).get()); + const auto& offsets = + assert_cast(src_arr->get_offsets_column()); + Status status = vector_const(src_arr, input_rows_count, res, offsets, combination_length); + if (!status.ok()) { + return status; + } + block.replace_by_position(result, std::move(res)); + return status; + } + + ColumnNumbers get_arguments_that_are_always_constant() const override { return {1}; } + +private: + static const size_t MAX_COMBINATION_LENGTH = 5; + static const size_t MAX_COMBINATION_COUNT = 100000; + + // Then combinationCount(n, k) = combinationCount(n-1, k-1) * n/k (https://en.wikipedia.org/wiki/Combination#Number_of_k-combinations) + // The formula is recursive. Here, instead of starting with k=combinationCount, n=arrayLength and recursing, + // we start with k=0 n=(arrayLength-combinationLength) and proceed "bottom up". + size_t _combination_count(size_t array_length, size_t combination_length) const { + size_t combinations = 1; + + for (size_t i = 1; i <= combination_length; i++) { + combinations = combinations * (array_length - combination_length + i) / i; + } + + return combinations; + } + + ALWAYS_INLINE std::vector _first_combination(Int64 combination_length, + size_t length) const { + std::vector comb(combination_length + 1); + std::iota(comb.begin(), comb.begin() + combination_length, 0); + comb[combination_length] = length; + return comb; + } + + // Generates the next k-combination in colex order. + // + // scan from the lowest index upward and increment the first + // position that can be increased without breaking the strictly + // increasing invariant. Resetting all lower positions to their + // minimal values ensures the result is the smallest combination + // greater than the current one. + bool _next_combination(std::vector& comb, Int64 combination_length) const { + for (size_t i = 0; i < combination_length; ++i) { + if (comb[i] + 1 < comb[i + 1]) { + ++comb[i]; + std::iota(comb.begin(), comb.begin() + i, 0); + return true; + } + } + return false; + } + + Status vector_const(const ColumnArray* nested_src_column_ptr, size_t input_rows_count, + ColumnPtr& res, const ColumnArray::ColumnOffsets& offsets, + Int64 combination_length) const { + const auto& src_data = nested_src_column_ptr->get_data(); + const auto& src_offsets = offsets.get_data(); + + auto inner_data = src_data.clone_empty(); + auto inner_offsets = ColumnArray::ColumnOffsets::create(); + auto inner_arr = ColumnArray::create(std::move(inner_data), std::move(inner_offsets)); + auto* inner = assert_cast(inner_arr.get()); + + auto outer_offsets = ColumnArray::ColumnOffsets::create(); + auto& outer_offsets_data = outer_offsets->get_data(); + outer_offsets_data.resize(input_rows_count); + + size_t prev_off = 0, outer_off = 0; + + for (size_t row = 0; row < input_rows_count; ++row) { + size_t curr_off = src_offsets[row]; + size_t row_len = curr_off - prev_off; + + if (combination_length <= 0 || combination_length > row_len) { + outer_offsets_data[row] = outer_off; + prev_off = curr_off; + continue; + } + + size_t combination_count = _combination_count(row_len, combination_length); + if (combination_count > MAX_COMBINATION_COUNT) { + return Status::InvalidArgument( + fmt::format("execute failed, function {}'s total size of sub-groups " + "generated must be smaller than 100,000", + get_name())); + } + std::vector comb = _first_combination(combination_length, row_len); + inner->get_data().reserve(inner->get_data().size() + + combination_count * combination_length); + inner->get_offsets().reserve(inner->get_offsets().size() + combination_count); + outer_off += combination_count; + do { + for (int i = 0; i < combination_length; ++i) { + size_t idx = prev_off + comb[i]; + inner->get_data().insert_from(src_data, idx); + } + inner->get_offsets().push_back(inner->get_data().size()); + } while (_next_combination(comb, combination_length)); + + outer_offsets_data[row] = outer_off; + prev_off = curr_off; + } + size_t inner_size = inner_arr->size(); + auto nullable_arr = + ColumnNullable::create(std::move(inner_arr), ColumnUInt8::create(inner_size, 0)); + res = ColumnArray::create(std::move(nullable_arr), std::move(outer_offsets)); + + return Status::OK(); + } +}; + +void register_function_array_combinations(SimpleFunctionFactory& factory) { + factory.register_function(); +} +} // namespace doris \ No newline at end of file diff --git a/be/src/exprs/function/array/function_array_register.cpp b/be/src/exprs/function/array/function_array_register.cpp index 7eca80dc5db671..0e16a8fa271358 100644 --- a/be/src/exprs/function/array/function_array_register.cpp +++ b/be/src/exprs/function/array/function_array_register.cpp @@ -57,6 +57,7 @@ void register_function_array_filter_function(SimpleFunctionFactory&); void register_function_array_splits(SimpleFunctionFactory&); void register_function_array_contains_all(SimpleFunctionFactory&); void register_function_array_match(SimpleFunctionFactory&); +void register_function_array_combinations(SimpleFunctionFactory&); void register_function_array(SimpleFunctionFactory& factory) { register_function_array_flatten(factory); @@ -95,6 +96,7 @@ void register_function_array(SimpleFunctionFactory& factory) { register_function_array_splits(factory); register_function_array_contains_all(factory); register_function_array_match(factory); + register_function_array_combinations(factory); } } // 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 c8719dbaeb5832..1f1ccddd0fae18 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 @@ -40,6 +40,7 @@ import org.apache.doris.nereids.trees.expressions.functions.scalar.Array; import org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayApply; import org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayAvg; +import org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayCombinations; import org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayCompact; import org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayConcat; import org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayContains; @@ -623,6 +624,7 @@ public class BuiltinScalarFunctions implements FunctionHelper { scalar(ArrayFirst.class, "array_first"), scalar(ArrayFirstIndex.class, "array_first_index"), scalar(ArrayFlatten.class, "array_flatten"), + scalar(ArrayCombinations.class, "array_combinations"), scalar(ArrayIntersect.class, "array_intersect"), scalar(ArrayJoin.class, "array_join"), scalar(ArrayLast.class, "array_last"), diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/ArrayCombinations.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/ArrayCombinations.java new file mode 100644 index 00000000000000..c09eadad20f53a --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/ArrayCombinations.java @@ -0,0 +1,85 @@ +// 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.exceptions.AnalysisException; +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.literal.IntegerLikeLiteral; +import org.apache.doris.nereids.trees.expressions.literal.NullLiteral; +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 org.apache.doris.nereids.types.coercion.FollowToAnyDataType; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; + +import java.util.List; + +/** + * ScalarFunction 'combinations' + */ +public class ArrayCombinations extends ScalarFunction + implements ExplicitlyCastableSignature, PropagateNullable { + + public static final List SIGNATURES = ImmutableList.of( + FunctionSignature.ret(ArrayType.of(ArrayType.of(new FollowToAnyDataType(0)))) + .args(ArrayType.of(new AnyDataType(0)), BigIntType.INSTANCE)); + + /** + * constructor with 2 arguments. + */ + public ArrayCombinations(Expression arg0, Expression arg1) { + super("array_combinations", arg0, arg1); + } + + @Override + public void checkLegalityBeforeTypeCoercion() { + if (!(child(1) instanceof IntegerLikeLiteral || child(1) instanceof NullLiteral)) { + throw new AnalysisException("Array_Combinations's second argument must be a constant literal."); + } + } + + @Override + public void checkLegalityAfterRewrite() { + checkLegalityBeforeTypeCoercion(); + } + + @Override + public List getSignatures() { + return SIGNATURES; + } + + /** + * withChildren. + */ + @Override + public ArrayCombinations withChildren(List children) { + Preconditions.checkArgument(children.size() == 2); + return new ArrayCombinations(children.get(0), children.get(1)); + } + + @Override + public R accept(ExpressionVisitor visitor, C context) { + return visitor.visitArrayCombinations(this, context); + } +} 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 c32d2157d50c50..475514d70d3bd5 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 @@ -42,6 +42,7 @@ import org.apache.doris.nereids.trees.expressions.functions.scalar.Array; import org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayApply; import org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayAvg; +import org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayCombinations; import org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayCompact; import org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayConcat; import org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayContains; @@ -823,6 +824,10 @@ default R visitArrayFlatten(ArrayFlatten arrayFlatten, C context) { return visitScalarFunction(arrayFlatten, context); } + default R visitArrayCombinations(ArrayCombinations arrayCombinations, C context) { + return visitScalarFunction(arrayCombinations, context); + } + default R visitArrayMap(ArrayMap arrayMap, C context) { return visitScalarFunction(arrayMap, context); } diff --git a/regression-test/data/nereids_function_p0/scalar_function/Array.out b/regression-test/data/nereids_function_p0/scalar_function/Array.out index 6a20260468e2bc..df67145772be9e 100644 --- a/regression-test/data/nereids_function_p0/scalar_function/Array.out +++ b/regression-test/data/nereids_function_p0/scalar_function/Array.out @@ -16128,3 +16128,20 @@ false false -- !sql -- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] +-- !sql -- +[["foo", "bar"], ["foo", "baz"], ["bar", "baz"]] + +-- !sql -- +[[1, 2], [1, 3], [2, 3]] + +-- !sql -- +[[1, 2], [1, 2], [2, 2]] + +-- !sql -- +[] + +-- !sql -- +[[null], [null]] + +-- !sql -- +[[[[1, 2, 3, 4, 5]], [[6, 7], [8, 9]]]] diff --git a/regression-test/data/query_p0/sql_functions/array_functions/array_combinations.out b/regression-test/data/query_p0/sql_functions/array_functions/array_combinations.out new file mode 100644 index 00000000000000..123c8217e04c81 --- /dev/null +++ b/regression-test/data/query_p0/sql_functions/array_functions/array_combinations.out @@ -0,0 +1,82 @@ +-- This file is automatically generated. You should know what you did if you want to edit this +-- !test_basic -- +1 [["foo", "bar"], ["foo", "baz"], ["bar", "baz"]] [[1, 2], [1, 3], [2, 3]] [[1, 2], [1, 2], [2, 2]] [[[1, 1], [4, 5]], [[1, 1], [1, 4]], [[4, 5], [1, 4]]] +2 [[null, null]] [[null, null]] [] [[[null, null], [null, null]], [[null, null], [null, null]], [[null, null], [null, null]]] +3 [] [] [] [] +4 [["a", "b"], ["a", "c"], ["b", "c"], ["a", "d"], ["b", "d"], ["c", "d"]] [[1, 2], [1, 3], [2, 3], [1, 4], [2, 4], [3, 4], [1, 5], [2, 5], [3, 5], [4, 5]] [[10, 20], [10, 30], [20, 30]] [[[1], [2]], [[1], [3]], [[2], [3]], [[1], [4]], [[2], [4]], [[3], [4]]] + +-- !test_empty_array -- +[] [] + +-- !test_param1_null -- +\N + +-- !test_param2_null -- +\N + +-- !test_param_3 -- +[["a", "b", "c"], ["a", "b", "d"], ["a", "c", "d"], ["b", "c", "d"]] [[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]] + +-- !test_param_4 -- +[["a", "b", "c", "d"], ["a", "b", "c", "e"], ["a", "b", "d", "e"], ["a", "c", "d", "e"], ["b", "c", "d", "e"]] [[1, 2, 3, 4], [1, 2, 3, 5], [1, 2, 4, 5], [1, 3, 4, 5], [2, 3, 4, 5]] + +-- !test_param_5 -- +[[1, 2, 3, 4, 5], [1, 2, 3, 4, 6], [1, 2, 3, 5, 6], [1, 2, 4, 5, 6], [1, 3, 4, 5, 6], [2, 3, 4, 5, 6]] + +-- !test_param_greater_than_length -- +[] [] + +-- !test_array_with_null -- +[[1, null], [1, 3], [null, 3]] [["a", null], ["a", "c"], [null, "c"]] + +-- !test_different_types -- +[[1.5, 2.5], [1.5, 3.5], [2.5, 3.5]] [[1, 0]] + +-- !test_date -- +[["2015-03-13", "2015-03-14"], ["2015-03-13", "2015-03-15"], ["2015-03-14", "2015-03-15"]] + +-- !test_date_cast -- +[["2015-03-13", "2015-03-14"], ["2015-03-13", "2015-03-15"], ["2015-03-14", "2015-03-15"]] + +-- !test_datetime -- +[["2015-03-13 12:36:38", "2015-03-14 12:36:38"]] + +-- !test_datetime_cast -- +[["2015-03-13 12:36:38", "2015-03-14 12:36:38"]] + +-- !test_datev2 -- +[["2023-02-05", "2023-02-06"], ["2023-02-05", "2023-02-07"], ["2023-02-06", "2023-02-07"]] + +-- !test_datetimev2 -- +[["2022-10-15 10:30:00.999", "2022-10-16 10:30:00.999"], ["2022-10-15 10:30:00.999", "2022-10-17 10:30:00.999"], ["2022-10-16 10:30:00.999", "2022-10-17 10:30:00.999"]] + +-- !test_datetimev2_6 -- +[["2022-10-15 10:30:00.999999", "2022-10-16 10:30:00.999999"], ["2022-10-15 10:30:00.999999", "2022-10-17 10:30:00.999999"], ["2022-10-16 10:30:00.999999", "2022-10-17 10:30:00.999999"]] + +-- !test_decimalv3 -- +[[111.111, 222.222], [111.111, 333.333], [222.222, 333.333]] + +-- !test_decimalv2 -- +[[0.100, 0.200], [0.100, 0.300], [0.200, 0.300]] + +-- !test_smallint_bigint -- +[[1, 2], [1, 3], [2, 3], [1, 4], [2, 4], [3, 4]] [[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4], [1, 2, 5], [1, 3, 5], [2, 3, 5], [1, 4, 5], [2, 4, 5], [3, 4, 5]] + +-- !test_char_varchar -- +[["a", "bb"], ["a", "ccc"], ["bb", "ccc"]] [["x", "y"], ["x", "z"], ["y", "z"]] + +-- !test_array_decimal -- +[[[1.10], [2.20]], [[1.10], [3.30]], [[2.20], [3.30]]] + +-- !test_array_datetime -- +[[["2023-01-01 00:00:00"], ["2023-01-02 00:00:00"]], [["2023-01-01 00:00:00"], ["2023-01-03 00:00:00"]], [["2023-01-02 00:00:00"], ["2023-01-03 00:00:00"]]] + +-- !test_array_timestamptz -- +[["2026-01-01 00:00:00+08:00", "2026-01-01 00:00:00+08:00"], ["2026-01-01 00:00:00+08:00", "2026-01-01 00:00:00+08:00"], ["2026-01-01 00:00:00+08:00", "2026-01-01 00:00:00+08:00"]] + +-- !test_ipv4 -- +[["192.168.1.1", "10.0.0.1"], ["192.168.1.1", "172.16.0.1"], ["10.0.0.1", "172.16.0.1"]] + +-- !test_ipv6 -- +[["2001:db8::1", "2001:db8::2"], ["2001:db8::1", "2001:db8::3"], ["2001:db8::2", "2001:db8::3"]] + diff --git a/regression-test/suites/nereids_function_p0/scalar_function/Array.groovy b/regression-test/suites/nereids_function_p0/scalar_function/Array.groovy index 9bdef14151b709..b648354a44ca49 100644 --- a/regression-test/suites/nereids_function_p0/scalar_function/Array.groovy +++ b/regression-test/suites/nereids_function_p0/scalar_function/Array.groovy @@ -1367,4 +1367,14 @@ suite("nereids_scalar_fn_Array") { qt_sql """select array_flatten([ [[1,2,3,4,5]],[[6,7],[8,9]] ]);""" qt_sql """select array_flatten([[[[[[1,2,3,4,5],[6,7],[8,9],[10,11],[12]]]]]]);""" + qt_sql """select array_combinations(['foo','bar','baz'], 2);""" + qt_sql """select array_combinations([1,2,3], 2);""" + qt_sql """select array_combinations([1,2,2], 2);""" + qt_sql """select array_combinations([], 2);""" + qt_sql """select array_combinations([NULL,NULL], 1);""" + qt_sql """select array_combinations([[[1,2,3,4,5]],[[6,7],[8,9]]],2);""" + test { + sql """select array_combinations([1,2,3], -1);""" + exception("execute failed, function array_combinations's second argument must be bigger than 0 and not bigger than 5") + } } diff --git a/regression-test/suites/query_p0/sql_functions/array_functions/array_combinations.groovy b/regression-test/suites/query_p0/sql_functions/array_functions/array_combinations.groovy new file mode 100644 index 00000000000000..139a0f5a0a5e00 --- /dev/null +++ b/regression-test/suites/query_p0/sql_functions/array_functions/array_combinations.groovy @@ -0,0 +1,257 @@ +// 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("array_combinations") { + sql """DROP TABLE IF EXISTS t_array_combinations""" + sql """ + CREATE TABLE IF NOT EXISTS t_array_combinations ( + `k1` int(11) NULL COMMENT "", + `s1` array NULL COMMENT "", + `a1` array NULL COMMENT "", + `a2` array NULL COMMENT "", + `aa1` array> NOT NULL COMMENT "", + ) ENGINE=OLAP + DISTRIBUTED BY HASH(`k1`) BUCKETS 1 + PROPERTIES ( + "replication_allocation" = "tag.location.default: 1", + "storage_format" = "V2" + ) + """ + sql """ INSERT INTO t_array_combinations VALUES(1, ['foo','bar','baz'], [1,2,3], [1,2,2], [[1,1],[4,5],[1,4]]) """ + sql """ INSERT INTO t_array_combinations VALUES(2, [null,null], [null,null], [], [[null,null],[null,null],[null,null]]) """ + sql """ INSERT INTO t_array_combinations VALUES(3, [], [], [], [[]]) """ + sql """ INSERT INTO t_array_combinations VALUES(4, ['a','b','c','d'], [1,2,3,4,5], [10,20,30], [[1],[2],[3],[4]]) """ + + qt_test_basic """ + select k1, array_combinations(s1, 2), array_combinations(a1, 2), array_combinations(a2, 2), array_combinations(aa1, 2) from t_array_combinations order by k1; + """ + + qt_test_empty_array """ + select array_combinations([], 1), array_combinations([], 2); + """ + + qt_test_param1_null """ + select array_combinations(null, 2); + """ + + qt_test_param2_null """ + select array_combinations([1,2,3], null); + """ + + test { + sql """select k1, array_combinations(['x','y','z'], k1) from t_array_combinations where k1 <= 3 order by k1;""" + exception("Array_Combinations's second argument must be a constant literal.") + } + + qt_test_param_3 """ + select array_combinations(['a','b','c','d'], 3), array_combinations([1,2,3,4], 3); + """ + + qt_test_param_4 """ + select array_combinations(['a','b','c','d','e'], 4), array_combinations([1,2,3,4,5], 4); + """ + + qt_test_param_5 """ + select array_combinations([1,2,3,4,5,6], 5); + """ + + qt_test_param_greater_than_length """ + select array_combinations([1,2], 3), array_combinations(['a'], 2); + """ + + qt_test_array_with_null """ + select array_combinations([1,null,3], 2), array_combinations(['a',null,'c'], 2); + """ + + qt_test_different_types """ + select array_combinations([1.5, 2.5, 3.5], 2), array_combinations([true, false], 2); + """ + + qt_test_date """ + select array_combinations(['2015-03-13','2015-03-14','2015-03-15'], 2); + """ + + qt_test_date_cast """ + select array_combinations( + array(cast('2015-03-13' as date), cast('2015-03-14' as date), cast('2015-03-15' as date)), + 2 + ); + """ + + qt_test_datetime """ + select array_combinations(['2015-03-13 12:36:38','2015-03-14 12:36:38'], 2); + """ + + qt_test_datetime_cast """ + select array_combinations( + array(cast('2015-03-13 12:36:38' as datetime), cast('2015-03-14 12:36:38' as datetime)), + 2 + ); + """ + + qt_test_datev2 """ + select array_combinations( + array(cast('2023-02-05' as datev2), cast('2023-02-06' as datev2), cast('2023-02-07' as datev2)), 2 + ); + """ + + qt_test_datetimev2 """ + select array_combinations( + array( + cast('2022-10-15 10:30:00.999' as datetimev2(3)), + cast('2022-10-16 10:30:00.999' as datetimev2(3)), + cast('2022-10-17 10:30:00.999' as datetimev2(3)) + ), + 2 + ); + """ + + qt_test_datetimev2_6 """ + select array_combinations( + array( + cast('2022-10-15 10:30:00.999999' as datetimev2(6)), + cast('2022-10-16 10:30:00.999999' as datetimev2(6)), + cast('2022-10-17 10:30:00.999999' as datetimev2(6)) + ), + 2 + ); + """ + + qt_test_decimalv3 """ + select array_combinations( + array( + cast(111.111 as decimalv3(6,3)), + cast(222.222 as decimalv3(6,3)), + cast(333.333 as decimalv3(6,3)) + ), + 2 + ); + """ + + qt_test_decimalv2 """ + select array_combinations( + cast( + array( + cast(0.100 as decimal(9,3)), + cast(0.200 as decimal(9,3)), + cast(0.300 as decimal(9,3)) + ) as array + ), + 2 + ); + """ + + qt_test_smallint_bigint """ + select + array_combinations(cast(array(1,2,3,4) as array), 2), + array_combinations(cast(array(1,2,3,4,5) as array), 3); + """ + + qt_test_char_varchar """ + select array_combinations( + cast(array('a','bb','ccc') as array), + 2 + ), + array_combinations( + cast(array('x','y','z') as array), + 2 + ); + """ + + qt_test_array_decimal """ + select array_combinations( + array( + array(cast(1.1 as decimalv3(6,2))), + array(cast(2.2 as decimalv3(6,2))), + array(cast(3.3 as decimalv3(6,2))) + ), + 2 + ); + """ + + qt_test_array_datetime """ + select array_combinations( + array( + array(cast('2023-01-01 00:00:00' as datetime)), + array(cast('2023-01-02 00:00:00' as datetime)), + array(cast('2023-01-03 00:00:00' as datetime)) + ), + 2 + ); + """ + + qt_test_array_timestamptz """ + select array_combinations( + array( + cast('2026-01-01 00:00:00' as TIMESTAMPTZ), + cast('2026-01-01 00:00:00' as TIMESTAMPTZ), + cast('2026-01-01 00:00:00' as TIMESTAMPTZ) + ), + 2 + ); + """ + + qt_test_ipv4 """ + select array_combinations( + array( + cast('192.168.1.1' as ipv4), + cast('10.0.0.1' as ipv4), + cast('172.16.0.1' as ipv4) + ), + 2 + ); + """ + + qt_test_ipv6 """ + select array_combinations( + array( + cast('2001:db8::1' as ipv6), + cast('2001:db8::2' as ipv6), + cast('2001:db8::3' as ipv6) + ), + 2 + ); + """ + + test { + sql """select array_combinations([1,2,3], -1);""" + exception("execute failed, function array_combinations's second argument must be bigger than 0 and not bigger than 5") + } + + test { + sql """select array_combinations([1,2,3], 0);""" + exception("execute failed, function array_combinations's second argument must be bigger than 0 and not bigger than 5") + } + + test { + sql """select array_combinations([1,2,3,4,5,6], 6);""" + exception("execute failed, function array_combinations's second argument must be bigger than 0 and not bigger than 5") + } + + test { + sql """select array_combinations([1,2,3,4,5,6], 6);""" + exception("execute failed, function array_combinations's second argument must be bigger than 0 and not bigger than 5") + } + + test { + sql """select array_combinations([1,2,3,4,5,6,7,8,9,10, + 11,12,13,14,15,16,17,18,19,20, + 21,22,23,24,25,26,27,28,29], 5);""" + exception("execute failed, function array_combinations's total size of sub-groups generated must be smaller than 100,000") + } + +} \ No newline at end of file