blob: baf27e72ddc859cfc2e0986f2624c8337c6df36f [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 "runtime/runtime_predicate.h"
#include <gtest/gtest.h>
#include <array>
#include "core/data_type/data_type_factory.hpp"
#include "core/data_type/data_type_number.h"
#include "core/field.h"
#include "exec/pipeline/thrift_builder.h"
#include "exprs/expr_zonemap_filter.h"
#include "exprs/vexpr_context.h"
#include "exprs/vtopn_pred.h"
#include "runtime/descriptors.h"
#include "runtime/query_context.h"
#include "testutil/mock/mock_runtime_state.h"
namespace doris {
namespace {
constexpr TPlanNodeId SOURCE_NODE_ID = 10;
constexpr TPlanNodeId TARGET_NODE_ID = 20;
constexpr SlotId SLOT_ID = 0;
TTopnFilterDesc create_topn_filter_desc(PrimitiveType type = TYPE_INT, bool is_asc = true,
bool nulls_first = false) {
auto target_expr = TRuntimeFilterDescBuilder::get_default_expr();
target_expr.nodes[0].__set_type(create_type_desc(type));
TTopnFilterDesc desc;
desc.__set_source_node_id(SOURCE_NODE_ID);
desc.__set_is_asc(is_asc);
desc.__set_null_first(nulls_first);
desc.__set_target_node_id_to_target_expr({{TARGET_NODE_ID, target_expr}});
return desc;
}
SlotDescriptor create_int_slot_descriptor() {
SlotDescriptor slot_desc;
slot_desc._id = SLOT_ID;
slot_desc._col_name = "k1";
slot_desc._type = DataTypeFactory::instance().create_data_type(PrimitiveType::TYPE_INT, false);
return slot_desc;
}
VExprContextSPtr create_prepared_topn_expr(MockRuntimeState* state, const DataTypePtr& data_type,
const Field& top_value = Field(), bool is_asc = true,
bool nulls_first = false) {
DORIS_CHECK(state != nullptr);
DORIS_CHECK(data_type != nullptr);
auto desc = create_topn_filter_desc(data_type->get_primitive_type(), is_asc, nulls_first);
state->get_query_ctx()->init_runtime_predicates({desc});
auto& predicate = state->get_query_ctx()->get_runtime_predicate(SOURCE_NODE_ID);
predicate.set_detected_source();
DORIS_CHECK(predicate.init_target(TARGET_NODE_ID, {}, -1).ok());
if (!top_value.is_null()) {
DORIS_CHECK(predicate.update(top_value).ok());
}
TExprNode node;
node.__set_type(create_type_desc(PrimitiveType::TYPE_BOOLEAN));
node.__set_is_nullable(data_type->is_nullable());
auto expr = VTopNPred::create_shared(node, SOURCE_NODE_ID, nullptr);
expr->add_child(VSlotRef::create_shared(SLOT_ID, 0, -1, data_type, "topn_column"));
auto context = VExprContext::create_shared(std::move(expr));
DORIS_CHECK(context->prepare(state, RowDescriptor()).ok());
DORIS_CHECK(context->open(state).ok());
return context;
}
} // namespace
TEST(RuntimePredicateTest, init_target_creates_column_predicate_for_valid_column_id) {
RuntimePredicate predicate(create_topn_filter_desc());
predicate.set_detected_source();
auto slot_desc = create_int_slot_descriptor();
phmap::flat_hash_map<int, SlotDescriptor*> slot_id_to_slot_desc;
slot_id_to_slot_desc[SLOT_ID] = &slot_desc;
ASSERT_TRUE(predicate.init_target(TARGET_NODE_ID, slot_id_to_slot_desc, 0).ok());
EXPECT_TRUE(predicate.enable());
EXPECT_EQ("k1", predicate.get_col_name(TARGET_NODE_ID));
EXPECT_NE(nullptr, predicate.get_predicate(TARGET_NODE_ID));
}
TEST(RuntimePredicateTest, init_target_without_column_predicate_still_enables_runtime_filter) {
RuntimePredicate predicate(create_topn_filter_desc());
predicate.set_detected_source();
phmap::flat_hash_map<int, SlotDescriptor*> slot_id_to_slot_desc;
ASSERT_TRUE(predicate.init_target(TARGET_NODE_ID, slot_id_to_slot_desc, -1).ok());
EXPECT_TRUE(predicate.enable());
EXPECT_EQ(nullptr, predicate.get_predicate(TARGET_NODE_ID));
auto top_value = Field::create_field<TYPE_INT>(10);
ASSERT_TRUE(predicate.update(top_value).ok());
EXPECT_TRUE(predicate.has_value());
EXPECT_EQ(top_value, predicate.get_value());
}
TEST(RuntimePredicateTest, TopNPredicateFiltersRawFixedValuesUsingCurrentBound) {
MockRuntimeState state;
const auto type = std::make_shared<DataTypeInt32>();
auto context = create_prepared_topn_expr(&state, type, Field::create_field<TYPE_INT>(3));
const std::array<int32_t, 4> values {1, 3, 4, 2};
IColumn::Filter matches(values.size(), 1);
ASSERT_TRUE(context->root()->can_execute_on_raw_fixed_values(type, 0));
ASSERT_TRUE(context->root()
->execute_on_raw_fixed_values(
reinterpret_cast<const uint8_t*>(values.data()), values.size(),
sizeof(int32_t), type, 0, matches.data())
.ok());
EXPECT_EQ(matches, (IColumn::Filter {1, 1, 0, 1}));
context->close();
}
TEST(RuntimePredicateTest, TopNPredicateEvaluatesDictionaryValuesUsingCurrentBound) {
MockRuntimeState state;
const auto type = std::make_shared<DataTypeInt32>();
auto context = create_prepared_topn_expr(&state, type, Field::create_field<TYPE_INT>(3));
DictionaryEvalContext dictionary_ctx;
dictionary_ctx.slots.emplace(0, DictionaryEvalContext::SlotDictionary {
.data_type = type,
.values = {Field::create_field<TYPE_INT>(4),
Field::create_field<TYPE_INT>(5)}});
ASSERT_TRUE(context->root()->can_evaluate_dictionary_filter());
EXPECT_EQ(context->root()->evaluate_dictionary_filter(dictionary_ctx),
ZoneMapFilterResult::kNoMatch);
dictionary_ctx.slots.at(0).values.push_back(Field::create_field<TYPE_INT>(2));
EXPECT_EQ(context->root()->evaluate_dictionary_filter(dictionary_ctx),
ZoneMapFilterResult::kMayMatch);
context->close();
}
TEST(RuntimePredicateTest, TopNPredicateFiltersRawBinaryValuesUsingCurrentBound) {
MockRuntimeState state;
const auto type = DataTypeFactory::instance().create_data_type(TYPE_STRING, false);
auto context =
create_prepared_topn_expr(&state, type, Field::create_field<TYPE_STRING>("middle"));
const std::array<StringRef, 3> values {StringRef("alpha"), StringRef("middle"),
StringRef("zulu")};
IColumn::Filter matches(values.size(), 1);
ASSERT_TRUE(context->root()->can_execute_on_raw_binary_values(type, 0));
ASSERT_TRUE(context->root()
->execute_on_raw_binary_values(values.data(), values.size(), type, 0,
matches.data())
.ok());
EXPECT_EQ(matches, (IColumn::Filter {1, 1, 0}));
context->close();
}
TEST(RuntimePredicateTest, TopNPredicateFiltersRawVarbinaryAndDictionaryValues) {
MockRuntimeState state;
const auto type = DataTypeFactory::instance().create_data_type(TYPE_VARBINARY, false);
auto context = create_prepared_topn_expr(
&state, type, Field::create_field<TYPE_VARBINARY>(StringView("middle")));
const std::array<StringRef, 3> values {StringRef("alpha"), StringRef("middle"),
StringRef("zulu")};
IColumn::Filter matches(values.size(), 1);
ASSERT_TRUE(context->root()
->execute_on_raw_binary_values(values.data(), values.size(), type, 0,
matches.data())
.ok());
EXPECT_EQ(matches, (IColumn::Filter {1, 1, 0}));
DictionaryEvalContext dictionary_ctx;
dictionary_ctx.slots.emplace(
0, DictionaryEvalContext::SlotDictionary {
.data_type = type,
.values = {Field::create_field<TYPE_VARBINARY>(StringView("zulu"))}});
EXPECT_EQ(context->root()->evaluate_dictionary_filter(dictionary_ctx),
ZoneMapFilterResult::kNoMatch);
dictionary_ctx.slots.at(0).values.push_back(
Field::create_field<TYPE_VARBINARY>(StringView("alpha")));
EXPECT_EQ(context->root()->evaluate_dictionary_filter(dictionary_ctx),
ZoneMapFilterResult::kMayMatch);
context->close();
}
TEST(RuntimePredicateTest, TopNPredicateKeepsDirectCapabilityBeforeFirstBound) {
MockRuntimeState state;
const auto type = std::make_shared<DataTypeInt32>();
auto context = create_prepared_topn_expr(&state, type);
const std::array<int32_t, 2> values {1, 4};
IColumn::Filter matches(values.size(), 1);
ASSERT_TRUE(context->root()->can_execute_on_raw_fixed_values(type, 0));
EXPECT_TRUE(context->root()->raw_predicate_result_for_null());
ASSERT_TRUE(context->root()
->execute_on_raw_fixed_values(
reinterpret_cast<const uint8_t*>(values.data()), values.size(),
sizeof(int32_t), type, 0, matches.data())
.ok());
EXPECT_EQ(matches, (IColumn::Filter {1, 1}));
auto& predicate = state.get_query_ctx()->get_runtime_predicate(SOURCE_NODE_ID);
ASSERT_TRUE(predicate.update(Field::create_field<TYPE_INT>(2)).ok());
EXPECT_FALSE(context->root()->raw_predicate_result_for_null());
ASSERT_TRUE(context->root()
->execute_on_raw_fixed_values(
reinterpret_cast<const uint8_t*>(values.data()), values.size(),
sizeof(int32_t), type, 0, matches.data())
.ok());
EXPECT_EQ(matches, (IColumn::Filter {1, 0}));
context->close();
}
TEST(RuntimePredicateTest, TopNPredicateAdvertisesDirectCapabilityForEverySupportedType) {
struct TypeSpec {
PrimitiveType type;
int precision = 0;
int scale = 0;
bool binary = false;
};
const std::array<TypeSpec, 23> supported_types {{
{TYPE_BOOLEAN},
{TYPE_TINYINT},
{TYPE_SMALLINT},
{TYPE_INT},
{TYPE_BIGINT},
{TYPE_LARGEINT},
{TYPE_DATE},
{TYPE_DATETIME},
{TYPE_DATEV2},
{TYPE_DATETIMEV2},
{TYPE_TIMESTAMPTZ},
{TYPE_TIMEV2},
{TYPE_DECIMAL32, 9, 2},
{TYPE_DECIMAL64, 18, 2},
{TYPE_DECIMALV2, 27, 9},
{TYPE_DECIMAL128I, 38, 2},
{TYPE_DECIMAL256, 76, 2},
{TYPE_IPV4},
{TYPE_IPV6},
{TYPE_CHAR, 0, 0, true},
{TYPE_STRING, 0, 0, true},
{TYPE_VARCHAR, 0, 0, true},
{TYPE_VARBINARY, 0, 0, true},
}};
for (const auto& spec : supported_types) {
SCOPED_TRACE(type_to_string(spec.type));
MockRuntimeState state;
const auto data_type = DataTypeFactory::instance().create_data_type(
spec.type, false, spec.precision, spec.scale);
auto context = create_prepared_topn_expr(&state, data_type);
if (spec.binary) {
EXPECT_TRUE(context->root()->can_execute_on_raw_binary_values(data_type, 0));
} else {
EXPECT_TRUE(context->root()->can_execute_on_raw_fixed_values(data_type, 0));
}
EXPECT_TRUE(context->root()->can_evaluate_dictionary_filter());
context->close();
}
}
TEST(RuntimePredicateTest, DescTopNPredicateFiltersRawValuesUsingCurrentBound) {
MockRuntimeState state;
const auto type = std::make_shared<DataTypeInt32>();
auto context = create_prepared_topn_expr(&state, type, Field::create_field<TYPE_INT>(3), false);
const std::array<int32_t, 3> values {1, 3, 4};
IColumn::Filter matches(values.size(), 1);
ASSERT_TRUE(context->root()
->execute_on_raw_fixed_values(
reinterpret_cast<const uint8_t*>(values.data()), values.size(),
sizeof(int32_t), type, 0, matches.data())
.ok());
EXPECT_EQ(matches, (IColumn::Filter {0, 1, 1}));
context->close();
}
TEST(RuntimePredicateTest, NullableNullsFirstTopNSupportsRawNullSemantics) {
MockRuntimeState state;
const auto type = make_nullable(std::make_shared<DataTypeInt32>());
auto context =
create_prepared_topn_expr(&state, type, Field::create_field<TYPE_INT>(3), true, true);
EXPECT_TRUE(context->root()->can_execute_on_raw_fixed_values(type, 0));
EXPECT_TRUE(context->root()->raw_predicate_result_for_null());
EXPECT_FALSE(context->root()->can_evaluate_dictionary_filter());
context->close();
}
TEST(RuntimePredicateTest, TopNPredicateNormalizesConstNullableComparisonResult) {
constexpr size_t rows = 3;
const auto type = make_nullable(std::make_shared<DataTypeInt32>());
for (const bool nulls_first : {false, true}) {
SCOPED_TRACE(nulls_first ? "NULLS FIRST" : "NULLS LAST");
MockRuntimeState state;
auto context = create_prepared_topn_expr(&state, type, Field::create_field<TYPE_INT>(3),
true, nulls_first);
Block block;
block.insert(
{ColumnNullable::create(ColumnInt32::create(rows, 0), ColumnUInt8::create(rows, 1)),
type, "topn_column"});
ColumnPtr result_column;
auto status = context->root()->execute_column(context.get(), &block, nullptr, block.rows(),
result_column);
ASSERT_TRUE(status.ok()) << status;
ASSERT_TRUE(is_column_const(*result_column));
EXPECT_FALSE(result_column->is_null_at(0));
EXPECT_EQ(result_column->get_bool(0), nulls_first);
int result_column_id = -1;
status = context->execute(&block, &result_column_id);
ASSERT_TRUE(status.ok()) << status;
const auto& strict_result = block.get_by_position(result_column_id);
ASSERT_TRUE(is_column_const(*strict_result.column));
EXPECT_NE(nullptr, check_and_get_column_with_const<ColumnNullable>(*strict_result.column));
EXPECT_FALSE(strict_result.column->is_null_at(0));
EXPECT_EQ(strict_result.column->get_bool(0), nulls_first);
IColumn::Filter filter(rows, 1);
bool can_filter_all = false;
status = context->execute_filter(&block, filter.data(), rows, false, &can_filter_all);
ASSERT_TRUE(status.ok()) << status;
EXPECT_EQ(filter, IColumn::Filter(rows, nulls_first));
EXPECT_EQ(can_filter_all, !nulls_first);
context->close();
}
}
} // namespace doris