blob: b5f31e90e166f21e28d7e810a248889ff3049538 [file]
// 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 <gtest/gtest.h>
#include <array>
#include <memory>
#include <string>
#include "agent/be_exec_version_manager.h"
#include "common/exception.h"
#include "core/column/column_decimal.h"
#include "core/column/column_nullable.h"
#include "core/column/variant_v2/column_variant_v2.h"
#include "core/data_type/data_type_decimal.h"
#include "core/data_type/data_type_nullable.h"
#include "core/data_type/data_type_number.h"
#include "core/data_type/data_type_variant.h"
#include "core/data_type/data_type_variant_v2.h"
#include "core/value/variant/variant_batch_builder.h"
#include "exprs/aggregate/aggregate_function_simple_factory.h"
#include "exprs/aggregate/aggregate_function_uniq.h"
namespace doris {
namespace {
class AggregateState {
public:
explicit AggregateState(const IAggregateFunction& function) : _function(function) {
place = reinterpret_cast<AggregateDataPtr>(
arena.aligned_alloc(function.size_of_data(), function.align_of_data()));
function.create(place);
}
~AggregateState() { _function.destroy(place); }
AggregateState(const AggregateState&) = delete;
AggregateState& operator=(const AggregateState&) = delete;
Arena arena;
AggregateDataPtr place = nullptr;
private:
const IAggregateFunction& _function;
};
ColumnVariantV2::MutablePtr encoded_values() {
VariantBatchBuilder builder(VariantBatchBuilder::ReserveHint {.rows = 7});
{
auto row = builder.begin_row();
row.add_int(1);
row.finish();
}
{
auto row = builder.begin_row();
row.add_decimal(10, 1);
row.finish();
}
{
auto row = builder.begin_row();
row.add_null();
row.finish();
}
{
auto row = builder.begin_row();
auto object = row.start_object();
object.add_key(StringRef("z"));
row.add_int(2);
object.add_key(StringRef("a"));
row.add_int(1);
object.finish();
row.finish();
}
{
auto row = builder.begin_row();
auto object = row.start_object();
object.add_key(StringRef("a"));
row.add_int(1);
object.add_key(StringRef("z"));
row.add_int(2);
object.finish();
row.finish();
}
{
auto row = builder.begin_row();
auto array = row.start_array();
row.add_int(1);
row.add_int(2);
array.finish();
row.finish();
}
{
auto row = builder.begin_row();
auto array = row.start_array();
row.add_int(2);
row.add_int(1);
array.finish();
row.finish();
}
VariantBatchBuilder block = builder.finish_batch();
auto column = ColumnVariantV2::create();
column->insert_encoded_batch(block);
return column;
}
ColumnVariantV2::MutablePtr typed_int_values() {
auto values = ColumnInt64::create();
for (int64_t value : {1, 1, 2, 0}) {
values->insert_value(value);
}
auto nulls = ColumnUInt8::create();
for (uint8_t value : {0, 0, 0, 1}) {
nulls->insert_value(value);
}
return ColumnVariantV2::create_typed(
ColumnNullable::create(std::move(values), std::move(nulls)),
std::make_shared<DataTypeInt64>());
}
ColumnVariantV2::MutablePtr typed_decimal_values() {
auto values = ColumnDecimal128V3::create(0, 2);
values->insert_value(Decimal128V3 {100});
values->insert_value(Decimal128V3 {200});
auto nulls = ColumnUInt8::create(2, 0);
return ColumnVariantV2::create_typed(
ColumnNullable::create(std::move(values), std::move(nulls)),
std::make_shared<DataTypeDecimal128>(38, 2));
}
void add_column(const IAggregateFunction& function, AggregateState& state, const IColumn& column) {
const IColumn* columns[] = {&column};
for (size_t row = 0; row < column.size(); ++row) {
function.add(state.place, columns, row, state.arena);
}
}
int64_t result(const IAggregateFunction& function, ConstAggregateDataPtr place) {
auto output = ColumnInt64::create();
function.insert_result_into(place, *output);
return output->get_data().front();
}
TEST(AggregateFunctionUniqVariantTest, CanonicalExactStateCoversEncodedAndTypedValues) {
AggregateFunctionUniqVariant function({std::make_shared<DataTypeVariantV2>()});
auto encoded = encoded_values();
AggregateState destination(function);
add_column(function, destination, *encoded);
EXPECT_EQ(result(function, destination.place), 5);
const size_t used_after_unique_values = destination.arena.used_size();
const IColumn* columns[] = {encoded.get()};
function.add(destination.place, columns, 0, destination.arena);
EXPECT_EQ(destination.arena.used_size(), used_after_unique_values);
{
AggregateState typed(function);
auto values = typed_int_values();
add_column(function, typed, *values);
EXPECT_EQ(result(function, typed.place), 3);
function.merge(destination.place, typed.place, destination.arena);
}
EXPECT_EQ(result(function, destination.place), 6);
{
AggregateState typed(function);
auto values = typed_decimal_values();
add_column(function, typed, *values);
function.merge(destination.place, typed.place, destination.arena);
}
EXPECT_EQ(result(function, destination.place), 6);
function.reset(destination.place);
EXPECT_EQ(result(function, destination.place), 0);
}
TEST(AggregateFunctionUniqVariantTest, SerializedStateOwnsOnlyCanonicalKeys) {
AggregateFunctionUniqVariant function({std::make_shared<DataTypeVariantV2>()});
AggregateState source(function);
auto encoded = encoded_values();
add_column(function, source, *encoded);
ColumnString serialized;
VectorBufferWriter writer(serialized);
function.serialize(source.place, writer);
writer.commit();
AggregateState restored(function);
VectorBufferReader first_reader(serialized.get_data_at(0));
function.deserialize(restored.place, first_reader, restored.arena);
EXPECT_EQ(result(function, restored.place), 5);
const size_t used_after_first_deserialize = restored.arena.used_size();
VectorBufferReader second_reader(serialized.get_data_at(0));
function.deserialize(restored.place, second_reader, restored.arena);
EXPECT_EQ(result(function, restored.place), 5);
EXPECT_EQ(restored.arena.used_size(), used_after_first_deserialize);
}
TEST(AggregateFunctionUniqVariantTest, DeserializeAndMergeCopiesNewKeysIntoDestinationArena) {
AggregateFunctionUniqVariant function({std::make_shared<DataTypeVariantV2>()});
AggregateState destination(function);
auto typed = typed_int_values();
add_column(function, destination, *typed);
EXPECT_EQ(result(function, destination.place), 3);
AggregateState scratch(function);
{
AggregateState source(function);
auto encoded = encoded_values();
add_column(function, source, *encoded);
ColumnString serialized;
VectorBufferWriter writer(serialized);
function.serialize(source.place, writer);
writer.commit();
VectorBufferReader reader(serialized.get_data_at(0));
function.deserialize_and_merge(destination.place, scratch.place, reader, destination.arena);
}
EXPECT_EQ(result(function, destination.place), 6);
}
TEST(AggregateFunctionUniqVariantTest, NullableFactorySkipsSqlNullButCountsVariantNull) {
const DataTypePtr variant_type = std::make_shared<DataTypeVariantV2>();
const DataTypePtr nullable_type = make_nullable(variant_type);
AggregateFunctionPtr function = AggregateFunctionSimpleFactory::instance().get(
"multi_distinct_count", {nullable_type}, std::make_shared<DataTypeInt64>(), false,
BeExecVersionManager::get_newest_version());
ASSERT_NE(function, nullptr);
auto nested = encoded_values();
auto outer_nulls = ColumnUInt8::create(nested->size(), 0);
outer_nulls->get_data()[5] = 1;
ColumnPtr nullable = ColumnNullable::create(std::move(nested), std::move(outer_nulls));
AggregateState state(*function);
add_column(*function, state, *nullable);
EXPECT_EQ(result(*function, state.place), 4);
}
TEST(AggregateFunctionUniqVariantTest, FactoryRejectsLegacyVariant) {
try {
static_cast<void>(AggregateFunctionSimpleFactory::instance().get(
"multi_distinct_count", {std::make_shared<DataTypeVariant>()},
std::make_shared<DataTypeInt64>(), false,
BeExecVersionManager::get_newest_version()));
FAIL() << "Expected legacy Variant to be rejected";
} catch (const Exception& exception) {
EXPECT_EQ(exception.code(), ErrorCode::INVALID_ARGUMENT);
EXPECT_NE(exception.message().find("legacy Variant"), std::string::npos)
<< exception.message();
EXPECT_NE(exception.message().find("Variant V2"), std::string::npos) << exception.message();
}
}
} // namespace
} // namespace doris