blob: cc26b6679a1342bab4541f52556cba58c1930e6a [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 "exprs/vtopn_pred.h"
#include <cstring>
#include "exprs/expr_zonemap_filter.h"
#include "util/simd/parquet_kernels.h"
namespace doris {
namespace {
using simd::RawComparisonOp;
size_t topn_raw_value_size(PrimitiveType type) {
switch (type) {
#define RETURN_TOPN_RAW_SIZE(TYPE) \
case TYPE: \
return sizeof(typename PrimitiveTypeTraits<TYPE>::CppType)
RETURN_TOPN_RAW_SIZE(TYPE_BOOLEAN);
RETURN_TOPN_RAW_SIZE(TYPE_TINYINT);
RETURN_TOPN_RAW_SIZE(TYPE_SMALLINT);
RETURN_TOPN_RAW_SIZE(TYPE_INT);
RETURN_TOPN_RAW_SIZE(TYPE_BIGINT);
RETURN_TOPN_RAW_SIZE(TYPE_LARGEINT);
RETURN_TOPN_RAW_SIZE(TYPE_FLOAT);
RETURN_TOPN_RAW_SIZE(TYPE_DOUBLE);
RETURN_TOPN_RAW_SIZE(TYPE_DATE);
RETURN_TOPN_RAW_SIZE(TYPE_DATETIME);
RETURN_TOPN_RAW_SIZE(TYPE_DATEV2);
RETURN_TOPN_RAW_SIZE(TYPE_DATETIMEV2);
RETURN_TOPN_RAW_SIZE(TYPE_TIMESTAMPTZ);
// Master no longer defines a C++ carrier for legacy TYPE_TIME; Parquet time values use
// TYPE_TIMEV2, so advertising the deprecated tag would make direct filtering unusable.
RETURN_TOPN_RAW_SIZE(TYPE_TIMEV2);
RETURN_TOPN_RAW_SIZE(TYPE_DECIMAL32);
RETURN_TOPN_RAW_SIZE(TYPE_DECIMAL64);
RETURN_TOPN_RAW_SIZE(TYPE_DECIMALV2);
RETURN_TOPN_RAW_SIZE(TYPE_DECIMAL128I);
RETURN_TOPN_RAW_SIZE(TYPE_DECIMAL256);
RETURN_TOPN_RAW_SIZE(TYPE_IPV4);
RETURN_TOPN_RAW_SIZE(TYPE_IPV6);
#undef RETURN_TOPN_RAW_SIZE
default:
return 0;
}
}
bool topn_binary_type(PrimitiveType type) {
return is_string_type(type) || type == TYPE_VARBINARY;
}
template <typename T, PrimitiveType PT>
void execute_topn_raw_comparison(const uint8_t* values, size_t num_values, const Field& bound,
RawComparisonOp op, uint8_t* matches) {
simd::raw_compare(values, num_values, bound.get<PT>(), op, matches);
}
template <typename T>
bool topn_raw_scalar_matches(const T& value, const T& bound, RawComparisonOp op) {
return op == RawComparisonOp::LE ? value <= bound : value >= bound;
}
template <PrimitiveType PT>
void execute_topn_raw_scalar(const uint8_t* values, size_t num_values, const Field& bound,
RawComparisonOp op, uint8_t* matches) {
using T = typename PrimitiveTypeTraits<PT>::CppType;
const T& typed_bound = bound.get<PT>();
for (size_t row = 0; row < num_values; ++row) {
T value;
std::memcpy(&value, values + row * sizeof(T), sizeof(T));
matches[row] &= topn_raw_scalar_matches(value, typed_bound, op) ? 1 : 0;
}
}
bool topn_string_matches(int comparison, RawComparisonOp op) {
return op == RawComparisonOp::LE ? comparison <= 0 : comparison >= 0;
}
} // namespace
bool VTopNPred::can_execute_on_raw_fixed_values(const DataTypePtr& data_type, int column_id) const {
if (_predicate == nullptr || data_type == nullptr || _children.size() != 1) {
return false;
}
const auto slot = std::dynamic_pointer_cast<VSlotRef>(_children[0]);
if (slot == nullptr || slot->column_id() != column_id || slot->data_type() == nullptr) {
return false;
}
const auto raw_type = remove_nullable(data_type);
return remove_nullable(slot->data_type())->equals(*raw_type) &&
topn_raw_value_size(raw_type->get_primitive_type()) != 0;
}
Status VTopNPred::execute_on_raw_fixed_values(const uint8_t* values, size_t num_values,
size_t value_width, const DataTypePtr& data_type,
int column_id, uint8_t* matches) const {
if (!can_execute_on_raw_fixed_values(data_type, column_id)) {
return Status::NotSupported("TopN predicate cannot evaluate raw fixed-width values");
}
DORIS_CHECK(values != nullptr || num_values == 0);
DORIS_CHECK(matches != nullptr || num_values == 0);
const auto primitive_type = remove_nullable(data_type)->get_primitive_type();
const size_t expected_width = topn_raw_value_size(primitive_type);
if (value_width != expected_width) {
return Status::Corruption("Raw TopN width {} does not match expected {}", value_width,
expected_width);
}
// The bound is mutable and may arrive after reader initialization. Snapshot it once per batch
// so every row observes one monotonic TopN frontier without invalidating cached capability.
const Field bound = _predicate->get_value();
if (bound.is_null()) {
return Status::OK();
}
if (!expr_zonemap::field_types_compatible(bound.get_type(), primitive_type)) {
return Status::InternalError("TopN bound type {} does not match raw value type {}",
type_to_string(bound.get_type()),
type_to_string(primitive_type));
}
const auto op = _predicate->is_asc() ? RawComparisonOp::LE : RawComparisonOp::GE;
switch (primitive_type) {
case TYPE_INT:
execute_topn_raw_comparison<int32_t, TYPE_INT>(values, num_values, bound, op, matches);
break;
case TYPE_BIGINT:
execute_topn_raw_comparison<int64_t, TYPE_BIGINT>(values, num_values, bound, op, matches);
break;
case TYPE_FLOAT:
execute_topn_raw_comparison<float, TYPE_FLOAT>(values, num_values, bound, op, matches);
break;
case TYPE_DOUBLE:
execute_topn_raw_comparison<double, TYPE_DOUBLE>(values, num_values, bound, op, matches);
break;
#define EXECUTE_TOPN_RAW_SCALAR(TYPE) \
case TYPE: \
execute_topn_raw_scalar<TYPE>(values, num_values, bound, op, matches); \
break
EXECUTE_TOPN_RAW_SCALAR(TYPE_BOOLEAN);
EXECUTE_TOPN_RAW_SCALAR(TYPE_TINYINT);
EXECUTE_TOPN_RAW_SCALAR(TYPE_SMALLINT);
EXECUTE_TOPN_RAW_SCALAR(TYPE_LARGEINT);
EXECUTE_TOPN_RAW_SCALAR(TYPE_DATE);
EXECUTE_TOPN_RAW_SCALAR(TYPE_DATETIME);
EXECUTE_TOPN_RAW_SCALAR(TYPE_DATEV2);
EXECUTE_TOPN_RAW_SCALAR(TYPE_DATETIMEV2);
EXECUTE_TOPN_RAW_SCALAR(TYPE_TIMESTAMPTZ);
// Keep dispatch aligned with topn_raw_value_size(): legacy TYPE_TIME has no master carrier.
EXECUTE_TOPN_RAW_SCALAR(TYPE_TIMEV2);
EXECUTE_TOPN_RAW_SCALAR(TYPE_DECIMAL32);
EXECUTE_TOPN_RAW_SCALAR(TYPE_DECIMAL64);
EXECUTE_TOPN_RAW_SCALAR(TYPE_DECIMALV2);
EXECUTE_TOPN_RAW_SCALAR(TYPE_DECIMAL128I);
EXECUTE_TOPN_RAW_SCALAR(TYPE_DECIMAL256);
EXECUTE_TOPN_RAW_SCALAR(TYPE_IPV4);
EXECUTE_TOPN_RAW_SCALAR(TYPE_IPV6);
#undef EXECUTE_TOPN_RAW_SCALAR
default:
return Status::NotSupported("TopN raw fixed-width type {} is unsupported",
type_to_string(primitive_type));
}
return Status::OK();
}
bool VTopNPred::can_execute_on_raw_binary_values(const DataTypePtr& data_type,
int column_id) const {
if (_predicate == nullptr || data_type == nullptr || _children.size() != 1) {
return false;
}
const auto raw_type = remove_nullable(data_type);
const auto raw_primitive_type = raw_type->get_primitive_type();
if (!topn_binary_type(raw_primitive_type)) {
return false;
}
const auto slot = std::dynamic_pointer_cast<VSlotRef>(_children[0]);
if (slot == nullptr || slot->column_id() != column_id || slot->data_type() == nullptr) {
return false;
}
const auto slot_primitive_type = remove_nullable(slot->data_type())->get_primitive_type();
return (is_string_type(raw_primitive_type) && is_string_type(slot_primitive_type)) ||
(raw_primitive_type == TYPE_VARBINARY && slot_primitive_type == TYPE_VARBINARY);
}
Status VTopNPred::execute_on_raw_binary_values(const StringRef* values, size_t num_values,
const DataTypePtr& data_type, int column_id,
uint8_t* matches) const {
if (!can_execute_on_raw_binary_values(data_type, column_id)) {
return Status::NotSupported("TopN predicate cannot evaluate raw binary values");
}
DORIS_CHECK(values != nullptr || num_values == 0);
DORIS_CHECK(matches != nullptr || num_values == 0);
const Field bound = _predicate->get_value();
if (bound.is_null()) {
return Status::OK();
}
const auto primitive_type = remove_nullable(data_type)->get_primitive_type();
StringRef bound_ref;
if (is_string_type(primitive_type) && is_string_type(bound.get_type())) {
const auto& value = bound.get<TYPE_STRING>();
bound_ref = StringRef(value.data(), value.size());
} else if (primitive_type == TYPE_VARBINARY && bound.get_type() == TYPE_VARBINARY) {
bound_ref = bound.get<TYPE_VARBINARY>().to_string_ref();
} else {
return Status::InternalError("TopN bound type {} does not match raw binary type {}",
type_to_string(bound.get_type()),
type_to_string(primitive_type));
}
const auto op = _predicate->is_asc() ? RawComparisonOp::LE : RawComparisonOp::GE;
for (size_t row = 0; row < num_values; ++row) {
matches[row] &= topn_string_matches(values[row].compare(bound_ref), op) ? 1 : 0;
}
return Status::OK();
}
bool VTopNPred::can_evaluate_dictionary_filter() const {
if (_predicate == nullptr || _predicate->nulls_first() || _children.size() != 1) {
return false;
}
const auto slot = std::dynamic_pointer_cast<VSlotRef>(_children[0]);
if (slot == nullptr || slot->data_type() == nullptr) {
return false;
}
const auto primitive_type = remove_nullable(slot->data_type())->get_primitive_type();
return topn_raw_value_size(primitive_type) != 0 || topn_binary_type(primitive_type);
}
ZoneMapFilterResult VTopNPred::evaluate_dictionary_filter(const DictionaryEvalContext& ctx) const {
if (!can_evaluate_dictionary_filter()) {
return ZoneMapFilterResult::kUnsupported;
}
const auto slot = std::dynamic_pointer_cast<VSlotRef>(_children[0]);
DORIS_CHECK(slot != nullptr);
const auto* dictionary = ctx.slot(slot->column_id());
if (dictionary == nullptr ||
!expr_zonemap::data_types_compatible(dictionary->data_type, slot->data_type())) {
return ZoneMapFilterResult::kUnsupported;
}
const Field bound = _predicate->get_value();
if (bound.is_null()) {
return ZoneMapFilterResult::kMayMatch;
}
const auto primitive_type = remove_nullable(dictionary->data_type)->get_primitive_type();
if (!expr_zonemap::field_types_compatible(bound.get_type(), primitive_type)) {
return ZoneMapFilterResult::kUnsupported;
}
for (const Field& value : dictionary->values) {
if (value.is_null()) {
continue;
}
if (!expr_zonemap::field_types_compatible(value.get_type(), primitive_type)) {
return ZoneMapFilterResult::kUnsupported;
}
if ((_predicate->is_asc() && value <= bound) || (!_predicate->is_asc() && value >= bound)) {
return ZoneMapFilterResult::kMayMatch;
}
}
return ZoneMapFilterResult::kNoMatch;
}
} // namespace doris