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]

Reply via email to