This is an automated email from the ASF dual-hosted git repository.
csun5285 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 a53b182f810 [fix](function) Reject a non-constant seed in
array_shuffle (#68438)
a53b182f810 is described below
commit a53b182f810be2b3731c7dfdf6f40ff5a0ad63bd
Author: Chenyang Sun <[email protected]>
AuthorDate: Fri Oct 9 17:21:35 2026 +0800
[fix](function) Reject a non-constant seed in array_shuffle (#68438)
### What problem does this PR solve?
Issue Number: None
Related PR: #44901
Problem Summary:
1. `array_shuffle` now rejects a non-constant seed, which was silently
ignored for every row but the first one of each block.
2. The seed now uses all 64 bits (`std::mt19937_64`), so any BIGINT seed
works, including a negative one, which failed with an out-of-range error
since #44901.
3. Every row is now shuffled on its own, so a constant array no longer
gets the same order on every row, and blocks without a seed no longer
repeat each other.
doc: https://github.com/apache/doris-website/pull/4176
### Release note
`array_shuffle` / `shuffle` rejects a non-constant seed, uses all 64
bits of the seed (so the order for a given seed changes), and shuffles
every row on its own.
### Check List (For Author)
- Test <!-- At least one of them must be included. -->
- [x] Regression test
- [x] Unit Test
- [ ] Manual test (add detailed scripts or steps below)
- [ ] No need to test or manual test. Explain why:
- [ ] This is a refactor/code format and no logic has been changed.
- [ ] Previous test can cover this change.
- [ ] No code files have been changed.
- [ ] Other reason <!-- Add your reason? -->
- Behavior changed:
- [ ] No.
- [x] Yes. See the three points above.
- Does this need documentation?
- [ ] No.
- [x] Yes. <!-- apache/doris-website PR link to be added -->
### Check List (For Reviewer who merge this PR)
- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label <!-- Add branch pick label that this PR
should merge into -->
---------
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
---
.../function/array/function_array_shuffle.cpp | 32 ++-
be/src/exprs/vectorized_fn_call.cpp | 3 +-
.../exprs/function/function_array_shuffle_test.cpp | 227 +++++++++++++++++++++
.../expressions/functions/scalar/ArrayShuffle.java | 28 ++-
.../functions/scalar/UniqueFunctionTest.java | 13 ++
.../sql-function/test_array_function.out | 2 +-
.../array_functions/test_array_shuffle_seed.out | 26 +++
.../size_funciton/test_size_function.out | 12 +-
.../array_functions/test_array_shuffle_seed.groovy | 101 +++++++++
9 files changed, 425 insertions(+), 19 deletions(-)
diff --git a/be/src/exprs/function/array/function_array_shuffle.cpp
b/be/src/exprs/function/array/function_array_shuffle.cpp
index df764f29c1e..f5cc6c86554 100644
--- a/be/src/exprs/function/array/function_array_shuffle.cpp
+++ b/be/src/exprs/function/array/function_array_shuffle.cpp
@@ -16,10 +16,9 @@
// under the License.
#include <fmt/format.h>
#include <glog/logging.h>
-#include <stdint.h>
-#include <time.h>
#include <algorithm>
+#include <cstdint>
#include <memory>
#include <ostream>
#include <random>
@@ -64,21 +63,40 @@ public:
return arguments[0];
}
+ // Shuffle a constant array on each row too, so every row gets its own
order.
+ bool use_default_implementation_for_constants() const override { return
false; }
+
+ Status open(FunctionContext* context, FunctionContext::FunctionStateScope
scope) override {
+ // The rows of a block draw from one random sequence that starts from
the seed, so a
+ // per-row seed would be ignored. Reject a non-constant seed instead.
+ if (scope == FunctionContext::THREAD_LOCAL && context->get_num_args()
== 2 &&
+ !context->is_col_constant(1)) {
+ return Status::InvalidArgument("The seed of {} must be a
constant", get_name());
+ }
+ return Status::OK();
+ }
+
Status execute_impl(FunctionContext* context, Block& block, const
ColumnNumbers& arguments,
uint32_t result, size_t input_rows_count) const
override {
ColumnPtr src_column =
block.get_by_position(arguments[0]).column->convert_to_full_column_if_const();
const auto& src_column_array = assert_cast<const
ColumnArray&>(*src_column);
- size_t seed = time(nullptr);
+ uint64_t seed = 0;
if (arguments.size() == 2) {
+ // open() makes sure the seed is a constant, so read it from the
first row.
ColumnPtr seed_column =
block.get_by_position(arguments[1]).column->convert_to_full_column_if_const();
- seed = assert_cast<const
ColumnInt64*>(seed_column.get())->get_element(0);
+ // Use all 64 bits, so any BIGINT works, a negative one too.
+ seed = static_cast<uint64_t>(
+ assert_cast<const
ColumnInt64*>(seed_column.get())->get_element(0));
+ } else {
+ // Give each block its own random seed, so blocks do not repeat
the same orders.
+ std::random_device random_device;
+ seed = (static_cast<uint64_t>(random_device()) << 32) |
random_device();
}
- // time() and seed will not exceed the range of uint32.
- std::mt19937 g(cast_set<uint32_t>(seed));
+ std::mt19937_64 g(seed);
auto dest_column_ptr = _execute(src_column_array, g);
if (!dest_column_ptr) {
return Status::RuntimeError(
@@ -91,7 +109,7 @@ public:
}
private:
- ColumnPtr _execute(const ColumnArray& src_column_array, std::mt19937& g)
const {
+ ColumnPtr _execute(const ColumnArray& src_column_array, std::mt19937_64&
g) const {
const auto& src_offsets = src_column_array.get_offsets();
const auto src_nested_column = src_column_array.get_data_ptr();
diff --git a/be/src/exprs/vectorized_fn_call.cpp
b/be/src/exprs/vectorized_fn_call.cpp
index 06bdfdd1bcb..a908fea81d3 100644
--- a/be/src/exprs/vectorized_fn_call.cpp
+++ b/be/src/exprs/vectorized_fn_call.cpp
@@ -671,8 +671,9 @@ bool VectorizedFnCall::can_push_down_to_index() const {
}
bool VectorizedFnCall::is_deterministic() const {
+ // array_shuffle gives the rows of a block different orders, even with a
seed.
static const std::set<std::string> NON_DETERMINISTIC_FUNCTIONS = {
- "random", "rand", "random_bytes", "uuid", "uuid_numeric"};
+ "random", "rand", "random_bytes", "uuid", "uuid_numeric",
"array_shuffle", "shuffle"};
return !NON_DETERMINISTIC_FUNCTIONS.contains(_function_name) &&
VExpr::is_deterministic();
}
diff --git a/be/test/exprs/function/function_array_shuffle_test.cpp
b/be/test/exprs/function/function_array_shuffle_test.cpp
new file mode 100644
index 00000000000..d4f1313cde3
--- /dev/null
+++ b/be/test/exprs/function/function_array_shuffle_test.cpp
@@ -0,0 +1,227 @@
+// 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 <cstdint>
+#include <limits>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "core/block/block.h"
+#include "core/column/column_const.h"
+#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"
+#include "exprs/function/simple_function_factory.h"
+#include "exprs/vectorized_fn_call.h"
+#include "gen_cpp/Exprs_types.h"
+
+namespace doris {
+
+static const TestArray kArray = {Int32(1), Int32(2), Int32(3), Int32(4),
Int32(5)};
+
+// Two random orders of 20 elements are the same only by a tiny chance, so
tests that expect
+// different orders use this array.
+static const TestArray kLongArray = [] {
+ TestArray array;
+ for (int32_t i = 0; i < 20; ++i) {
+ array.emplace_back(Int32(i));
+ }
+ return array;
+}();
+
+// Runs array_shuffle on one block with one row per element of arrays, and
puts each result row
+// as a string in results. With const_array, the arrays must all be the same
and the array column
+// is a ColumnConst. An empty seeds runs array_shuffle(array), one seed is a
constant seed, and
+// more seeds are a seed column with one seed per row.
+// Returns the first failed status of open() and execute().
+static Status run_array_shuffle(const std::vector<TestArray>& arrays, bool
const_array,
+ const std::vector<int64_t>& seeds,
+ std::vector<std::string>* results) {
+ auto array_type =
+
std::make_shared<DataTypeArray>(make_nullable(std::make_shared<DataTypeInt32>()));
+ auto seed_type = std::make_shared<DataTypeInt64>();
+ const size_t row_size = arrays.size();
+ // Empty rows make a failed call fail the checks instead of reading past
the end.
+ results->assign(row_size, "");
+
+ MutableColumnPtr array_column = array_type->create_column();
+ for (size_t i = 0; i < (const_array ? 1 : row_size); ++i) {
+ EXPECT_TRUE(insert_cell(array_column, array_type, arrays[i]));
+ }
+ ColumnPtr array_ptr = std::move(array_column);
+ if (const_array) {
+ array_ptr = ColumnConst::create(array_ptr, row_size);
+ }
+
+ Block block;
+ block.insert({array_ptr, array_type, "array"});
+ DataTypes arg_types = {array_type};
+ ColumnNumbers arguments = {0};
+ std::vector<std::shared_ptr<ColumnPtrWrapper>> constant_cols = {nullptr};
+ if (const_array) {
+ constant_cols[0] = std::make_shared<ColumnPtrWrapper>(array_ptr);
+ }
+ if (!seeds.empty()) {
+ MutableColumnPtr seed_column = seed_type->create_column();
+ for (auto seed : seeds) {
+ EXPECT_TRUE(insert_cell(seed_column, seed_type, seed));
+ }
+ ColumnPtr seed_ptr = std::move(seed_column);
+ constant_cols.push_back(nullptr);
+ if (seeds.size() == 1) {
+ seed_ptr = ColumnConst::create(seed_ptr, row_size);
+ constant_cols[1] = std::make_shared<ColumnPtrWrapper>(seed_ptr);
+ } else {
+ EXPECT_EQ(seeds.size(), row_size);
+ }
+ block.insert({seed_ptr, seed_type, "seed"});
+ arg_types.push_back(seed_type);
+ arguments.push_back(1);
+ }
+
+ DataTypePtr return_type = array_type;
+ FunctionBasePtr func = SimpleFunctionFactory::instance().get_function(
+ "array_shuffle", block.get_columns_with_type_and_name(),
return_type);
+ EXPECT_NE(func, nullptr);
+
+ FunctionUtils fn_utils(return_type, arg_types, false);
+ auto* fn_ctx = fn_utils.get_fn_ctx();
+ fn_ctx->set_constant_cols(constant_cols);
+ RETURN_IF_ERROR(func->open(fn_ctx, FunctionContext::FRAGMENT_LOCAL));
+ RETURN_IF_ERROR(func->open(fn_ctx, FunctionContext::THREAD_LOCAL));
+
+ block.insert({nullptr, return_type, "result"});
+ auto result_idx = block.columns() - 1;
+ RETURN_IF_ERROR(func->execute(fn_ctx, block, arguments, result_idx,
row_size));
+ static_cast<void>(func->close(fn_ctx, FunctionContext::THREAD_LOCAL));
+ static_cast<void>(func->close(fn_ctx, FunctionContext::FRAGMENT_LOCAL));
+
+ const auto& result_column = *block.get_by_position(result_idx).column;
+ for (size_t i = 0; i < row_size; ++i) {
+ (*results)[i] = return_type->to_string(result_column, i);
+ }
+ return Status::OK();
+}
+
+// Builds an array_shuffle(array) call under the given function name.
+static VExprSPtr array_shuffle_call(const std::string& function_name) {
+ DataTypePtr array_type =
+
std::make_shared<DataTypeArray>(make_nullable(std::make_shared<DataTypeInt32>()));
+ TFunctionName fn_name;
+ fn_name.__set_function_name(function_name);
+ TFunction fn;
+ fn.__set_name(fn_name);
+ fn.__set_binary_type(TFunctionBinaryType::BUILTIN);
+ fn.__set_arg_types({array_type->to_thrift()});
+ fn.__set_ret_type(array_type->to_thrift());
+ fn.__set_has_var_args(true);
+
+ TExprNode node;
+ node.__set_node_type(TExprNodeType::FUNCTION_CALL);
+ node.__set_type(array_type->to_thrift());
+ node.__set_fn(fn);
+ node.__set_num_children(1);
+ node.__set_is_nullable(true);
+ return VectorizedFnCall::create_shared(node);
+}
+
+// Runs array_shuffle(array, seed) with a constant seed, which must succeed.
+static std::vector<std::string> shuffle_with_const_seed(const
std::vector<TestArray>& arrays,
+ int64_t seed, bool
const_array = false) {
+ std::vector<std::string> results;
+ auto st = run_array_shuffle(arrays, const_array, {seed}, &results);
+ EXPECT_TRUE(st.ok()) << st;
+ return results;
+}
+
+// Runs array_shuffle(array) on a constant array, which must succeed.
+static std::vector<std::string> shuffle_const_array_without_seed(const
TestArray& array,
+ size_t
row_size) {
+ std::vector<std::string> results;
+ auto st = run_array_shuffle(std::vector<TestArray>(row_size, array), true,
{}, &results);
+ EXPECT_TRUE(st.ok()) << st;
+ return results;
+}
+
+// The rows of a block draw from one random sequence that starts from the
seed, so a per-row
+// seed would be ignored. A non-constant seed is rejected instead.
+TEST(function_array_shuffle_test, non_constant_seed) {
+ std::vector<std::string> results;
+ auto st = run_array_shuffle({kArray, kArray}, false, {1, 2}, &results);
+ EXPECT_TRUE(st.is<ErrorCode::INVALID_ARGUMENT>()) << st;
+ EXPECT_NE(st.to_string().find("must be a constant"), std::string::npos) <<
st;
+}
+
+// A constant seed gives the same result each time, and the first row gives
the same result as
+// running that row alone.
+TEST(function_array_shuffle_test, const_seed) {
+ auto seed1 = shuffle_with_const_seed({kArray}, 1);
+ auto results = shuffle_with_const_seed({kArray, kArray, kArray}, 1);
+ EXPECT_EQ(results[0], seed1[0]);
+ EXPECT_EQ(shuffle_with_const_seed({kArray, kArray, kArray}, 1), results);
+ EXPECT_NE(shuffle_with_const_seed({kLongArray}, 2),
shuffle_with_const_seed({kLongArray}, 1));
+}
+
+// Any BIGINT is a valid seed, a negative one too. All 64 bits are used, so
seeds with the same
+// low 32 bits still give different results.
+TEST(function_array_shuffle_test, any_bigint_seed) {
+ EXPECT_NE(shuffle_with_const_seed({kLongArray}, -1),
+ shuffle_with_const_seed({kLongArray}, 4294967295));
+ EXPECT_NE(shuffle_with_const_seed({kLongArray}, 4294967301),
+ shuffle_with_const_seed({kLongArray}, 5));
+ EXPECT_NE(shuffle_with_const_seed({kLongArray},
std::numeric_limits<int64_t>::min()),
+ shuffle_with_const_seed({kLongArray}, 0));
+ EXPECT_NE(shuffle_with_const_seed({kLongArray},
std::numeric_limits<int64_t>::max()),
+ shuffle_with_const_seed({kLongArray}, -1));
+}
+
+// Arrays with 0 or 1 element stay the same.
+TEST(function_array_shuffle_test, short_arrays) {
+ const TestArray empty_array = {};
+ const TestArray one_element = {Int32(7)};
+ auto results = shuffle_with_const_seed({empty_array, one_element, kArray},
1);
+ EXPECT_EQ(results[0], "[]");
+ EXPECT_EQ(results[1], "[7]");
+}
+
+// A constant array is still shuffled on each row, so it gives the same rows
as the same arrays
+// in a column, and the rows do not all get the same order.
+TEST(function_array_shuffle_test, const_array) {
+ const std::vector<TestArray> arrays(3, kLongArray);
+ auto from_column = shuffle_with_const_seed(arrays, 1);
+ EXPECT_EQ(shuffle_with_const_seed(arrays, 1, true), from_column);
+ EXPECT_NE(from_column[0], from_column[1]);
+}
+
+// Without a seed, each row and each call gets a new random order.
+TEST(function_array_shuffle_test, no_seed) {
+ auto first = shuffle_const_array_without_seed(kLongArray, 2);
+ auto second = shuffle_const_array_without_seed(kLongArray, 2);
+ EXPECT_NE(first[0], first[1]);
+ EXPECT_NE(first[0], second[0]);
+}
+
+// Running array_shuffle again on other rows gives other orders, so it is not
deterministic, and
+// a scan must not run it twice, for example as a file-local filter copy and
again in the scanner.
+TEST(function_array_shuffle_test, not_deterministic) {
+ EXPECT_FALSE(array_shuffle_call("array_shuffle")->is_deterministic());
+ EXPECT_FALSE(array_shuffle_call("shuffle")->is_deterministic());
+}
+
+} // namespace doris
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/ArrayShuffle.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/ArrayShuffle.java
index 3eef9c608bb..f9039ace214 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/ArrayShuffle.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/ArrayShuffle.java
@@ -18,7 +18,9 @@
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.VolatileIdentity;
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;
@@ -36,7 +38,7 @@ import java.util.List;
* ScalarFunction 'array_shuffle'
* with 1 or 2 arguments : array_shuffle(arr) or array_shuffle(arr, seed)
*/
-public class ArrayShuffle extends ScalarFunction
+public class ArrayShuffle extends UniqueFunction
implements BinaryExpression, ExplicitlyCastableSignature,
PropagateNullable {
public static final List<FunctionSignature> SIGNATURES = ImmutableList.of(
@@ -49,18 +51,22 @@ public class ArrayShuffle extends ScalarFunction
* constructor with 1 arguments.
*/
public ArrayShuffle(Expression arg) {
- super("array_shuffle", arg);
+ this(VolatileIdentity.newVolatileIdentity(), ImmutableList.of(arg));
}
/**
* constructor with 2 arguments.
*/
public ArrayShuffle(Expression arg, Expression arg1) {
- super("array_shuffle", arg, arg1);
+ this(VolatileIdentity.newVolatileIdentity(), ImmutableList.of(arg,
arg1));
+ }
+
+ private ArrayShuffle(VolatileIdentity volatileIdentity, List<Expression>
children) {
+ super("array_shuffle", volatileIdentity, children);
}
/** constructor for withChildren and reuse signature */
- private ArrayShuffle(ScalarFunctionParams functionParams) {
+ private ArrayShuffle(UniqueFunctionParams functionParams) {
super(functionParams);
}
@@ -73,6 +79,20 @@ public class ArrayShuffle extends ScalarFunction
return new ArrayShuffle(getFunctionParams(children));
}
+ @Override
+ public ArrayShuffle withIgnoreUniqueId(boolean ignoreUniqueId) {
+ return new
ArrayShuffle(volatileIdentity.withIgnoreUniqueId(ignoreUniqueId), children);
+ }
+
+ @Override
+ public void checkLegalityBeforeTypeCoercion() {
+ // The rows of a block draw from one random sequence that starts from
the seed,
+ // so a per-row seed would be ignored.
+ if (arity() == 2 && !getArgument(1).isConstant()) {
+ throw new AnalysisException("The seed of array_shuffle must be a
constant: " + toSql());
+ }
+ }
+
@Override
public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
return visitor.visitArrayShuffle(this, context);
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/scalar/UniqueFunctionTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/scalar/UniqueFunctionTest.java
index c0dd9dc0d14..97b3a3b0f96 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/scalar/UniqueFunctionTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/scalar/UniqueFunctionTest.java
@@ -26,6 +26,7 @@ import
org.apache.doris.nereids.trees.expressions.NamedExpression;
import org.apache.doris.nereids.trees.expressions.SlotReference;
import org.apache.doris.nereids.trees.expressions.WindowExpression;
import org.apache.doris.nereids.trees.expressions.functions.agg.Sum;
+import org.apache.doris.nereids.trees.expressions.literal.ArrayLiteral;
import org.apache.doris.nereids.trees.expressions.literal.BigIntLiteral;
import org.apache.doris.nereids.trees.plans.Plan;
import org.apache.doris.nereids.trees.plans.logical.LogicalAggregate;
@@ -97,6 +98,18 @@ class UniqueFunctionTest extends SqlTestBase {
Assertions.assertNotEquals(uuidNum, new UuidNumeric());
Assertions.assertEquals(uuidNum.withIgnoreUniqueId(true), new
UuidNumeric().withIgnoreUniqueId(true));
Assertions.assertEquals(uuidNum, uuidNum.withChildren());
+
+ ArrayLiteral array = new ArrayLiteral(ImmutableList.of(new
BigIntLiteral(1L), new BigIntLiteral(2L)));
+ ArrayShuffle shuffle1 = new ArrayShuffle(array);
+ ArrayShuffle shuffle2 = new ArrayShuffle(array, new
BigIntLiteral(10L));
+ Assertions.assertNotEquals(shuffle1, new ArrayShuffle(array));
+ Assertions.assertEquals(shuffle1.withIgnoreUniqueId(true), new
ArrayShuffle(array).withIgnoreUniqueId(true));
+ Assertions.assertEquals(shuffle1, shuffle1.withChildren(array));
+ Assertions.assertEquals(shuffle1, shuffle1.withChildren(array, new
BigIntLiteral(10L))); // only compare unique id
+ Assertions.assertNotEquals(shuffle2, new ArrayShuffle(array, new
BigIntLiteral(10L)));
+ Assertions.assertEquals(shuffle2.withIgnoreUniqueId(true),
+ new ArrayShuffle(array, new
BigIntLiteral(10L)).withIgnoreUniqueId(true));
+ Assertions.assertEquals(shuffle2, shuffle2.withChildren(array, new
BigIntLiteral(10L)));
}
@Test
diff --git
a/regression-test/data/doc/sql-manual/sql-function/test_array_function.out
b/regression-test/data/doc/sql-manual/sql-function/test_array_function.out
index a8b38ff8c67..3d380e1474b 100644
--- a/regression-test/data/doc/sql-manual/sql-function/test_array_function.out
+++ b/regression-test/data/doc/sql-manual/sql-function/test_array_function.out
@@ -427,7 +427,7 @@ false
[[1, 2], [3]]
-- !sql --
-[3, 5, 4, 1, 2]
+[3, 1, 2, 4, 5]
-- !sql --
3
diff --git
a/regression-test/data/query_p0/sql_functions/array_functions/test_array_shuffle_seed.out
b/regression-test/data/query_p0/sql_functions/array_functions/test_array_shuffle_seed.out
new file mode 100644
index 00000000000..0ca7a1e70a7
--- /dev/null
+++
b/regression-test/data/query_p0/sql_functions/array_functions/test_array_shuffle_seed.out
@@ -0,0 +1,26 @@
+-- This file is automatically generated. You should know what you did if you
want to edit this
+-- !const_seed --
+[4, 1, 5, 3, 2] [1, 2, 5, 4, 3] [4, 1, 5, 3, 2] [1, 2, 5, 4, 3]
+
+-- !bigint_seed --
+[4, 5, 2, 6, 9, 0, 1, 3, 8, 7] [5, 3, 8, 6, 7, 9, 0, 4, 2, 1] [0, 6, 3, 1, 2,
5, 9, 8, 7, 4] [4, 7, 2, 3, 8, 0, 1, 5, 9, 6] [7, 2, 5, 4, 6, 9, 8, 0, 1, 3]
+
+-- !null_seed --
+\N
+
+-- !table --
+1 [1, 2, 3, 4, 5] 5
+2 [1, 2, 3, 4, 5] 5
+3 \N \N
+4 [] 0
+5 [42] 1
+
+-- !const_array --
+100 100
+
+-- !const_array_fold --
+100
+
+-- !no_seed_blocks --
+100000
+
diff --git
a/regression-test/data/query_p0/sql_functions/size_funciton/test_size_function.out
b/regression-test/data/query_p0/sql_functions/size_funciton/test_size_function.out
index cc453fbea27..285028cec10 100644
---
a/regression-test/data/query_p0/sql_functions/size_funciton/test_size_function.out
+++
b/regression-test/data/query_p0/sql_functions/size_funciton/test_size_function.out
@@ -1,21 +1,21 @@
-- This file is automatically generated. You should know what you did if you
want to edit this
-- !sql --
-4 ["aaa", "bbb", null, "fff"] ["aaa", "bbb", null, "fff"]
+4 [null, "aaa", "bbb", "fff"] [null, "aaa", "bbb", "fff"]
-- !sql --
-4 ["2020-01-02", "2021-01-01", "2022-01-03", "1996-04-17"]
["2020-01-02", "2021-01-01", "2022-01-03", "1996-04-17"]
+4 ["2022-01-03", "2020-01-02", "2021-01-01", "1996-04-17"]
["2022-01-03", "2020-01-02", "2021-01-01", "1996-04-17"]
-- !sql --
-4 ["aaa", "bbb", null, "fff"] ["aaa", "bbb", null, "fff"]
+4 [null, "aaa", "bbb", "fff"] [null, "aaa", "bbb", "fff"]
-- !sql --
-4 ["2020-01-02", "2021-01-01", "2022-01-03", "1996-04-17"]
["2020-01-02", "2021-01-01", "2022-01-03", "1996-04-17"]
+4 ["2022-01-03", "2020-01-02", "2021-01-01", "1996-04-17"]
["2022-01-03", "2020-01-02", "2021-01-01", "1996-04-17"]
-- !sql --
-4 ["aaa", "bbb", null, "fff"] ["aaa", "bbb", null, "fff"]
+4 [null, "aaa", "bbb", "fff"] [null, "aaa", "bbb", "fff"]
-- !sql --
-4 ["2020-01-02", "2021-01-01", "2022-01-03", "1996-04-17"]
["2020-01-02", "2021-01-01", "2022-01-03", "1996-04-17"]
+4 ["2022-01-03", "2020-01-02", "2021-01-01", "1996-04-17"]
["2022-01-03", "2020-01-02", "2021-01-01", "1996-04-17"]
-- !sql --
2
diff --git
a/regression-test/suites/query_p0/sql_functions/array_functions/test_array_shuffle_seed.groovy
b/regression-test/suites/query_p0/sql_functions/array_functions/test_array_shuffle_seed.groovy
new file mode 100644
index 00000000000..e61776a1deb
--- /dev/null
+++
b/regression-test/suites/query_p0/sql_functions/array_functions/test_array_shuffle_seed.groovy
@@ -0,0 +1,101 @@
+// 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_array_shuffle_seed") {
+ sql "DROP TABLE IF EXISTS test_array_shuffle_seed"
+ sql """
+ CREATE TABLE test_array_shuffle_seed (
+ k INT,
+ a ARRAY<INT> NULL,
+ s BIGINT NULL
+ ) DUPLICATE KEY(k)
+ DISTRIBUTED BY HASH(k) BUCKETS 1
+ PROPERTIES ("replication_num" = "1")
+ """
+ sql """
+ INSERT INTO test_array_shuffle_seed VALUES
+ (1, [1, 2, 3, 4, 5], 1),
+ (2, [1, 2, 3, 4, 5], 2),
+ (3, NULL, 1),
+ (4, [], 1),
+ (5, [42], 1)
+ """
+
+ // The rows of a block draw from one random sequence that starts from the
seed, so a per-row
+ // seed would be ignored. A non-constant seed is rejected instead.
+ test {
+ sql "SELECT k, array_shuffle(a, s) FROM test_array_shuffle_seed"
+ exception "must be a constant"
+ }
+ test {
+ sql "SELECT k, shuffle(a, k) FROM test_array_shuffle_seed"
+ exception "must be a constant"
+ }
+ test {
+ sql """
+ WITH t AS (SELECT [1, 2, 3, 4, 5] a, 1 seed UNION ALL SELECT [1,
2, 3, 4, 5], 2)
+ SELECT seed, array_shuffle(a, seed) FROM t ORDER BY seed
+ """
+ exception "must be a constant"
+ }
+ test {
+ sql "SELECT array_shuffle([1, 2, 3, 4, 5], cast(random() * 10 as
bigint))"
+ exception "must be a constant"
+ }
+
+ // A constant seed gives a fixed result. A constant expression works as a
seed too.
+ order_qt_const_seed """
+ SELECT array_shuffle([1, 2, 3, 4, 5], 1), array_shuffle([1, 2, 3, 4,
5], 2),
+ shuffle([1, 2, 3, 4, 5], 1), array_shuffle([1, 2, 3, 4, 5], 1 +
1)
+ """
+ // Any BIGINT is a valid seed, a negative one too. All 64 bits are used,
so -1 and 4294967295
+ // (same low 32 bits) give different results, and so do
-9223372036854775808 and 0.
+ order_qt_bigint_seed """
+ SELECT array_shuffle(array_range(10), -1),
array_shuffle(array_range(10), 4294967295),
+ array_shuffle(array_range(10), -9223372036854775808),
+ array_shuffle(array_range(10), 0),
+ array_shuffle(array_range(10), 9223372036854775807)
+ """
+ // A NULL seed gives NULL.
+ order_qt_null_seed "SELECT array_shuffle([1, 2, 3, 4, 5], NULL)"
+ // Shuffling keeps the elements, so sorting them back gives a stable
result.
+ order_qt_table """
+ SELECT k, array_sort(array_shuffle(a, 1)), array_size(shuffle(a, -1))
+ FROM test_array_shuffle_seed
+ """
+
+ // A constant array is still shuffled on each row, so the rows do not all
get the same order.
+ // Each block starts again from the seed, so keep all 100 rows in one
block.
+ order_qt_const_array """
+ SELECT /*+ SET_VAR(batch_size = 4064) */
+ count(DISTINCT cast(array_shuffle(array_range(20), 1) AS
string)),
+ count(DISTINCT cast(array_shuffle(array_range(20)) AS string))
+ FROM numbers("number" = "100")
+ """
+ // array_shuffle is not folded into one constant, even when BE folds the
constants.
+ order_qt_const_array_fold """
+ SELECT /*+ SET_VAR(enable_fold_constant_by_be = true) */
+ count(DISTINCT cast(array_shuffle(array_range(20)) AS string))
+ FROM numbers("number" = "100")
+ """
+ // Without a seed, each block gets its own random seed, so the blocks do
not repeat the
+ // same orders.
+ order_qt_no_seed_blocks """
+ SELECT count(DISTINCT cast(array_shuffle(array_range(20 + number * 0))
AS string))
+ FROM numbers("number" = "100000")
+ """
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]