This is an automated email from the ASF dual-hosted git repository.

mrhhsg 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 e429e59bdfc [fix](function) Keep array_sort from crashing on an 
inconsistent lambda comparator (#67628)
e429e59bdfc is described below

commit e429e59bdfcf1e555824867a0ed6190828b5ff3b
Author: Jerry Hu <[email protected]>
AuthorDate: Mon Sep 28 15:12:41 2026 +0800

    [fix](function) Keep array_sort from crashing on an inconsistent lambda 
comparator (#67628)
    
    ### What problem does this PR solve?
    
    Issue Number: None
    
    Problem Summary:
    
    `array_sort` hands the user's lambda comparator straight to `std::sort`.
    libstdc++'s introsort relies on the comparator being a deterministic
    strict
    weak ordering: its unguarded partition and unguarded insertion loops
    walk
    past the range as soon as that contract is broken. A comparator such as
    
    ```sql
    SELECT array_sort(
        (x, y) -> IF(x > 100 AND y > 100, -1, IF(x < y, -1, IF(x = y, 0, 1))),
        [1, ..., 10, 101, ..., 160]);
    ```
    
    therefore crashes BE with SIGSEGV in `ArraySortFunction::execute` /
    `std::__introsort_loop`, and the same happens for a non-deterministic
    comparator like `(x, y) -> IF(random() < 0.5, -1, 1)`. pdqsort has the
    same
    unguarded loops, so switching to it would not help.
    
    A pre-check cannot fix this either: detecting every violation before
    sorting
    costs O(n^2) to O(n^3) lambda evaluations, and any sampled check lets
    some
    comparator through to the unguarded sort.
    
    Every standard library sort (`std::sort`, `std::stable_sort`,
    `std::make_heap` / `std::sort_heap`, ...) requires a strict weak
    ordering, so
    none of them can carry a no-crash guarantee for user SQL: libstdc++
    debug
    mode and libc++ hardening abort on such comparators, and other
    implementations are free to rely on the violated contract.
    
    This PR therefore sorts the permutation with a Doris-owned routine,
    `sort_with_untrusted_comparator`
    (`be/src/util/untrusted_comparator_sort.h`).
    It is a bottom-up merge sort whose loop bounds and accesses depend only
    on the
    range length; the comparator only chooses which of two in-range elements
    is
    copied next. For any comparator it terminates within `n * ceil(log2 n)`
    comparator calls, never reads outside the range and yields a permutation
    of
    the input. For a consistent comparator it is a stable sort, uses fewer
    lambda
    evaluations than introsort in the worst case, and an already sorted
    array
    costs O(n) evaluations. An inconsistent comparator now yields an
    unspecified
    order instead of a crash, which is the same result such a comparator
    already
    produced whenever `std::sort` happened not to crash.
    
    ### Release note
    
    None
    
    ### Check List (For Author)
    
    - Test:
        - Unit Test: `UntrustedComparatorSortTest` checks sortedness and
    stability for consistent comparators, and for always-less, never-less,
    partially reflexive and random comparators checks that every comparator
    argument stays in range, the call count stays within the bound and the
    output is a permutation of the input; it also checks the linear cost
          of sorted input and that a comparator exception propagates.
    - Regression test: `test_array_sort_lambda_comparator` runs the reported
    comparator on literal and table input, an always-less comparator and a
    random comparator (BE must stay alive and return an array of the same
    size), and checks large / nullable arrays with consistent comparators.
    - Behavior changed: No. Queries that used to crash BE now return a
    result;
      the relative order of elements the comparator reports as equal is
      unspecified, as before.
    - Does this need documentation: No
    
    https://claude.ai/code/session_016A7UJu7EA7j4NkGz3yjkt6
    https://claude.ai/code/session_01JBR4GzAqsy54QvL79AdN1x
---
 .../exprs/lambda_function/varray_sort_function.cpp |  66 ++++---
 be/src/util/untrusted_comparator_sort.h            |  86 +++++++++
 be/test/util/untrusted_comparator_sort_test.cpp    | 196 +++++++++++++++++++++
 .../test_array_sort_lambda_comparator.out          |  28 +++
 .../test_array_sort_lambda_comparator.groovy       |  88 +++++++++
 5 files changed, 437 insertions(+), 27 deletions(-)

diff --git a/be/src/exprs/lambda_function/varray_sort_function.cpp 
b/be/src/exprs/lambda_function/varray_sort_function.cpp
index 82fd2fb09ff..efee9bad2e0 100644
--- a/be/src/exprs/lambda_function/varray_sort_function.cpp
+++ b/be/src/exprs/lambda_function/varray_sort_function.cpp
@@ -32,6 +32,7 @@
 #include "core/column/column_nullable.h"
 #include "core/column/column_varbinary.h"
 #include "core/column/column_vector.h"
+#include "core/custom_allocator.h"
 #include "core/data_type/data_type.h"
 #include "exec/common/util.hpp"
 #include "exprs/lambda_function/lambda_execution_context.h"
@@ -41,6 +42,7 @@
 #include "exprs/vexpr.h"
 #include "exprs/vexpr_context.h"
 #include "exprs/vlambda_function_expr.h"
+#include "util/untrusted_comparator_sort.h"
 
 namespace doris {
 
@@ -85,9 +87,10 @@ public:
         return Status::OK();
     }
 
-    Status execute(VExprContext* context, const Block* block, const Selector* 
expr_selector,
-                   size_t count, ColumnPtr& result_column, const DataTypePtr& 
result_type,
-                   const VExprSPtrs& children) const override {
+    Status execute( // NOLINT(readability-function-size)
+            VExprContext* context, const Block* block, const Selector* 
expr_selector, size_t count,
+            ColumnPtr& result_column, const DataTypePtr& result_type,
+            const VExprSPtrs& children) const override {
         ///* array_sort(lambda, arg) *///
 
         DCHECK_EQ(children.size(), 2);
@@ -202,33 +205,42 @@ public:
                     };
 
                     const int lambda_result_base = 
static_cast<int>(lambda_block.columns());
-                    for (int row = 0; row < input_rows; ++row) {
-                        auto start = off_data[row - 1];
-                        auto end = off_data[row];
-                        std::sort(&permutation[start], &permutation[end], 
[&](size_t i, size_t j) {
-                            prepare_lambda_input(i, 0);
-                            prepare_lambda_input(j, 1);
-                            int lambda_res_id = lambda_result_base;
-                            auto status =
-                                    children[0]->execute(context, 
&lambda_block, &lambda_res_id);
-                            if (!status.ok()) [[unlikely]] {
-                                throw Exception(Status::InternalError(
-                                        "when execute array_sort lambda 
function: {}",
-                                        status.to_string()));
-                            }
+                    // Returns true when element i sorts before element j 
according to the
+                    // user's lambda.
+                    auto less = [&](size_t i, size_t j) {
+                        prepare_lambda_input(i, 0);
+                        prepare_lambda_input(j, 1);
+                        int lambda_res_id = lambda_result_base;
+                        auto status = children[0]->execute(context, 
&lambda_block, &lambda_res_id);
+                        if (!status.ok()) [[unlikely]] {
+                            throw Exception(Status::InternalError(
+                                    "when execute array_sort lambda function: 
{}",
+                                    status.to_string()));
+                        }
 
-                            // raw_res_col maybe columnVector or ColumnConst
-                            ColumnPtr raw_res_col =
-                                    
lambda_block.get_by_position(lambda_res_id).column;
-                            ColumnPtr full_res_col = 
raw_res_col->convert_to_full_column_if_const();
+                        // raw_res_col maybe columnVector or ColumnConst
+                        ColumnPtr raw_res_col = 
lambda_block.get_by_position(lambda_res_id).column;
+                        ColumnPtr full_res_col = 
raw_res_col->convert_to_full_column_if_const();
 
-                            // only -1, 0, 1
-                            long cmp = assert_cast<const 
ColumnInt8*>(full_res_col.get())
-                                               ->get_data()[0];
-                            lambda_block.erase_tail(lambda_result_base);
+                        // only -1, 0, 1
+                        long cmp =
+                                assert_cast<const 
ColumnInt8*>(full_res_col.get())->get_data()[0];
+                        lambda_block.erase_tail(lambda_result_base);
 
-                            return cmp < 0;
-                        });
+                        return cmp < 0;
+                    };
+
+                    // The comparator is user SQL and may violate strict weak 
ordering, or
+                    // even be non-deterministic. Standard library sorts rely 
on the comparator
+                    // contract to keep their accesses in range, so a broken 
comparator crashes
+                    // BE. sort_with_untrusted_comparator bounds every access 
by the range
+                    // length; an inconsistent comparator yields an 
unspecified order instead.
+                    DorisVector<size_t> scratch;
+                    for (int row = 0; row < input_rows; ++row) {
+                        auto start = off_data[row - 1];
+                        auto end = off_data[row];
+                        sort_with_untrusted_comparator(permutation.data() + 
start,
+                                                       permutation.data() + 
end, scratch, less);
                     }
                 },
                 src_data);
diff --git a/be/src/util/untrusted_comparator_sort.h 
b/be/src/util/untrusted_comparator_sort.h
new file mode 100644
index 00000000000..1bd39d31f6e
--- /dev/null
+++ b/be/src/util/untrusted_comparator_sort.h
@@ -0,0 +1,86 @@
+// 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.
+
+#pragma once
+
+#include <algorithm>
+#include <cstddef>
+#include <utility>
+#include <vector>
+
+#include "core/custom_allocator.h"
+
+namespace doris {
+
+// Sorts [first, last) with a comparator that cannot be trusted to be a strict 
weak ordering,
+// for example one evaluated from user SQL. Such a comparator may report 
`less(a, a)`, may
+// report both `less(a, b)` and `less(b, a)`, and may even answer differently 
when asked the
+// same question twice.
+//
+// Standard library sorts (std::sort, std::make_heap/std::sort_heap, ...) 
require a strict weak
+// ordering and are free to read outside the range, loop forever or abort when 
it is violated.
+// This routine is a bottom-up merge sort in which every loop bound and every 
access is derived
+// from the range length alone; the comparator only decides which of two 
in-range elements is
+// copied next. Therefore, for ANY comparator:
+//   - it makes at most n * ceil(log2(n)) comparator calls,
+//   - it never accesses memory outside [first, last) and `scratch`,
+//   - the result is a permutation of the input.
+// When the comparator is a strict weak ordering the result is sorted, and the 
sort is stable.
+//
+// `scratch` is caller-owned so that repeated calls can reuse its allocation. 
If the comparator
+// throws, the exception propagates and the contents of [first, last) are 
unspecified.
+template <typename T, typename Less>
+void sort_with_untrusted_comparator(T* first, T* last, DorisVector<T>& 
scratch, Less&& less) {
+    const size_t n = last - first;
+    scratch.resize(n);
+
+    T* src = first;
+    T* dst = scratch.data();
+    for (size_t width = 1; width < n; width *= 2) {
+        for (size_t lo = 0; lo < n; lo += 2 * width) {
+            const size_t mid = std::min(lo + width, n);
+            const size_t hi = std::min(lo + 2 * width, n);
+            // Runs that are already in order are copied without merging. This 
is what makes an
+            // already sorted input cost O(n) comparator calls instead of O(n 
log n).
+            if (mid == hi || !less(src[mid], src[mid - 1])) {
+                std::copy(src + lo, src + hi, dst + lo);
+                continue;
+            }
+            size_t i = lo;
+            size_t j = mid;
+            size_t k = lo;
+            while (i < mid && j < hi) {
+                // The right run wins only when strictly less, so equal 
elements keep their
+                // relative order.
+                if (less(src[j], src[i])) {
+                    dst[k++] = src[j++];
+                } else {
+                    dst[k++] = src[i++];
+                }
+            }
+            std::copy(src + i, src + mid, dst + k);
+            k += mid - i;
+            std::copy(src + j, src + hi, dst + k);
+        }
+        std::swap(src, dst);
+    }
+    if (src != first) {
+        std::copy(src, src + n, first);
+    }
+}
+
+} // namespace doris
diff --git a/be/test/util/untrusted_comparator_sort_test.cpp 
b/be/test/util/untrusted_comparator_sort_test.cpp
new file mode 100644
index 00000000000..4b77ad9d798
--- /dev/null
+++ b/be/test/util/untrusted_comparator_sort_test.cpp
@@ -0,0 +1,196 @@
+// 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 "util/untrusted_comparator_sort.h"
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <cmath>
+#include <cstddef>
+#include <random>
+#include <stdexcept>
+#include <vector>
+
+#include "core/custom_allocator.h"
+
+namespace doris {
+
+namespace {
+
+// Upper bound on comparator calls promised by sort_with_untrusted_comparator.
+size_t max_comparator_calls(size_t n) {
+    return n < 2 ? 0 : n * 
static_cast<size_t>(std::ceil(std::log2(static_cast<double>(n))));
+}
+
+std::vector<size_t> identity_permutation(size_t n) {
+    std::vector<size_t> data(n);
+    for (size_t i = 0; i < n; ++i) {
+        data[i] = i;
+    }
+    return data;
+}
+
+bool is_permutation_of_identity(const std::vector<size_t>& data) {
+    std::vector<bool> seen(data.size(), false);
+    for (size_t v : data) {
+        if (v >= data.size() || seen[v]) {
+            return false;
+        }
+        seen[v] = true;
+    }
+    return true;
+}
+
+// Runs the sort on 0..n-1 (shuffled by `shuffle`) with an arbitrary 
comparator and checks the
+// guarantees that hold for any comparator: bounded number of calls, every 
argument in range,
+// and the output being a permutation of the input.
+template <typename Less>
+std::vector<size_t> sort_and_check_invariants(size_t n, bool shuffle, Less 
less) {
+    auto data = identity_permutation(n);
+    if (shuffle) {
+        std::mt19937 rng(n);
+        std::shuffle(data.begin(), data.end(), rng);
+    }
+
+    size_t calls = 0;
+    bool argument_out_of_range = false;
+    auto checked_less = [&](size_t a, size_t b) {
+        ++calls;
+        argument_out_of_range |= a >= n || b >= n;
+        return less(a, b);
+    };
+
+    DorisVector<size_t> scratch;
+    sort_with_untrusted_comparator(data.data(), data.data() + data.size(), 
scratch, checked_less);
+
+    EXPECT_FALSE(argument_out_of_range) << "n=" << n;
+    EXPECT_LE(calls, max_comparator_calls(n)) << "n=" << n;
+    EXPECT_TRUE(is_permutation_of_identity(data)) << "n=" << n;
+    return data;
+}
+
+const std::vector<size_t> kSizes = {0, 1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 31, 
33, 100, 1000, 1025};
+
+} // namespace
+
+TEST(UntrustedComparatorSortTest, ConsistentComparatorSorts) {
+    for (size_t n : kSizes) {
+        auto data = sort_and_check_invariants(n, true, [](size_t a, size_t b) 
{ return a < b; });
+        EXPECT_EQ(data, identity_permutation(n)) << "n=" << n;
+
+        data = sort_and_check_invariants(n, true, [](size_t a, size_t b) { 
return a > b; });
+        auto expected = identity_permutation(n);
+        std::reverse(expected.begin(), expected.end());
+        EXPECT_EQ(data, expected) << "n=" << n;
+    }
+}
+
+TEST(UntrustedComparatorSortTest, ConsistentComparatorIsStable) {
+    // Sort by key only; equal keys must keep the order in which they appear 
in the input.
+    for (size_t n : kSizes) {
+        auto key = [](size_t v) { return v % 7; };
+        auto data = sort_and_check_invariants(n, true,
+                                              [&](size_t a, size_t b) { return 
key(a) < key(b); });
+
+        std::vector<size_t> input = identity_permutation(n);
+        std::mt19937 rng(n);
+        std::shuffle(input.begin(), input.end(), rng);
+        std::stable_sort(input.begin(), input.end(),
+                         [&](size_t a, size_t b) { return key(a) < key(b); });
+        EXPECT_EQ(data, input) << "n=" << n;
+    }
+}
+
+TEST(UntrustedComparatorSortTest, SortedInputCostsLinearComparisons) {
+    for (size_t n : kSizes) {
+        size_t calls = 0;
+        auto data = identity_permutation(n);
+        DorisVector<size_t> scratch;
+        sort_with_untrusted_comparator(data.data(), data.data() + n, scratch,
+                                       [&](size_t a, size_t b) {
+                                           ++calls;
+                                           return a < b;
+                                       });
+        EXPECT_EQ(data, identity_permutation(n)) << "n=" << n;
+        EXPECT_LE(calls, n) << "n=" << n;
+    }
+}
+
+TEST(UntrustedComparatorSortTest, AlwaysLessComparator) {
+    for (size_t n : kSizes) {
+        sort_and_check_invariants(n, true, [](size_t, size_t) { return true; 
});
+    }
+}
+
+TEST(UntrustedComparatorSortTest, NeverLessComparatorKeepsInputOrder) {
+    for (size_t n : kSizes) {
+        auto data = sort_and_check_invariants(n, false, [](size_t, size_t) { 
return false; });
+        EXPECT_EQ(data, identity_permutation(n)) << "n=" << n;
+    }
+}
+
+TEST(UntrustedComparatorSortTest, PartiallyReflexiveComparator) {
+    // Every pair of values >= 50 compares as "less" in both directions, which 
is the shape of
+    // the SQL comparator `(x, y) -> IF(x > 100 AND y > 100, -1, ...)`.
+    for (size_t n : kSizes) {
+        sort_and_check_invariants(n, true, [](size_t a, size_t b) {
+            if (a >= 50 && b >= 50) {
+                return true;
+            }
+            return a < b;
+        });
+    }
+}
+
+TEST(UntrustedComparatorSortTest, RandomComparator) {
+    std::mt19937 rng(42);
+    for (size_t n : kSizes) {
+        sort_and_check_invariants(n, true, [&](size_t, size_t) { return (rng() 
& 1) == 1; });
+    }
+}
+
+TEST(UntrustedComparatorSortTest, ScratchIsReusedAcrossCalls) {
+    DorisVector<size_t> scratch;
+    for (size_t n : std::vector<size_t> {1000, 3, 0, 17, 1025}) {
+        auto data = identity_permutation(n);
+        std::mt19937 rng(n);
+        std::shuffle(data.begin(), data.end(), rng);
+        sort_with_untrusted_comparator(data.data(), data.data() + n, scratch,
+                                       [](size_t a, size_t b) { return a < b; 
});
+        EXPECT_EQ(data, identity_permutation(n)) << "n=" << n;
+        EXPECT_GE(scratch.capacity(), n);
+    }
+}
+
+TEST(UntrustedComparatorSortTest, ComparatorExceptionPropagates) {
+    auto data = identity_permutation(100);
+    std::reverse(data.begin(), data.end());
+    DorisVector<size_t> scratch;
+    size_t calls = 0;
+    EXPECT_THROW(sort_with_untrusted_comparator(data.data(), data.data() + 
data.size(), scratch,
+                                                [&](size_t a, size_t b) {
+                                                    if (++calls == 50) {
+                                                        throw 
std::runtime_error("lambda failed");
+                                                    }
+                                                    return a < b;
+                                                }),
+                 std::runtime_error);
+    EXPECT_EQ(calls, size_t {50});
+}
+
+} // namespace doris
diff --git 
a/regression-test/data/query_p0/sql_functions/array_functions/test_array_sort_lambda_comparator.out
 
b/regression-test/data/query_p0/sql_functions/array_functions/test_array_sort_lambda_comparator.out
new file mode 100644
index 00000000000..b8aedec930e
--- /dev/null
+++ 
b/regression-test/data/query_p0/sql_functions/array_functions/test_array_sort_lambda_comparator.out
@@ -0,0 +1,28 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !inconsistent_comparator_literal --
+70
+
+-- !always_less_comparator --
+199
+
+-- !random_comparator --
+199
+
+-- !large_desc --
+[99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, 87, 86, 85, 84, 83, 82, 81, 
80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 
60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 
20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
+
+-- !large_with_null --
+[null, null, null, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 
18, 19, 20]
+
+-- !inconsistent_comparator_table --
+1      70
+2      3
+3      0
+4      \N
+
+-- !consistent_comparator_table --
+1      [160, 159, 158, 157, 156, 155, 154, 153, 152, 151, 150, 149, 148, 147, 
146, 145, 144, 143, 142, 141, 140, 139, 138, 137, 136, 135, 134, 133, 132, 131, 
130, 129, 128, 127, 126, 125, 124, 123, 122, 121, 120, 119, 118, 117, 116, 115, 
114, 113, 112, 111, 110, 109, 108, 107, 106, 105, 104, 103, 102, 101, 10, 9, 8, 
7, 6, 5, 4, 3, 2, 1]
+2      [3, 2, 1]
+3      []
+4      \N
+
diff --git 
a/regression-test/suites/query_p0/sql_functions/array_functions/test_array_sort_lambda_comparator.groovy
 
b/regression-test/suites/query_p0/sql_functions/array_functions/test_array_sort_lambda_comparator.groovy
new file mode 100644
index 00000000000..d029db7208a
--- /dev/null
+++ 
b/regression-test/suites/query_p0/sql_functions/array_functions/test_array_sort_lambda_comparator.groovy
@@ -0,0 +1,88 @@
+// 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_sort_lambda_comparator") {
+    // A comparator that is not a strict weak ordering must not crash BE. 
Every pair of values
+    // above 100 compares as "less" in both directions, and there are far more 
than the
+    // insertion-sort threshold of such values. Only the cardinality is 
asserted because the
+    // resulting order is unspecified for such a comparator.
+    order_qt_inconsistent_comparator_literal """
+        SELECT cardinality(array_sort(
+            (x, y) -> IF(x > 100 AND y > 100, -1, IF(x < y, -1, IF(x = y, 0, 
1))),
+            [1,2,3,4,5,6,7,8,9,10,101,102,103,104,105,106,107,108,109,110,
+             
111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,
+             
131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,
+             151,152,153,154,155,156,157,158,159,160]))
+    """
+
+    // A comparator that says "less" for every pair.
+    order_qt_always_less_comparator """
+        SELECT cardinality(array_sort((x, y) -> -1, array_range(1, 200)))
+    """
+
+    // A non-deterministic comparator changes its answer between calls on the 
same pair.
+    order_qt_random_comparator """
+        SELECT cardinality(array_sort((x, y) -> IF(random() < 0.5, -1, 1), 
array_range(1, 200)))
+    """
+
+    // Consistent comparators on arrays larger than the insertion-sort 
threshold still sort.
+    order_qt_large_desc """
+        SELECT array_sort((x, y) -> IF(x < y, 1, IF(x = y, 0, -1)), 
array_range(1, 100))
+    """
+    // NULLs sort first and compare equal to each other, so the comparator 
stays a strict weak
+    // ordering with several NULL elements.
+    order_qt_large_with_null """
+        SELECT array_sort((x, y) -> CASE WHEN x IS NULL AND y IS NULL THEN 0
+                                         WHEN x IS NULL THEN -1
+                                         WHEN y IS NULL THEN 1
+                                         WHEN x < y THEN -1
+                                         WHEN x = y THEN 0
+                                         ELSE 1 END,
+                          [20, null, 19, 18, 17, null, 16, 15, 14, 13, 12, 11, 
10, 9, 8, 7, 6, 5, 4, 3, 2, 1, null])
+    """
+
+    // Same inconsistent comparator over a table column (non-constant input 
path).
+    sql "DROP TABLE IF EXISTS test_array_sort_lambda_comparator_tbl"
+    sql """
+        CREATE TABLE test_array_sort_lambda_comparator_tbl (
+            id INT,
+            arr ARRAY<INT>
+        ) ENGINE=OLAP
+        DUPLICATE KEY(id)
+        DISTRIBUTED BY HASH(id) BUCKETS 1
+        PROPERTIES ("replication_num" = "1")
+    """
+    sql """
+        INSERT INTO test_array_sort_lambda_comparator_tbl VALUES
+            (1, [1,2,3,4,5,6,7,8,9,10,101,102,103,104,105,106,107,108,109,110,
+                 
111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,
+                 
131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,
+                 151,152,153,154,155,156,157,158,159,160]),
+            (2, [3, 1, 2]),
+            (3, []),
+            (4, NULL)
+    """
+    order_qt_inconsistent_comparator_table """
+        SELECT id, cardinality(array_sort(
+            (x, y) -> IF(x > 100 AND y > 100, -1, IF(x < y, -1, IF(x = y, 0, 
1))), arr))
+        FROM test_array_sort_lambda_comparator_tbl
+    """
+    order_qt_consistent_comparator_table """
+        SELECT id, array_sort((x, y) -> IF(x < y, 1, IF(x = y, 0, -1)), arr)
+        FROM test_array_sort_lambda_comparator_tbl
+    """
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to