diff --git a/velox/exec/SortWindowBuild.cpp b/velox/exec/SortWindowBuild.cpp index bf1fb0405bd2..5107758ccca9 100644 --- a/velox/exec/SortWindowBuild.cpp +++ b/velox/exec/SortWindowBuild.cpp @@ -20,7 +20,7 @@ namespace facebook::velox::exec { namespace { -std::vector makeSpillCompareFlags( +std::vector makeCompareFlags( int32_t numPartitionKeys, const std::vector& sortingOrders) { std::vector compareFlags; @@ -42,14 +42,15 @@ std::vector makeSpillCompareFlags( SortWindowBuild::SortWindowBuild( const std::shared_ptr& node, velox::memory::MemoryPool* pool, + common::PrefixSortConfig&& prefixSortConfig, const common::SpillConfig* spillConfig, tsan_atomic* nonReclaimableSection, folly::Synchronized* spillStats) : WindowBuild(node, pool, spillConfig, nonReclaimableSection), numPartitionKeys_{node->partitionKeys().size()}, - spillCompareFlags_{ - makeSpillCompareFlags(numPartitionKeys_, node->sortingOrders())}, + compareFlags_{makeCompareFlags(numPartitionKeys_, node->sortingOrders())}, pool_(pool), + prefixSortConfig_(prefixSortConfig), spillStats_(spillStats) { VELOX_CHECK_NOT_NULL(pool_); allKeyInfo_.reserve(partitionKeyInfo_.size() + sortKeyInfo_.size()); @@ -145,8 +146,8 @@ void SortWindowBuild::setupSpiller() { Spiller::Type::kOrderByInput, data_.get(), inputType_, - spillCompareFlags_.size(), - spillCompareFlags_, + compareFlags_.size(), + compareFlags_, spillConfig_, spillStats_); } @@ -217,12 +218,8 @@ void SortWindowBuild::sortPartitions() { RowContainerIterator iter; data_->listRows(&iter, numRows_, sortedRows_.data()); - std::sort( - sortedRows_.begin(), - sortedRows_.end(), - [this](const char* leftRow, const char* rightRow) { - return compareRowsWithKeys(leftRow, rightRow, allKeyInfo_); - }); + PrefixSort::sort( + sortedRows_, pool_, data_.get(), compareFlags_, prefixSortConfig_); computePartitionStartRows(); } diff --git a/velox/exec/SortWindowBuild.h b/velox/exec/SortWindowBuild.h index 0caecfe6a5c3..7f995fc35ac3 100644 --- a/velox/exec/SortWindowBuild.h +++ b/velox/exec/SortWindowBuild.h @@ -16,6 +16,7 @@ #pragma once +#include "velox/exec/PrefixSort.h" #include "velox/exec/Spiller.h" #include "velox/exec/WindowBuild.h" @@ -29,6 +30,7 @@ class SortWindowBuild : public WindowBuild { SortWindowBuild( const std::shared_ptr& node, velox::memory::MemoryPool* pool, + common::PrefixSortConfig&& prefixSortConfig, const common::SpillConfig* spillConfig, tsan_atomic* nonReclaimableSection, folly::Synchronized* spillStats); @@ -82,10 +84,14 @@ class SortWindowBuild : public WindowBuild { // keys are set to default values. Compare flags for sorting keys match // sorting order specified in the plan node. // - // Used to sort 'data_' while spilling. - const std::vector spillCompareFlags_; + // Used to sort 'data_' while spilling and in Prefix sort. + const std::vector compareFlags_; memory::MemoryPool* const pool_; + + // Config for Prefix-sort. + const common::PrefixSortConfig prefixSortConfig_; + folly::Synchronized* const spillStats_; // allKeyInfo_ is a combination of (partitionKeyInfo_ and sortKeyInfo_). diff --git a/velox/exec/Window.cpp b/velox/exec/Window.cpp index ec1fc1fb1aef..a414f0e20f78 100644 --- a/velox/exec/Window.cpp +++ b/velox/exec/Window.cpp @@ -56,7 +56,14 @@ Window::Window( } } else { windowBuild_ = std::make_unique( - windowNode, pool(), spillConfig, &nonReclaimableSection_, &spillStats_); + windowNode, + pool(), + common::PrefixSortConfig{ + driverCtx->queryConfig().prefixSortNormalizedKeyMaxBytes(), + driverCtx->queryConfig().prefixSortMinRows()}, + spillConfig, + &nonReclaimableSection_, + &spillStats_); } } diff --git a/velox/exec/benchmarks/CMakeLists.txt b/velox/exec/benchmarks/CMakeLists.txt index bf4e517f459e..b97bb0547789 100644 --- a/velox/exec/benchmarks/CMakeLists.txt +++ b/velox/exec/benchmarks/CMakeLists.txt @@ -95,3 +95,16 @@ target_link_libraries( velox_vector_fuzzer velox_vector_test_lib ${FOLLY_BENCHMARK}) + +add_executable(velox_window_prefixsort_benchmark WindowPrefixSortBenchmark.cpp) + +target_link_libraries( + velox_window_prefixsort_benchmark + velox_aggregates + velox_exec + velox_exec_test_lib + velox_hive_connector + velox_vector_fuzzer + velox_vector_test_lib + velox_window + ${FOLLY_BENCHMARK}) diff --git a/velox/exec/benchmarks/WindowPrefixSortBenchmark.cpp b/velox/exec/benchmarks/WindowPrefixSortBenchmark.cpp new file mode 100644 index 000000000000..e762a7f821c2 --- /dev/null +++ b/velox/exec/benchmarks/WindowPrefixSortBenchmark.cpp @@ -0,0 +1,386 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * Licensed 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 +#include +#include +#include + +#include "velox/exec/tests/utils/Cursor.h" +#include "velox/exec/tests/utils/HiveConnectorTestBase.h" +#include "velox/exec/tests/utils/PlanBuilder.h" +#include "velox/functions/prestosql/aggregates/RegisterAggregateFunctions.h" +#include "velox/functions/prestosql/window/WindowFunctionsRegistration.h" +#include "velox/vector/fuzzer/VectorFuzzer.h" + +DEFINE_int64(fuzzer_seed, 99887766, "Seed for random input dataset generator"); + +using namespace facebook::velox; +using namespace facebook::velox::test; +using namespace facebook::velox::exec; +using namespace facebook::velox::exec::test; + +static constexpr int32_t kNumVectors = 50; +static constexpr int32_t kRowsPerVector = 1'0000; + +namespace { + +class WindowPrefixSortBenchmark : public HiveConnectorTestBase { + public: + explicit WindowPrefixSortBenchmark() { + HiveConnectorTestBase::SetUp(); + aggregate::prestosql::registerAllAggregateFunctions(); + window::prestosql::registerAllWindowFunctions(); + + inputType_ = ROW({ + {"k_array", INTEGER()}, + {"k_norm", INTEGER()}, + {"k_hash", INTEGER()}, + {"k_sort", INTEGER()}, + {"i32", INTEGER()}, + {"i64", BIGINT()}, + {"f32", REAL()}, + {"f64", DOUBLE()}, + {"i32_halfnull", INTEGER()}, + {"i64_halfnull", BIGINT()}, + {"f32_halfnull", REAL()}, + {"f64_halfnull", DOUBLE()}, + }); + + VectorFuzzer::Options opts; + opts.vectorSize = kRowsPerVector; + opts.nullRatio = 0; + VectorFuzzer fuzzer(opts, pool_.get(), FLAGS_fuzzer_seed); + std::vector inputVectors; + for (auto i = 0; i < kNumVectors; ++i) { + std::vector children; + + // Generate key with a small number of unique values from a small range + // (0-16). + children.emplace_back(makeFlatVector( + kRowsPerVector, [](auto row) { return row % 17; })); + + // Generate key with a small number of unique values from a large range + // (300 total values). + children.emplace_back( + makeFlatVector(kRowsPerVector, [](auto row) { + if (row % 3 == 0) { + return std::numeric_limits::max() - row % 100; + } else if (row % 3 == 1) { + return row % 100; + } else { + return std::numeric_limits::min() + row % 100; + } + })); + + // Generate key with many unique values from a large range (500K total + // values). + children.emplace_back(fuzzer.fuzzFlat(INTEGER())); + + // Generate a column with increasing values to get a deterministic sort + // order. + children.emplace_back(makeFlatVector( + kRowsPerVector, [](auto row) { return row; })); + + // Generate random values without nulls. + children.emplace_back(fuzzer.fuzzFlat(INTEGER())); + children.emplace_back(fuzzer.fuzzFlat(BIGINT())); + children.emplace_back(fuzzer.fuzzFlat(REAL())); + children.emplace_back(fuzzer.fuzzFlat(DOUBLE())); + + // Generate random values with nulls. + opts.nullRatio = 0.05; // 5% + fuzzer.setOptions(opts); + + children.emplace_back(fuzzer.fuzzFlat(INTEGER())); + children.emplace_back(fuzzer.fuzzFlat(BIGINT())); + children.emplace_back(fuzzer.fuzzFlat(REAL())); + children.emplace_back(fuzzer.fuzzFlat(DOUBLE())); + + inputVectors.emplace_back(makeRowVector(inputType_->names(), children)); + } + + sourceFilePath_ = TempFilePath::create(); + writeToFile(sourceFilePath_->getPath(), inputVectors); + } + + ~WindowPrefixSortBenchmark() override { + HiveConnectorTestBase::TearDown(); + } + + CpuWallTiming windowNanos() { + return windowNanos_; + } + + void TestBody() override {} + + void run( + const std::string& key, + const std::string& aggregate, + bool prefixSort = true) { + folly::BenchmarkSuspender suspender1; + + if ((prefixSort && !lastRunPrefixSort_) || + (!prefixSort && lastRunPrefixSort_)) { + std::cout << "WindowNanos: " << windowNanos_.toString() << "\n"; + windowNanos_.clear(); + } + + lastRunPrefixSort_ = prefixSort; + std::string functionSql = fmt::format( + "{} over (partition by {} order by k_sort)", aggregate, key); + + core::PlanNodeId tableScanPlanId; + core::PlanFragment plan = PlanBuilder() + .tableScan(inputType_) + .capturePlanNodeId(tableScanPlanId) + .window({functionSql}) + .planFragment(); + + vector_size_t numResultRows = 0; + auto task = makeTask(plan, prefixSort); + task->addSplit( + tableScanPlanId, + exec::Split(makeHiveConnectorSplit(sourceFilePath_->getPath()))); + task->noMoreSplits(tableScanPlanId); + suspender1.dismiss(); + + while (auto result = task->next()) { + numResultRows += result->size(); + } + + folly::BenchmarkSuspender suspender2; + auto stats = task->taskStats(); + for (auto& pipeline : stats.pipelineStats) { + for (auto& op : pipeline.operatorStats) { + if (op.operatorType == "Window") { + windowNanos_.add(op.addInputTiming); + windowNanos_.add(op.getOutputTiming); + } + if (op.operatorType == "Values") { + // This is the timing for Window::noMoreInput() where the window + // sorting happens. So including in the cpu timing. + windowNanos_.add(op.finishTiming); + } + } + } + suspender2.dismiss(); + folly::doNotOptimizeAway(numResultRows); + } + + std::shared_ptr makeTask( + core::PlanFragment plan, + bool prefixSort) { + if (prefixSort) { + return exec::Task::create( + "t", + std::move(plan), + 0, + core::QueryCtx::create(executor_.get()), + Task::ExecutionMode::kSerial); + } else { + const std::unordered_map queryConfigMap( + {{core::QueryConfig::kPrefixSortNormalizedKeyMaxBytes, "0"}}); + return exec::Task::create( + "t", + std::move(plan), + 0, + core::QueryCtx::create( + executor_.get(), core::QueryConfig(queryConfigMap)), + Task::ExecutionMode::kSerial); + } + } + + private: + RowTypePtr inputType_; + std::shared_ptr sourceFilePath_; + + CpuWallTiming windowNanos_; + bool lastRunPrefixSort_; +}; + +std::unique_ptr benchmark; + +void doSortRun(uint32_t, const std::string& key, const std::string& aggregate) { + benchmark->run(key, aggregate, false); +} + +void doPrefixSortRun( + uint32_t, + const std::string& key, + const std::string& aggregate) { + benchmark->run(key, aggregate, true); +} + +#define AGG_BENCHMARKS(_name_, _key_) \ + BENCHMARK_NAMED_PARAM( \ + doSortRun, \ + _name_##_INTEGER_##_key_, \ + #_key_, \ + fmt::format("{}(i32)", (#_name_))); \ + BENCHMARK_NAMED_PARAM( \ + doPrefixSortRun, \ + _name_##_INTEGER_##_key_, \ + #_key_, \ + fmt::format("{}(i32)", (#_name_))); \ + BENCHMARK_NAMED_PARAM( \ + doSortRun, \ + _name_##_REAL_##_key_, \ + #_key_, \ + fmt::format("{}(f32)", (#_name_))); \ + BENCHMARK_NAMED_PARAM( \ + doPrefixSortRun, \ + _name_##_REAL_##_key_, \ + #_key_, \ + fmt::format("{}(f32)", (#_name_))); \ + BENCHMARK_NAMED_PARAM( \ + doSortRun, \ + _name_##_INTEGER_NULLS_##_key_, \ + #_key_, \ + fmt::format("{}(i32_halfnull)", (#_name_))); \ + BENCHMARK_NAMED_PARAM( \ + doPrefixSortRun, \ + _name_##_INTEGER_NULLS_##_key_, \ + #_key_, \ + fmt::format("{}(i32_halfnull)", (#_name_))); \ + BENCHMARK_NAMED_PARAM( \ + doSortRun, \ + _name_##_REAL_NULLS_##_key_, \ + #_key_, \ + fmt::format("{}(f32_halfnull)", (#_name_))); \ + BENCHMARK_NAMED_PARAM( \ + doPrefixSortRun, \ + _name_##_REAL_NULLS_##_key_, \ + #_key_, \ + fmt::format("{}(f32_halfnull)", (#_name_))); \ + BENCHMARK_DRAW_LINE(); \ + BENCHMARK_DRAW_LINE(); + +#define MULTI_KEY_AGG_BENCHMARKS(_name_, _key1_, _key2_) \ + BENCHMARK_NAMED_PARAM( \ + doSortRun, \ + _name_##_BIGINT_##_key1_##_key2_, \ + fmt::format("{},{}", (#_key1_), (#_key2_)), \ + fmt::format("{}(i64)", (#_name_))); \ + BENCHMARK_NAMED_PARAM( \ + doPrefixSortRun, \ + _name_##_BIGINT_##_key1_##_key2_, \ + fmt::format("{},{}", (#_key1_), (#_key2_)), \ + fmt::format("{}(i64)", (#_name_))); \ + BENCHMARK_NAMED_PARAM( \ + doSortRun, \ + _name_##_BIGINT_NULLS_##_key1_##_key2_, \ + fmt::format("{},{}", (#_key1_), (#_key2_)), \ + fmt::format("{}(i64_halfnull)", (#_name_))); \ + BENCHMARK_NAMED_PARAM( \ + doPrefixSortRun, \ + _name_##_BIGINT_NULLS_##_key1_##_key2_, \ + fmt::format("{},{}", (#_key1_), (#_key2_)), \ + fmt::format("{}(i64_halfnull)", (#_name_))); \ + BENCHMARK_NAMED_PARAM( \ + doSortRun, \ + _name_##_DOUBLE_##_key1_##_key2_, \ + fmt::format("{},{}", (#_key1_), (#_key2_)), \ + fmt::format("{}(f64)", (#_name_))); \ + BENCHMARK_NAMED_PARAM( \ + doPrefixSortRun, \ + _name_##_DOUBLE_##_key1_##_key2_, \ + fmt::format("{},{}", (#_key1_), (#_key2_)), \ + fmt::format("{}(f64)", (#_name_))); \ + BENCHMARK_NAMED_PARAM( \ + doSortRun, \ + _name_##_DOUBLE_NULLS_##_key1_##_key2_, \ + fmt::format("{},{}", (#_key1_), (#_key2_)), \ + fmt::format("{}(f64_halfnull)", (#_name_))); \ + BENCHMARK_NAMED_PARAM( \ + doPrefixSortRun, \ + _name_##_DOUBLE_NULLS_##_key1_##_key2_, \ + fmt::format("{},{}", (#_key1_), (#_key2_)), \ + fmt::format("{}(f64_halfnull)", (#_name_))); \ + BENCHMARK_DRAW_LINE(); \ + BENCHMARK_DRAW_LINE(); + +// Count(1) aggregate. +BENCHMARK_NAMED_PARAM(doSortRun, count_k_array, "k_array", "count(1)"); +BENCHMARK_NAMED_PARAM(doPrefixSortRun, count_k_array, "k_array", "count(1)"); +BENCHMARK_NAMED_PARAM(doSortRun, count_k_norm, "k_norm", "count(1)"); +BENCHMARK_NAMED_PARAM(doPrefixSortRun, count_k_norm, "k_norm", "count(1)"); +BENCHMARK_NAMED_PARAM(doSortRun, count_k_hash, "k_hash", "count(1)"); +BENCHMARK_NAMED_PARAM(doPrefixSortRun, count_k_hash, "k_hash", "count(1)"); +BENCHMARK_NAMED_PARAM( + doSortRun, + count_k_array_k_hash, + "k_array,i32", + "count(1)"); +BENCHMARK_NAMED_PARAM( + doPrefixSortRun, + count_k_array_k_hash, + "k_array,i64", + "count(1)"); +BENCHMARK_DRAW_LINE(); + +// Count aggregate. +AGG_BENCHMARKS(count, k_array) +AGG_BENCHMARKS(count, k_norm) +AGG_BENCHMARKS(count, k_hash) +MULTI_KEY_AGG_BENCHMARKS(count, k_array, i32) +MULTI_KEY_AGG_BENCHMARKS(count, k_array, i64) +MULTI_KEY_AGG_BENCHMARKS(count, k_hash, f32) +MULTI_KEY_AGG_BENCHMARKS(count, k_hash, f64) +BENCHMARK_DRAW_LINE(); + +// Avg aggregate. +AGG_BENCHMARKS(avg, k_array) +AGG_BENCHMARKS(avg, k_norm) +AGG_BENCHMARKS(avg, k_hash) +MULTI_KEY_AGG_BENCHMARKS(avg, k_array, i32) +MULTI_KEY_AGG_BENCHMARKS(avg, k_array, i64) +MULTI_KEY_AGG_BENCHMARKS(avg, k_hash, f32) +MULTI_KEY_AGG_BENCHMARKS(avg, k_hash, f64) +BENCHMARK_DRAW_LINE(); + +// Min aggregate. +AGG_BENCHMARKS(min, k_array) +AGG_BENCHMARKS(min, k_norm) +AGG_BENCHMARKS(min, k_hash) +MULTI_KEY_AGG_BENCHMARKS(min, k_array, i32) +MULTI_KEY_AGG_BENCHMARKS(min, k_array, i64) +MULTI_KEY_AGG_BENCHMARKS(min, k_hash, f32) +MULTI_KEY_AGG_BENCHMARKS(min, k_hash, f64) +BENCHMARK_DRAW_LINE(); + +// Max aggregate. +AGG_BENCHMARKS(max, k_array) +AGG_BENCHMARKS(max, k_norm) +AGG_BENCHMARKS(max, k_hash) +MULTI_KEY_AGG_BENCHMARKS(max, k_array, i32) +MULTI_KEY_AGG_BENCHMARKS(max, k_array, i64) +MULTI_KEY_AGG_BENCHMARKS(max, k_hash, f32) +MULTI_KEY_AGG_BENCHMARKS(max, k_hash, f64) +BENCHMARK_DRAW_LINE(); + +} // namespace + +int main(int argc, char** argv) { + folly::Init(&argc, &argv); + facebook::velox::memory::MemoryManager::initialize({}); + + benchmark = std::make_unique(); + folly::runBenchmarks(); + std::cout << "WindowNanos: " << benchmark->windowNanos().toString() << "\n"; + benchmark.reset(); + return 0; +} diff --git a/velox/exec/fuzzer/AggregationFuzzerBase.cpp b/velox/exec/fuzzer/AggregationFuzzerBase.cpp index 95e6cb9d125b..b6fe99a14491 100644 --- a/velox/exec/fuzzer/AggregationFuzzerBase.cpp +++ b/velox/exec/fuzzer/AggregationFuzzerBase.cpp @@ -242,9 +242,9 @@ std::vector AggregationFuzzerBase::generateSortingKeys( const std::string& prefix, std::vector& names, std::vector& types, - bool rangeFrame) { + bool rangeFrame, + std::optional numKeys) { std::vector keys; - vector_size_t numKeys; vector_size_t maxDepth; std::vector sortingKeyTypes = defaultScalarTypes(); @@ -262,12 +262,14 @@ std::vector AggregationFuzzerBase::generateSortingKeys( DOUBLE()}; maxDepth = 0; } else { - numKeys = randInt(1, 5); + if (!numKeys.has_value()) { + numKeys = boost::random::uniform_int_distribution(1, 5)(rng_); + } // Pick random, possibly complex, type. maxDepth = 2; } - for (auto i = 0; i < numKeys; ++i) { + for (auto i = 0; i < numKeys.value(); ++i) { keys.push_back(fmt::format("{}{}", prefix, i)); types.push_back(vectorFuzzer_.randOrderableType(sortingKeyTypes, maxDepth)); names.push_back(keys.back()); diff --git a/velox/exec/fuzzer/AggregationFuzzerBase.h b/velox/exec/fuzzer/AggregationFuzzerBase.h index 9f7d6161f7bb..a2237f8a9d74 100644 --- a/velox/exec/fuzzer/AggregationFuzzerBase.h +++ b/velox/exec/fuzzer/AggregationFuzzerBase.h @@ -187,7 +187,8 @@ class AggregationFuzzerBase { const std::string& prefix, std::vector& names, std::vector& types, - bool rangeFrame = false); + bool rangeFrame = false, + std::optional numKeys = std::nullopt); std::pair pickSignature(); diff --git a/velox/exec/fuzzer/WindowFuzzer.cpp b/velox/exec/fuzzer/WindowFuzzer.cpp index d73318452fa7..a94feb48a6ef 100644 --- a/velox/exec/fuzzer/WindowFuzzer.cpp +++ b/velox/exec/fuzzer/WindowFuzzer.cpp @@ -254,8 +254,9 @@ std::vector WindowFuzzer::generateSortingKeysAndOrders( const std::string& prefix, std::vector& names, std::vector& types, - bool isKRangeFrame) { - auto keys = generateSortingKeys(prefix, names, types, isKRangeFrame); + bool isKRangeFrame, + std::optional numKeys) { + auto keys = generateSortingKeys(prefix, names, types, isKRangeFrame, numKeys); std::vector results; for (auto i = 0; i < keys.size(); ++i) { auto asc = vectorFuzzer_.coinToss(0.5); @@ -451,7 +452,10 @@ void WindowFuzzer::go() { auto useRowNumberKey = requireSortedInput || windowType == core::WindowNode::WindowType::kRows; - const auto partitionKeys = generateSortingKeys("p", argNames, argTypes); + const uint32_t numKeys = + boost::random::uniform_int_distribution(1, 15)(rng_); + const auto partitionKeys = + generateSortingKeys("p", argNames, argTypes, false, numKeys); std::vector sortingKeysAndOrders; TypeKind orderByTypeKind; @@ -465,12 +469,12 @@ void WindowFuzzer::go() { // kRange frames need only one order by key. This would be row_number for // functions that are order dependent. sortingKeysAndOrders = - generateSortingKeysAndOrders("s", argNames, argTypes, true); + generateSortingKeysAndOrders("s", argNames, argTypes, true, numKeys); orderByTypeKind = argTypes.back()->kind(); } else if (vectorFuzzer_.coinToss(0.5)) { // 50% chance without order-by clause. sortingKeysAndOrders = - generateSortingKeysAndOrders("s", argNames, argTypes); + generateSortingKeysAndOrders("s", argNames, argTypes, false, numKeys); } auto input = generateInputDataWithRowNumber( diff --git a/velox/exec/fuzzer/WindowFuzzer.h b/velox/exec/fuzzer/WindowFuzzer.h index 447eec87f646..4e9b88057b35 100644 --- a/velox/exec/fuzzer/WindowFuzzer.h +++ b/velox/exec/fuzzer/WindowFuzzer.h @@ -188,7 +188,8 @@ class WindowFuzzer : public AggregationFuzzerBase { const std::string& prefix, std::vector& names, std::vector& types, - const bool isKRangeFrame = false); + const bool isKRangeFrame = false, + std::optional numKeys = std::nullopt); // Return 'true' if query plans failed. bool verifyWindow( diff --git a/velox/functions/prestosql/window/tests/AggregateWindowTest.cpp b/velox/functions/prestosql/window/tests/AggregateWindowTest.cpp index 4e216de1a19c..a0c9b3f03143 100644 --- a/velox/functions/prestosql/window/tests/AggregateWindowTest.cpp +++ b/velox/functions/prestosql/window/tests/AggregateWindowTest.cpp @@ -422,5 +422,26 @@ TEST_F(AggregateWindowTest, zeroRangeFrame) { test("range between k following and unbounded following", expected); } +TEST_F(AggregateWindowTest, singlePartitionColumnForPrefixSort) { + auto size = 100; + auto input = makeRowVector( + {makeRandomInputVector(VARCHAR(), size, 0.0), + makeFlatVector(size, [](auto row) { return row; }), + makeFlatVector(size, [](auto row) { return row; })}); + // Single partition with varchar column triggers window sorting to use + // std::sort instead of Prefix sort used for multi-key partition/order by + // cases. + WindowTestBase::testWindowFunction( + {input}, + "sum(c2)", + {"partition by c0"}, + {"rows between unbounded preceding and unbounded following"}); + WindowTestBase::testWindowFunction( + {input}, + "min(c2)", + {"partition by c0"}, + {"rows between unbounded preceding and unbounded following"}); +} + }; // namespace }; // namespace facebook::velox::window::test