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]