This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.1 by this push:
new d39e68c81ed [fix](be) Preserve floating-point equality in Parquet
pruning (#66470)
d39e68c81ed is described below
commit d39e68c81ed94ebbe64600239a8916b87f770965
Author: Gabriel <[email protected]>
AuthorDate: Fri Aug 7 12:58:19 2026 +0800
[fix](be) Preserve floating-point equality in Parquet pruning (#66470)
## Summary
- backport the floating-point pruning correctness fixes to branch-4.1
for File Scanner V2
- preserve Doris NaN and signed-zero equality semantics in V2 Parquet
Bloom, min/max, and IN-predicate pruning
- keep pruning conservative when Parquet statistics omit NaNs or a Bloom
encoding cannot safely prove absence
- leave the legacy File Scanner V1 path unchanged
## Testing
- clang-format 16 check on all affected C/C++ files
- compiled every affected production and test translation unit after
rebasing onto branch-4.1
- 121 focused BE unit tests passed, including expression, hybrid-set,
and native V2 Parquet pruning coverage
---
be/src/core/field.h | 13 +
be/src/exprs/expr_zonemap_filter.cpp | 37 ++-
be/src/exprs/expr_zonemap_filter.h | 4 +-
be/src/exprs/function/functions_comparison.h | 17 +-
be/src/exprs/hybrid_set.h | 18 +-
be/src/exprs/vdirect_in_predicate.h | 5 +-
be/src/exprs/vin_predicate.cpp | 8 +-
be/src/exprs/vin_predicate.h | 1 +
be/src/format_v2/parquet/parquet_statistics.cpp | 3 +
.../index/zone_map/zonemap_eval_context.cpp | 5 +
.../storage/index/zone_map/zonemap_eval_context.h | 3 +
be/test/core/field_test.cpp | 11 +
be/test/exprs/expr_zonemap_filter_test.cpp | 223 +++++++++++++-
be/test/exprs/hybrid_set_test.cpp | 29 ++
be/test/format_v2/parquet/parquet_scan_test.cpp | 241 +++++++++++++++
.../format_v2/parquet/parquet_statistics_test.cpp | 330 +++++++++++++++++++++
16 files changed, 921 insertions(+), 27 deletions(-)
diff --git a/be/src/core/field.h b/be/src/core/field.h
index d39d8208777..f418283d4e5 100644
--- a/be/src/core/field.h
+++ b/be/src/core/field.h
@@ -25,6 +25,7 @@
#include <algorithm>
#include <cassert>
+#include <cmath>
#include <cstring>
#include <map>
#include <string>
@@ -271,6 +272,18 @@ public:
template <PrimitiveType T>
const typename PrimitiveTypeTraits<T>::CppType& get() const;
+ bool is_nan() const {
+ // Keep type dispatch with the value so callers cannot reinterpret
Field storage using a
+ // mismatched PrimitiveType.
+ if (type == PrimitiveType::TYPE_FLOAT) {
+ return std::isnan(get<TYPE_FLOAT>());
+ }
+ if (type == PrimitiveType::TYPE_DOUBLE) {
+ return std::isnan(get<TYPE_DOUBLE>());
+ }
+ return false;
+ }
+
bool operator==(const Field& rhs) const {
return operator<=>(rhs) == std::strong_ordering::equal;
}
diff --git a/be/src/exprs/expr_zonemap_filter.cpp
b/be/src/exprs/expr_zonemap_filter.cpp
index 95502a761b0..608f9604397 100644
--- a/be/src/exprs/expr_zonemap_filter.cpp
+++ b/be/src/exprs/expr_zonemap_filter.cpp
@@ -18,7 +18,9 @@
#include "exprs/expr_zonemap_filter.h"
#include <algorithm>
+#include <cmath>
#include <set>
+#include <type_traits>
#include <utility>
#include "common/logging.h"
@@ -59,6 +61,24 @@ bool dictionary_contains(const
DictionaryEvalContext::SlotDictionary& dictionary
});
}
+template <typename T>
+bool floating_point_bloom_filter_may_contain(const segment_v2::BloomFilter&
bloom_filter, T value) {
+ static_assert(std::is_floating_point_v<T>);
+ // Doris equality collapses NaN payloads and signed zeros, while Parquet
Bloom hashes physical
+ // bytes. A negative probe is safe only after covering the entire
Doris-equivalent class.
+ if (std::isnan(value)) {
+ return true;
+ }
+ const auto test_value = [&](T candidate) {
+ return bloom_filter.test_bytes(reinterpret_cast<const
char*>(&candidate),
+ sizeof(candidate));
+ };
+ if (test_value(value)) {
+ return true;
+ }
+ return value == T {0} && test_value(-value);
+}
+
bool bloom_filter_may_contain(const BloomFilterEvalContext::SlotBloomFilter&
slot_filter,
const Field& value) {
DORIS_CHECK(slot_filter.data_type != nullptr);
@@ -83,13 +103,11 @@ bool bloom_filter_may_contain(const
BloomFilterEvalContext::SlotBloomFilter& slo
}
case TYPE_FLOAT: {
const float typed_value = value.get<TYPE_FLOAT>();
- return slot_filter.bloom_filter->test_bytes(reinterpret_cast<const
char*>(&typed_value),
- sizeof(typed_value));
+ return
floating_point_bloom_filter_may_contain(*slot_filter.bloom_filter, typed_value);
}
case TYPE_DOUBLE: {
const double typed_value = value.get<TYPE_DOUBLE>();
- return slot_filter.bloom_filter->test_bytes(reinterpret_cast<const
char*>(&typed_value),
- sizeof(typed_value));
+ return
floating_point_bloom_filter_may_contain(*slot_filter.bloom_filter, typed_value);
}
case TYPE_CHAR:
case TYPE_VARCHAR:
@@ -150,6 +168,7 @@ Status
materialize_hybrid_set_for_zonemap_filter(HybridSetBase& set, const DataT
DORIS_CHECK(value_type != nullptr);
result->contains_null = set.contain_null();
+ result->contains_nan = false;
result->values.clear();
result->min_value = Field();
result->max_value = Field();
@@ -164,6 +183,7 @@ Status
materialize_hybrid_set_for_zonemap_filter(HybridSetBase& set, const DataT
auto literal = VLiteral::create_shared(literal_node);
Field field;
literal->get_column_ptr()->get(0, field);
+ result->contains_nan |= field.is_nan();
result->values.emplace_back(std::move(field));
}
iterator->next();
@@ -243,7 +263,8 @@ ZoneMapFilterResult eval_null_zonemap(const
ZoneMapEvalContext& ctx, const VExpr
ZoneMapFilterResult eval_in_zonemap(const ZoneMapEvalContext& ctx, const
VExprSPtr& slot_expr,
bool is_not_in, const std::vector<Field>&
values,
- const Field& min_value, const Field&
max_value) {
+ bool contains_nan, const Field& min_value,
+ const Field& max_value) {
auto slot = std::dynamic_pointer_cast<VSlotRef>(slot_expr);
DORIS_CHECK(slot != nullptr);
// Empty IN has no candidate values, while NOT IN with an empty set cannot
filter anything.
@@ -276,6 +297,12 @@ ZoneMapFilterResult eval_in_zonemap(const
ZoneMapEvalContext& ctx, const VExprSP
return ZoneMapFilterResult::kNoMatch;
}
+ if (ctx.floating_nan_count_unknown(slot->column_id()) &&
+ ((!is_not_in && contains_nan) || (is_not_in && !contains_nan))) {
+ // Hidden Parquet NaNs can satisfy IN only when queried, and NOT IN
only when omitted.
+ return unsupported_zonemap_filter(ctx);
+ }
+
if (!range_stats_usable_for_zonemap(zone_map, slot_type)) {
return unsupported_zonemap_filter(ctx);
}
diff --git a/be/src/exprs/expr_zonemap_filter.h
b/be/src/exprs/expr_zonemap_filter.h
index a068a1e5147..b87d4e0030e 100644
--- a/be/src/exprs/expr_zonemap_filter.h
+++ b/be/src/exprs/expr_zonemap_filter.h
@@ -45,6 +45,7 @@ namespace doris::expr_zonemap {
struct InZonemapMaterializedSet {
bool contains_null = false;
+ bool contains_nan = false;
std::vector<Field> values;
Field min_value;
Field max_value;
@@ -142,7 +143,8 @@ ZoneMapFilterResult eval_null_zonemap(const
ZoneMapEvalContext& ctx, const VExpr
ZoneMapFilterResult eval_in_zonemap(const ZoneMapEvalContext& ctx, const
VExprSPtr& slot_expr,
bool is_not_in, const std::vector<Field>&
values,
- const Field& min_value, const Field&
max_value);
+ bool contains_nan, const Field& min_value,
+ const Field& max_value);
ZoneMapFilterResult eval_eq_dictionary(const DictionaryEvalContext& ctx,
const SlotLiteral& slot_literal);
diff --git a/be/src/exprs/function/functions_comparison.h
b/be/src/exprs/function/functions_comparison.h
index 1910bd845c7..3487bf9778b 100644
--- a/be/src/exprs/function/functions_comparison.h
+++ b/be/src/exprs/function/functions_comparison.h
@@ -322,6 +322,15 @@ inline ZoneMapFilterResult evaluate(const
ZoneMapEvalContext& ctx, const VExprSP
const auto effective_op = slot_literal->literal_on_left ? symmetric_op(op)
: op;
const auto& literal = slot_literal->literal;
+ const bool literal_is_nan = literal.is_nan();
+ const bool hidden_nan_can_match = (effective_op == Op::EQ &&
literal_is_nan) ||
+ (effective_op == Op::NE &&
!literal_is_nan) ||
+ (effective_op == Op::GT &&
!literal_is_nan) ||
+ effective_op == Op::GE;
+ if (ctx.floating_nan_count_unknown(slot_literal->slot_index) &&
hidden_nan_can_match) {
+ // Parquet bounds omit NaNs, so only operators that cannot match a
hidden NaN may prune.
+ return unsupported_zonemap_filter(ctx);
+ }
switch (effective_op) {
case Op::EQ:
return literal < zone_map.min_value || zone_map.max_value < literal
@@ -375,7 +384,13 @@ inline bool can_evaluate(const VExprSPtrs& arguments) {
}
inline bool can_evaluate_equality(const VExprSPtrs& arguments, Op op) {
- return op == Op::EQ && can_evaluate(arguments);
+ if (op != Op::EQ || !can_evaluate(arguments)) {
+ return false;
+ }
+ const auto slot_literal =
expr_zonemap::extract_slot_and_literal(arguments);
+ DORIS_CHECK(slot_literal.has_value());
+ // Bloom membership cannot disprove Doris NaN equality across different
physical encodings.
+ return !slot_literal->literal.is_nan();
}
inline bool dictionary_value_matches(const Field& value, const Field& literal,
Op op) {
diff --git a/be/src/exprs/hybrid_set.h b/be/src/exprs/hybrid_set.h
index f542968786b..6a408bc1a57 100644
--- a/be/src/exprs/hybrid_set.h
+++ b/be/src/exprs/hybrid_set.h
@@ -138,6 +138,19 @@ struct IsFixedContainer : std::false_type {};
template <typename T, size_t N>
struct IsFixedContainer<FixedContainer<T, N>> : std::true_type {};
+template <typename T>
+struct DynamicContainerHash {
+ size_t operator()(const T& value) const {
+ if constexpr (std::is_floating_point_v<T>) {
+ T normalized = value;
+ // The hash must collapse NaN payloads and signed zeros exactly as
Doris equality does.
+ NormalizeFloat(normalized);
+ return phmap::Hash<T> {}(normalized);
+ }
+ return phmap::Hash<T> {}(value);
+ }
+};
+
/**
* Dynamic Container uses phmap::flat_hash_set.
* @tparam T Element Type
@@ -146,7 +159,8 @@ template <typename T>
class DynamicContainer {
public:
using Self = DynamicContainer;
- using Iterator = typename flat_hash_set<T>::iterator;
+ using Set = flat_hash_set<T, DynamicContainerHash<T>>;
+ using Iterator = typename Set::iterator;
using ElementType = T;
DynamicContainer() = default;
@@ -167,7 +181,7 @@ public:
size_t size() const { return _set.size(); }
private:
- flat_hash_set<T> _set;
+ Set _set;
};
// TODO Maybe change void* parameter to template parameter better.
diff --git a/be/src/exprs/vdirect_in_predicate.h
b/be/src/exprs/vdirect_in_predicate.h
index b272c816edb..ace2ac513a2 100644
--- a/be/src/exprs/vdirect_in_predicate.h
+++ b/be/src/exprs/vdirect_in_predicate.h
@@ -42,6 +42,7 @@ class VDirectInPredicate final : public VExpr {
std::once_flag materialize_once;
Status materialization_status;
bool zonemap_materialized = false;
+ bool seg_filter_contains_nan = false;
std::vector<Field> seg_filter_values;
Field seg_filter_min;
Field seg_filter_max;
@@ -102,7 +103,8 @@ public:
ZoneMapFilterResult evaluate_zonemap_filter(const ZoneMapEvalContext& ctx)
const override {
return expr_zonemap::eval_in_zonemap(
ctx, get_child(0), false, _pruning_state->seg_filter_values,
- _pruning_state->seg_filter_min,
_pruning_state->seg_filter_max);
+ _pruning_state->seg_filter_contains_nan,
_pruning_state->seg_filter_min,
+ _pruning_state->seg_filter_max);
}
bool can_evaluate_zonemap_filter() const override {
@@ -332,6 +334,7 @@ private:
return;
}
pruning_state->seg_filter_values = std::move(materialized.values);
+ pruning_state->seg_filter_contains_nan = materialized.contains_nan;
pruning_state->seg_filter_min = std::move(materialized.min_value);
pruning_state->seg_filter_max = std::move(materialized.max_value);
pruning_state->zonemap_materialized = true;
diff --git a/be/src/exprs/vin_predicate.cpp b/be/src/exprs/vin_predicate.cpp
index 00f355add51..b5151e9c656 100644
--- a/be/src/exprs/vin_predicate.cpp
+++ b/be/src/exprs/vin_predicate.cpp
@@ -158,6 +158,7 @@ Status VInPredicate::evaluate_inverted_index(VExprContext*
context, uint32_t seg
Status VInPredicate::_materialize_for_zonemap_filter(VExprContext* context) {
_seg_filter_values.clear();
_seg_filter_contains_null = false;
+ _seg_filter_contains_nan = false;
_zonemap_materialized = false;
_direct_filter_set.reset();
if (_children.size() < 2 || !_children[0]->is_slot_ref()) {
@@ -184,6 +185,7 @@ Status
VInPredicate::_materialize_for_zonemap_filter(VExprContext* context) {
RETURN_IF_ERROR(expr_zonemap::materialize_hybrid_set_for_zonemap_filter(
*in_state->hybrid_set, data_type, &materialized));
_seg_filter_contains_null = materialized.contains_null;
+ _seg_filter_contains_nan = materialized.contains_nan;
_seg_filter_values = std::move(materialized.values);
_seg_filter_min = std::move(materialized.min_value);
_seg_filter_max = std::move(materialized.max_value);
@@ -196,7 +198,8 @@ ZoneMapFilterResult
VInPredicate::evaluate_zonemap_filter(const ZoneMapEvalConte
return ZoneMapFilterResult::kNoMatch;
}
return expr_zonemap::eval_in_zonemap(ctx, get_child(0), _is_not_in,
_seg_filter_values,
- _seg_filter_min, _seg_filter_max);
+ _seg_filter_contains_nan,
_seg_filter_min,
+ _seg_filter_max);
}
bool VInPredicate::can_evaluate_zonemap_filter() const {
@@ -218,7 +221,8 @@ ZoneMapFilterResult
VInPredicate::evaluate_bloom_filter(const BloomFilterEvalCon
}
bool VInPredicate::can_evaluate_bloom_filter() const {
- return _zonemap_materialized && !_is_not_in &&
+ // A NaN member forces conservative retention regardless of the remaining
finite probes.
+ return _zonemap_materialized && !_is_not_in && !_seg_filter_contains_nan &&
std::dynamic_pointer_cast<VSlotRef>(get_child(0)) != nullptr;
}
diff --git a/be/src/exprs/vin_predicate.h b/be/src/exprs/vin_predicate.h
index 245c1439be0..2fbbf8393f4 100644
--- a/be/src/exprs/vin_predicate.h
+++ b/be/src/exprs/vin_predicate.h
@@ -102,6 +102,7 @@ private:
bool _is_args_all_constant = false;
bool _zonemap_materialized = false;
bool _seg_filter_contains_null = false;
+ bool _seg_filter_contains_nan = false;
std::shared_ptr<HybridSetBase> _direct_filter_set;
std::vector<Field> _seg_filter_values;
Field _seg_filter_min;
diff --git a/be/src/format_v2/parquet/parquet_statistics.cpp
b/be/src/format_v2/parquet/parquet_statistics.cpp
index 63b880f7c5e..26b76ab4c9c 100644
--- a/be/src/format_v2/parquet/parquet_statistics.cpp
+++ b/be/src/format_v2/parquet/parquet_statistics.cpp
@@ -838,6 +838,9 @@ void add_slot_zonemap(ZoneMapEvalContext* ctx, int
slot_index, const DataTypePtr
ZoneMapEvalContext::SlotZoneMap slot_zone_map;
slot_zone_map.data_type = data_type;
slot_zone_map.zone_map = std::move(zone_map);
+ const auto primitive_type =
remove_nullable(data_type)->get_primitive_type();
+ slot_zone_map.floating_nan_count_unknown =
+ primitive_type == TYPE_FLOAT || primitive_type == TYPE_DOUBLE;
ctx->slots.emplace(slot_index, std::move(slot_zone_map));
}
diff --git a/be/src/storage/index/zone_map/zonemap_eval_context.cpp
b/be/src/storage/index/zone_map/zonemap_eval_context.cpp
index b65d4400e8a..2fb6864349c 100644
--- a/be/src/storage/index/zone_map/zonemap_eval_context.cpp
+++ b/be/src/storage/index/zone_map/zonemap_eval_context.cpp
@@ -37,6 +37,11 @@ DataTypePtr ZoneMapEvalContext::data_type(int slot_index)
const {
return it->second.data_type;
}
+bool ZoneMapEvalContext::floating_nan_count_unknown(int slot_index) const {
+ auto it = slots.find(slot_index);
+ return it != slots.end() && it->second.floating_nan_count_unknown;
+}
+
void ZoneMapEvalStats::merge_page_eval_stats(const ZoneMapEvalStats& src) {
// Page-level evaluation repeats the same conjuncts for many pages. Keep
structural
// diagnostics once per column, while operation counters still reflect
actual page checks.
diff --git a/be/src/storage/index/zone_map/zonemap_eval_context.h
b/be/src/storage/index/zone_map/zonemap_eval_context.h
index c5a750b0591..7c1afab8780 100644
--- a/be/src/storage/index/zone_map/zonemap_eval_context.h
+++ b/be/src/storage/index/zone_map/zonemap_eval_context.h
@@ -52,10 +52,13 @@ public:
struct SlotZoneMap {
DataTypePtr data_type;
std::shared_ptr<const segment_v2::ZoneMap> zone_map;
+ // Parquet min/max does not expose whether a floating chunk also
contains NaNs.
+ bool floating_nan_count_unknown = false;
};
std::shared_ptr<const segment_v2::ZoneMap> zone_map(int slot_index) const;
DataTypePtr data_type(int slot_index) const;
+ bool floating_nan_count_unknown(int slot_index) const;
phmap::flat_hash_map<int, SlotZoneMap> slots;
diff --git a/be/test/core/field_test.cpp b/be/test/core/field_test.cpp
index 694b292049c..efe94cef968 100644
--- a/be/test/core/field_test.cpp
+++ b/be/test/core/field_test.cpp
@@ -20,6 +20,7 @@
#include <gtest/gtest-message.h>
#include <gtest/gtest-test-part.h>
+#include <limits>
#include <string>
#include "core/column/column_string.h"
@@ -32,6 +33,16 @@
#include "gtest/gtest_pred_impl.h" // IWYU pragma: keep
namespace doris {
+TEST(VFieldTest, detects_floating_point_nan) {
+
EXPECT_TRUE(Field::create_field<TYPE_FLOAT>(std::numeric_limits<float>::quiet_NaN()).is_nan());
+ EXPECT_TRUE(
+
Field::create_field<TYPE_DOUBLE>(std::numeric_limits<double>::quiet_NaN()).is_nan());
+ EXPECT_FALSE(Field::create_field<TYPE_FLOAT>(0.0F).is_nan());
+ EXPECT_FALSE(Field::create_field<TYPE_DOUBLE>(-0.0).is_nan());
+ EXPECT_FALSE(Field::create_field<TYPE_INT>(0).is_nan());
+ EXPECT_FALSE(Field().is_nan());
+}
+
TEST(VFieldTest, field_string) {
Field f;
diff --git a/be/test/exprs/expr_zonemap_filter_test.cpp
b/be/test/exprs/expr_zonemap_filter_test.cpp
index 668a528b6a1..d5d8d62a100 100644
--- a/be/test/exprs/expr_zonemap_filter_test.cpp
+++ b/be/test/exprs/expr_zonemap_filter_test.cpp
@@ -20,6 +20,9 @@
#include <gtest/gtest.h>
#include <array>
+#include <bit>
+#include <cstdint>
+#include <limits>
#include <map>
#include <memory>
#include <mutex>
@@ -428,6 +431,193 @@ TEST(ExprZonemapFilterTest,
ComparisonDictionarySupportsTypedRangesWhileBloomUse
{string_slot,
make_string_literal("delta")}));
}
+TEST(ExprZonemapFilterTest, FloatingPointNanBloomProbeIsConservative) {
+ auto bloom_filter = std::make_unique<segment_v2::BlockSplitBloomFilter>();
+
ASSERT_TRUE(bloom_filter->init(segment_v2::BloomFilter::MINIMUM_BYTES).ok());
+ const double finite_value = 1.0;
+ bloom_filter->add_bytes(reinterpret_cast<const char*>(&finite_value),
sizeof(finite_value));
+
+ FunctionComparison<EqualsOp, NameEquals> equals;
+ const auto check_type = [&](const DataTypePtr& type, Field nan_field) {
+ auto slot = make_slot(0, type);
+ auto literal = std::make_shared<VLiteral>(create_texpr_node_from(
+ nan_field, remove_nullable(type)->get_primitive_type(), 0, 0));
+ auto bloom_ctx = make_bloom_filter_context(bloom_filter.get(), type);
+
+ EXPECT_FALSE(equals.can_evaluate_bloom_filter({slot, literal}));
+ EXPECT_EQ(ZoneMapFilterResult::kMayMatch,
+ equals.evaluate_bloom_filter(bloom_ctx, {slot, literal}));
+ EXPECT_EQ(ZoneMapFilterResult::kMayMatch,
+ expr_zonemap::eval_in_bloom_filter(bloom_ctx, slot, false,
{nan_field}));
+
+ const auto primitive_type =
remove_nullable(type)->get_primitive_type();
+ const Field absent_finite = primitive_type == TYPE_FLOAT
+ ?
Field::create_field<TYPE_FLOAT>(2.0F)
+ :
Field::create_field<TYPE_DOUBLE>(2.0);
+ auto finite_literal = std::make_shared<VLiteral>(
+ create_texpr_node_from(absent_finite, primitive_type, 0, 0));
+ EXPECT_TRUE(equals.can_evaluate_bloom_filter({slot, finite_literal}));
+ EXPECT_EQ(ZoneMapFilterResult::kNoMatch,
+ equals.evaluate_bloom_filter(bloom_ctx, {slot,
finite_literal}));
+ };
+
+ check_type(std::make_shared<DataTypeFloat32>(),
+
Field::create_field<TYPE_FLOAT>(std::numeric_limits<float>::quiet_NaN()));
+ check_type(std::make_shared<DataTypeFloat64>(),
+
Field::create_field<TYPE_DOUBLE>(std::numeric_limits<double>::quiet_NaN()));
+}
+
+TEST(ExprZonemapFilterTest, FloatingPointInWithNanIsNotBloomEligible) {
+ auto type = std::make_shared<DataTypeFloat64>();
+ auto predicate =
std::make_shared<VInPredicate>(make_in_predicate_node(false, 2));
+ predicate->add_child(make_slot(0, type));
+ predicate->_zonemap_materialized = true;
+ predicate->_seg_filter_contains_nan = true;
+ predicate->_seg_filter_values = {
+ Field::create_field<TYPE_DOUBLE>(1.0),
+
Field::create_field<TYPE_DOUBLE>(std::numeric_limits<double>::quiet_NaN())};
+
+ EXPECT_FALSE(predicate->can_evaluate_bloom_filter());
+ predicate->_seg_filter_contains_nan = false;
+ EXPECT_TRUE(predicate->can_evaluate_bloom_filter());
+}
+
+TEST(ExprZonemapFilterTest,
FloatingPointNanEqualityIgnoresFiniteOnlyRangeBounds) {
+ const auto check_type = []<PrimitiveType Type, typename DataType, typename
UInt>(
+ UInt nan_bits) {
+ using T = typename PrimitiveTypeTraits<Type>::CppType;
+ auto type = std::make_shared<DataType>();
+ auto slot = make_slot(0, type);
+ const auto nan_field =
Field::create_field<Type>(std::bit_cast<T>(nan_bits));
+ auto nan_literal =
+ std::make_shared<VLiteral>(create_texpr_node_from(nan_field,
Type, 0, 0));
+
+ segment_v2::ZoneMap zone_map;
+ zone_map.min_value = Field::create_field<Type>(T {0});
+ zone_map.max_value = Field::create_field<Type>(T {0});
+ zone_map.has_not_null = true;
+ auto ctx = make_context(std::move(zone_map), type);
+ ctx.slots.at(0).floating_nan_count_unknown = true;
+
+ FunctionComparison<EqualsOp, NameEquals> equals;
+ EXPECT_EQ(ZoneMapFilterResult::kUnsupported,
+ equals.evaluate_zonemap_filter(ctx, {slot, nan_literal}));
+
+ const auto finite_field = Field::create_field<Type>(T {10});
+ const auto zero_field = Field::create_field<Type>(T {0});
+ const auto one_field = Field::create_field<Type>(T {1});
+ auto zero_literal =
+ std::make_shared<VLiteral>(create_texpr_node_from(zero_field,
Type, 0, 0));
+ auto one_literal =
+ std::make_shared<VLiteral>(create_texpr_node_from(one_field,
Type, 0, 0));
+ FunctionComparison<NotEqualsOp, NameNotEquals> not_equals;
+ FunctionComparison<GreaterOp, NameGreater> greater;
+ FunctionComparison<GreaterOrEqualsOp, NameGreaterOrEquals>
greater_equal;
+ FunctionComparison<LessOp, NameLess> less;
+ FunctionComparison<LessOrEqualsOp, NameLessOrEquals> less_equal;
+ EXPECT_EQ(ZoneMapFilterResult::kUnsupported,
+ not_equals.evaluate_zonemap_filter(ctx, {slot,
zero_literal}));
+ EXPECT_EQ(ZoneMapFilterResult::kUnsupported,
+ greater.evaluate_zonemap_filter(ctx, {slot, one_literal}));
+ EXPECT_EQ(ZoneMapFilterResult::kUnsupported,
+ greater_equal.evaluate_zonemap_filter(ctx, {slot,
one_literal}));
+ EXPECT_EQ(ZoneMapFilterResult::kUnsupported,
+ less.evaluate_zonemap_filter(ctx, {one_literal, slot}));
+ EXPECT_EQ(ZoneMapFilterResult::kUnsupported,
+ less_equal.evaluate_zonemap_filter(ctx, {one_literal,
slot}));
+ EXPECT_EQ(ZoneMapFilterResult::kUnsupported,
+ expr_zonemap::eval_in_zonemap(ctx, slot, false,
{finite_field, nan_field}, true,
+ finite_field, nan_field));
+ EXPECT_EQ(ZoneMapFilterResult::kUnsupported,
+ expr_zonemap::eval_in_zonemap(ctx, slot, true, {zero_field},
false, zero_field,
+ zero_field));
+
+ segment_v2::ZoneMap all_null_zone_map;
+ all_null_zone_map.min_value = zero_field;
+ all_null_zone_map.max_value = zero_field;
+ auto all_null_ctx = make_context(std::move(all_null_zone_map), type);
+ all_null_ctx.slots.at(0).floating_nan_count_unknown = true;
+ EXPECT_EQ(
+ ZoneMapFilterResult::kNoMatch,
+ expr_zonemap::eval_in_zonemap(all_null_ctx, slot, false,
{finite_field, nan_field},
+ true, finite_field, nan_field));
+
+ ctx.slots.at(0).floating_nan_count_unknown = false;
+ EXPECT_EQ(ZoneMapFilterResult::kNoMatch,
+ equals.evaluate_zonemap_filter(ctx, {slot, nan_literal}));
+ EXPECT_EQ(ZoneMapFilterResult::kNoMatch,
+ expr_zonemap::eval_in_zonemap(ctx, slot, false,
{finite_field, nan_field}, true,
+ finite_field, nan_field));
+ };
+
+ check_type.template operator()<TYPE_FLOAT, DataTypeFloat32>(uint32_t
{0x7fc00002U});
+ check_type.template operator()<TYPE_DOUBLE, DataTypeFloat64>(uint64_t
{0x7ff8000000000002ULL});
+}
+
+TEST(ExprZonemapFilterTest, DirectInRawFixedKeepsEqualNanPayloadFromLargeSet) {
+ const auto check_type = []<PrimitiveType Type, typename DataType, typename
UInt>(
+ UInt stored_bits, UInt probe_bits) {
+ using T = typename PrimitiveTypeTraits<Type>::CppType;
+ auto type = std::make_shared<DataType>();
+ std::shared_ptr<HybridSetBase> filter(create_set(Type, false));
+ for (int value = 0; value < FIXED_CONTAINER_MAX_SIZE; ++value) {
+ T finite = static_cast<T>(value);
+ filter->insert(&finite);
+ }
+ const T stored_nan = std::bit_cast<T>(stored_bits);
+ filter->insert(&stored_nan);
+ ASSERT_EQ(FIXED_CONTAINER_MAX_SIZE + 1, filter->size());
+
+ VDirectInPredicate predicate(make_in_predicate_node(false, 1), filter,
true);
+ predicate.add_child(make_slot(0, type));
+ ASSERT_TRUE(predicate.can_execute_on_raw_fixed_values(type, 0));
+
+ const T probe_nan = std::bit_cast<T>(probe_bits);
+ uint8_t match = 1;
+ ASSERT_TRUE(
+ predicate
+ .execute_on_raw_fixed_values(reinterpret_cast<const
uint8_t*>(&probe_nan),
+ 1, sizeof(T), type, 0,
&match)
+ .ok());
+ EXPECT_EQ(1, match);
+ };
+
+ check_type.template operator()<TYPE_FLOAT, DataTypeFloat32>(uint32_t
{0x7fc00001U},
+ uint32_t
{0x7fc00002U});
+ check_type.template operator()<TYPE_DOUBLE, DataTypeFloat64>(uint64_t
{0x7ff8000000000001ULL},
+ uint64_t
{0x7ff8000000000002ULL});
+}
+
+TEST(ExprZonemapFilterTest,
FloatingPointSignedZeroBloomProbeChecksBothEncodings) {
+ FunctionComparison<EqualsOp, NameEquals> equals;
+ const auto check_type = [&]<PrimitiveType Type>(
+ const DataTypePtr& type,
+ typename
PrimitiveTypeTraits<Type>::CppType stored_value,
+ typename
PrimitiveTypeTraits<Type>::CppType predicate_value) {
+ auto bloom_filter =
std::make_unique<segment_v2::BlockSplitBloomFilter>();
+
ASSERT_TRUE(bloom_filter->init(segment_v2::BloomFilter::MINIMUM_BYTES).ok());
+ bloom_filter->add_bytes(reinterpret_cast<const char*>(&stored_value),
sizeof(stored_value));
+ ASSERT_FALSE(bloom_filter->test_bytes(reinterpret_cast<const
char*>(&predicate_value),
+ sizeof(predicate_value)));
+
+ auto slot = make_slot(0, type);
+ const auto field = Field::create_field<Type>(predicate_value);
+ auto literal =
std::make_shared<VLiteral>(create_texpr_node_from(field, Type, 0, 0));
+ auto bloom_ctx = make_bloom_filter_context(bloom_filter.get(), type);
+ EXPECT_EQ(ZoneMapFilterResult::kMayMatch,
+ equals.evaluate_bloom_filter(bloom_ctx, {slot, literal}));
+ EXPECT_EQ(ZoneMapFilterResult::kMayMatch,
+ expr_zonemap::eval_in_bloom_filter(bloom_ctx, slot, false,
{field}));
+ };
+
+ const auto float_type = std::make_shared<DataTypeFloat32>();
+ check_type.template operator()<TYPE_FLOAT>(float_type, -0.0F, 0.0F);
+ check_type.template operator()<TYPE_FLOAT>(float_type, 0.0F, -0.0F);
+ const auto double_type = std::make_shared<DataTypeFloat64>();
+ check_type.template operator()<TYPE_DOUBLE>(double_type, -0.0, 0.0);
+ check_type.template operator()<TYPE_DOUBLE>(double_type, 0.0, -0.0);
+}
+
TEST(ExprZonemapFilterTest,
DefaultFunctionForwardsDictionaryAndBloomEvaluation) {
auto type = int_type();
auto slot = make_slot(0, type);
@@ -479,7 +669,7 @@ TEST(ExprZonemapFilterTest,
MissingSlotTypeCountsUnsupportedZonemapEvalOnce) {
std::vector<Field> values {int_field(10)};
ZoneMapEvalContext in_ctx;
EXPECT_EQ(ZoneMapFilterResult::kUnsupported,
- expr_zonemap::eval_in_zonemap(in_ctx, slot, false, values,
int_field(10),
+ expr_zonemap::eval_in_zonemap(in_ctx, slot, false, values,
false, int_field(10),
int_field(10)));
EXPECT_EQ(1, in_ctx.stats.unusable_zonemap_eval_count);
}
@@ -542,20 +732,20 @@ TEST(ExprZonemapFilterTest,
InZonemapSkipsZonesWithoutNonNullValues) {
segment_v2::ZoneMap empty_zone;
auto empty_ctx = make_context(empty_zone, type);
EXPECT_EQ(ZoneMapFilterResult::kNoMatch,
- expr_zonemap::eval_in_zonemap(empty_ctx, slot, false, values,
int_field(10),
+ expr_zonemap::eval_in_zonemap(empty_ctx, slot, false, values,
false, int_field(10),
int_field(10)));
EXPECT_EQ(ZoneMapFilterResult::kNoMatch,
- expr_zonemap::eval_in_zonemap(empty_ctx, slot, true, values,
int_field(10),
+ expr_zonemap::eval_in_zonemap(empty_ctx, slot, true, values,
false, int_field(10),
int_field(10)));
segment_v2::ZoneMap only_null_zone;
only_null_zone.has_null = true;
auto only_null_ctx = make_context(only_null_zone, type);
EXPECT_EQ(ZoneMapFilterResult::kNoMatch,
- expr_zonemap::eval_in_zonemap(only_null_ctx, slot, false,
values, int_field(10),
- int_field(10)));
+ expr_zonemap::eval_in_zonemap(only_null_ctx, slot, false,
values, false,
+ int_field(10), int_field(10)));
EXPECT_EQ(ZoneMapFilterResult::kNoMatch,
- expr_zonemap::eval_in_zonemap(only_null_ctx, slot, true, values,
int_field(10),
+ expr_zonemap::eval_in_zonemap(only_null_ctx, slot, true, values,
false, int_field(10),
int_field(10)));
}
@@ -645,8 +835,9 @@ TEST(ExprZonemapFilterTest,
CharZonemapUsesTrimmedLogicalBounds) {
auto in_value = Field::create_field<TYPE_STRING>("gamma");
std::vector<Field> values {in_value};
auto in_ctx = make_context(zone_map, char_type);
- EXPECT_EQ(ZoneMapFilterResult::kNoMatch,
- expr_zonemap::eval_in_zonemap(in_ctx, slot, false, values,
in_value, in_value));
+ EXPECT_EQ(
+ ZoneMapFilterResult::kNoMatch,
+ expr_zonemap::eval_in_zonemap(in_ctx, slot, false, values, false,
in_value, in_value));
}
TEST(ExprZonemapFilterTest, InZonemapFallsBackToRangeWhenPointListIsLarge) {
@@ -659,7 +850,8 @@ TEST(ExprZonemapFilterTest,
InZonemapFallsBackToRangeWhenPointListIsLarge) {
values.emplace_back(int_field(value));
}
EXPECT_EQ(ZoneMapFilterResult::kMayMatch,
- expr_zonemap::eval_in_zonemap(ctx, slot, false, values,
int_field(1), int_field(65)));
+ expr_zonemap::eval_in_zonemap(ctx, slot, false, values, false,
int_field(1),
+ int_field(65)));
EXPECT_EQ(0, ctx.stats.in_zonemap_point_check_count);
EXPECT_EQ(1, ctx.stats.in_zonemap_range_only_count);
@@ -672,7 +864,8 @@ TEST(ExprZonemapFilterTest,
InZonemapUsesPointChecksUnderThreshold) {
std::vector<Field> values {int_field(1), int_field(30)};
EXPECT_EQ(ZoneMapFilterResult::kNoMatch,
- expr_zonemap::eval_in_zonemap(ctx, slot, false, values,
int_field(1), int_field(30)));
+ expr_zonemap::eval_in_zonemap(ctx, slot, false, values, false,
int_field(1),
+ int_field(30)));
EXPECT_EQ(1, ctx.stats.in_zonemap_point_check_count);
}
@@ -683,19 +876,19 @@ TEST(ExprZonemapFilterTest,
InZonemapHandlesEmptyListAndNotInSingleValueRange) {
std::vector<Field> empty_values;
EXPECT_EQ(ZoneMapFilterResult::kNoMatch,
- expr_zonemap::eval_in_zonemap(ctx, slot, false, empty_values,
{}, {}));
+ expr_zonemap::eval_in_zonemap(ctx, slot, false, empty_values,
false, {}, {}));
EXPECT_EQ(ZoneMapFilterResult::kMayMatch,
- expr_zonemap::eval_in_zonemap(ctx, slot, true, empty_values, {},
{}));
+ expr_zonemap::eval_in_zonemap(ctx, slot, true, empty_values,
false, {}, {}));
auto single_value_ctx = make_context(make_int_zonemap(10, 10), type);
std::vector<Field> values {int_field(10)};
EXPECT_EQ(ZoneMapFilterResult::kNoMatch,
- expr_zonemap::eval_in_zonemap(single_value_ctx, slot, true,
values, int_field(10),
- int_field(10)));
+ expr_zonemap::eval_in_zonemap(single_value_ctx, slot, true,
values, false,
+ int_field(10), int_field(10)));
std::vector<Field> other_values {int_field(11)};
EXPECT_EQ(ZoneMapFilterResult::kMayMatch,
- expr_zonemap::eval_in_zonemap(single_value_ctx, slot, true,
other_values,
+ expr_zonemap::eval_in_zonemap(single_value_ctx, slot, true,
other_values, false,
int_field(11), int_field(11)));
}
diff --git a/be/test/exprs/hybrid_set_test.cpp
b/be/test/exprs/hybrid_set_test.cpp
index 5f14dc924eb..9c7611d13b0 100644
--- a/be/test/exprs/hybrid_set_test.cpp
+++ b/be/test/exprs/hybrid_set_test.cpp
@@ -19,6 +19,8 @@
#include <gtest/gtest.h>
+#include <bit>
+#include <cstdint>
#include <memory>
#include <string>
@@ -391,6 +393,33 @@ TEST_F(HybridSetTest, double) {
a = 5.1;
EXPECT_FALSE(set->find(&a));
}
+
+TEST_F(HybridSetTest, DynamicFloatingSetFindsDorisEqualNanPayload) {
+ const auto check_type = []<PrimitiveType Type, typename UInt>(UInt
stored_bits,
+ UInt
probe_bits) {
+ using T = typename PrimitiveTypeTraits<Type>::CppType;
+ std::unique_ptr<HybridSetBase> set(create_set(Type, false));
+ for (int value = 0; value < FIXED_CONTAINER_MAX_SIZE; ++value) {
+ T finite = static_cast<T>(value);
+ set->insert(&finite);
+ }
+ const T stored_nan = std::bit_cast<T>(stored_bits);
+ set->insert(&stored_nan);
+ ASSERT_EQ(FIXED_CONTAINER_MAX_SIZE + 1, set->size());
+
+ const T probe_nan = std::bit_cast<T>(probe_bits);
+ EXPECT_TRUE(set->find(&probe_nan));
+ uint8_t match = 1;
+ set->find_batch_raw_fixed(reinterpret_cast<const
uint8_t*>(&probe_nan), 1, sizeof(T),
+ &match);
+ EXPECT_EQ(1, match);
+ };
+
+ check_type.template operator()<TYPE_FLOAT>(uint32_t {0x7fc00001U},
uint32_t {0x7fc00002U});
+ check_type.template operator()<TYPE_DOUBLE>(uint64_t
{0x7ff8000000000001ULL},
+ uint64_t
{0x7ff8000000000002ULL});
+}
+
TEST_F(HybridSetTest, string) {
std::unique_ptr<HybridSetBase> set(create_set(PrimitiveType::TYPE_VARCHAR,
false));
StringRef a;
diff --git a/be/test/format_v2/parquet/parquet_scan_test.cpp
b/be/test/format_v2/parquet/parquet_scan_test.cpp
index c3532c051f0..68775a74f20 100644
--- a/be/test/format_v2/parquet/parquet_scan_test.cpp
+++ b/be/test/format_v2/parquet/parquet_scan_test.cpp
@@ -25,6 +25,7 @@
#include <parquet/arrow/writer.h>
#include <parquet/encoding.h>
+#include <bit>
#include <cstring>
#include <filesystem>
#include <fstream>
@@ -66,6 +67,7 @@
#include "format_v2/file_reader.h"
#include "format_v2/parquet/parquet_column_schema.h"
#include "format_v2/parquet/parquet_reader.h"
+#include "format_v2/parquet/reader/native/block_split_bloom_filter.h"
#include "format_v2/parquet/reader/native_column_reader.h"
#include "gen_cpp/PlanNodes_types.h"
#include "gen_cpp/Types_types.h"
@@ -570,6 +572,47 @@ VExprContextSPtr create_string_in_conjunct(int column_id,
const std::vector<std:
return VExprContext::create_shared(std::move(root));
}
+DataTypePtr floating_data_type(PrimitiveType type) {
+ if (type == TYPE_FLOAT) {
+ return std::make_shared<DataTypeFloat32>();
+ }
+ DORIS_CHECK(type == TYPE_DOUBLE);
+ return std::make_shared<DataTypeFloat64>();
+}
+
+VExprContextSPtr create_floating_function_conjunct(int column_id,
PrimitiveType type,
+ const std::string&
function_name,
+ TExprOpcode::type opcode,
const Field& value) {
+ const auto data_type = floating_data_type(type);
+ auto root = create_binary_predicate(
+ function_name, opcode,
+ VSlotRef::create_shared(column_id, column_id, -1,
make_nullable(data_type),
+ "floating_key"),
+ VLiteral::create_shared(data_type, value));
+ return VExprContext::create_shared(std::move(root));
+}
+
+VExprContextSPtr create_floating_in_conjunct(int column_id, PrimitiveType type,
+ const std::vector<Field>& values,
bool is_not_in) {
+ const auto data_type = floating_data_type(type);
+ const auto result_type = make_nullable(std::make_shared<DataTypeUInt8>());
+ TExprNode node;
+ node.__set_node_type(TExprNodeType::IN_PRED);
+ node.__set_type(result_type->to_thrift());
+ node.__set_num_children(static_cast<int16_t>(values.size() + 1));
+ node.__set_is_nullable(true);
+ TInPredicate in_predicate;
+ in_predicate.__set_is_not_in(is_not_in);
+ node.__set_in_predicate(in_predicate);
+ auto root = VInPredicate::create_shared(node);
+ root->add_child(VSlotRef::create_shared(column_id, column_id, -1,
make_nullable(data_type),
+ "floating_key"));
+ for (const auto& value : values) {
+ root->add_child(VLiteral::create_shared(data_type, value));
+ }
+ return VExprContext::create_shared(std::move(root));
+}
+
VExprContextSPtr create_null_conjunct(int column_id, const DataTypePtr&
data_type, bool is_null) {
const auto nullable_type = make_nullable(remove_nullable(data_type));
const auto result_type = std::make_shared<DataTypeUInt8>();
@@ -1173,6 +1216,22 @@ std::shared_ptr<arrow::Array> build_int64_array(const
std::vector<int64_t>& valu
return finish_array(&builder);
}
+std::shared_ptr<arrow::Array> build_float_array(const std::vector<float>&
values) {
+ arrow::FloatBuilder builder;
+ for (const auto value : values) {
+ EXPECT_TRUE(builder.Append(value).ok());
+ }
+ return finish_array(&builder);
+}
+
+std::shared_ptr<arrow::Array> build_double_array(const std::vector<double>&
values) {
+ arrow::DoubleBuilder builder;
+ for (const auto value : values) {
+ EXPECT_TRUE(builder.Append(value).ok());
+ }
+ return finish_array(&builder);
+}
+
std::shared_ptr<arrow::Array> build_int8_array(const std::vector<int8_t>&
values) {
arrow::Int8Builder builder;
for (const auto value : values) {
@@ -1297,6 +1356,111 @@ void write_table(const std::string& file_path, const
std::shared_ptr<arrow::Tabl
row_group_size,
builder.build()));
}
+template <typename T>
+std::vector<uint8_t> build_parquet_bloom_filter(const T* values, size_t count)
{
+ format::parquet::native::BlockSplitBloomFilter bloom_filter;
+ DORIS_CHECK(bloom_filter
+ .init(segment_v2::BloomFilter::MINIMUM_BYTES,
+ segment_v2::HashStrategyPB::XX_HASH_64)
+ .ok());
+ for (size_t index = 0; index < count; ++index) {
+ bloom_filter.add_bytes(reinterpret_cast<const char*>(&values[index]),
sizeof(T));
+ }
+
+ tparquet::BloomFilterAlgorithm algorithm;
+ algorithm.__set_BLOCK(tparquet::SplitBlockAlgorithm());
+ tparquet::BloomFilterHash hash;
+ hash.__set_XXHASH(tparquet::XxHash());
+ tparquet::BloomFilterCompression compression;
+ compression.__set_UNCOMPRESSED(tparquet::Uncompressed());
+ tparquet::BloomFilterHeader header;
+ header.__set_numBytes(static_cast<int32_t>(bloom_filter.size()));
+ header.__set_algorithm(algorithm);
+ header.__set_hash(hash);
+ header.__set_compression(compression);
+
+ std::vector<uint8_t> bytes;
+ ThriftSerializer serializer(/*compact=*/true, 64);
+ DORIS_CHECK(serializer.serialize(&header, &bytes).ok());
+ bytes.insert(bytes.end(), bloom_filter.data(), bloom_filter.data() +
bloom_filter.size());
+ return bytes;
+}
+
+void append_floating_bloom_filters(const std::string& file_path,
+ const std::vector<float>& float_values,
+ const std::vector<double>& double_values) {
+ DORIS_CHECK(float_values.size() == double_values.size());
+ std::ifstream input(file_path, std::ios::binary | std::ios::ate);
+ DORIS_CHECK(input.good());
+ const auto input_size = static_cast<std::streamoff>(input.tellg());
+ DORIS_CHECK(input_size >= static_cast<std::streamoff>(8));
+ std::vector<uint8_t> file_bytes(cast_set<size_t>(input_size));
+ input.seekg(0);
+ input.read(reinterpret_cast<char*>(file_bytes.data()),
cast_set<std::streamsize>(input_size));
+ DORIS_CHECK(input.good());
+ DORIS_CHECK(memcmp(file_bytes.data() + file_bytes.size() - 4, "PAR1", 4)
== 0);
+
+ const uint32_t footer_size = decode_fixed32_le(file_bytes.data() +
file_bytes.size() - 8);
+ DORIS_CHECK(footer_size <= file_bytes.size() - 8);
+ const size_t footer_offset = file_bytes.size() - 8 - footer_size;
+ uint32_t thrift_size = footer_size;
+ tparquet::FileMetaData metadata;
+ DORIS_CHECK(
+ deserialize_thrift_msg(file_bytes.data() + footer_offset,
&thrift_size, true, &metadata)
+ .ok());
+
+ file_bytes.resize(footer_offset);
+ size_t first_row = 0;
+ for (auto& row_group : metadata.row_groups) {
+ const size_t row_count = cast_set<size_t>(row_group.num_rows);
+ DORIS_CHECK(row_group.columns.size() >= 2);
+ DORIS_CHECK(first_row + row_count <= float_values.size());
+ const auto append_column_bloom = [&]<typename T>(size_t column_id,
+ const std::vector<T>&
values) {
+ auto bytes = build_parquet_bloom_filter(values.data() + first_row,
row_count);
+ auto& column = row_group.columns[column_id].meta_data;
+
column.__set_bloom_filter_offset(cast_set<int64_t>(file_bytes.size()));
+ column.__set_bloom_filter_length(cast_set<int32_t>(bytes.size()));
+ file_bytes.insert(file_bytes.end(), bytes.begin(), bytes.end());
+ };
+ append_column_bloom(0, float_values);
+ append_column_bloom(1, double_values);
+ first_row += row_count;
+ }
+ DORIS_CHECK(first_row == float_values.size());
+
+ std::vector<uint8_t> footer;
+ ThriftSerializer serializer(/*compact=*/true, 1024);
+ DORIS_CHECK(serializer.serialize(&metadata, &footer).ok());
+ file_bytes.insert(file_bytes.end(), footer.begin(), footer.end());
+ std::array<uint8_t, sizeof(uint32_t)> encoded_footer_size {};
+ encode_fixed32_le(encoded_footer_size.data(),
cast_set<uint32_t>(footer.size()));
+ file_bytes.insert(file_bytes.end(), encoded_footer_size.begin(),
encoded_footer_size.end());
+ file_bytes.insert(file_bytes.end(), {'P', 'A', 'R', '1'});
+
+ std::ofstream output(file_path, std::ios::binary | std::ios::trunc);
+ output.write(reinterpret_cast<const char*>(file_bytes.data()),
file_bytes.size());
+ output.close();
+ DORIS_CHECK(output.good());
+}
+
+void write_floating_parquet_file_with_bloom_filters(const std::string&
file_path) {
+ const std::vector<float> float_values {
+ -1.0F, -0.0F, 1.0F, 0.5F, std::bit_cast<float>(uint32_t
{0x7fc00001U}),
+ -2.0F, 2.0F, 4.0F};
+ const std::vector<double> double_values {
+ -1.0, -0.0, 1.0, 0.5, std::bit_cast<double>(uint64_t
{0x7ff8000000000001ULL}),
+ -2.0, 2.0, 4.0};
+ auto schema = arrow::schema({arrow::field("float_value", arrow::float32(),
false),
+ arrow::field("double_value",
arrow::float64(), false),
+ arrow::field("id", arrow::int32(), false)});
+ auto table = arrow::Table::Make(
+ schema, {build_float_array(float_values),
build_double_array(double_values),
+ build_int32_array({0, 1, 2, 3, 4, 5, 6, 7})});
+ write_table(file_path, table, 4);
+ append_floating_bloom_filters(file_path, float_values, double_values);
+}
+
void write_required_adjusted_time_parquet_file(const std::string& file_path) {
auto file_result = arrow::io::FileOutputStream::Open(file_path);
ASSERT_TRUE(file_result.ok()) << file_result.status();
@@ -1781,6 +1945,83 @@ protected:
std::string _file_path;
};
+TEST_F(ParquetScanTest,
FloatingPredicatesPreserveDorisSemanticsWithRealBloomFilters) {
+ write_floating_parquet_file_with_bloom_filters(_file_path);
+
+ const auto expect_ids = [&](int column_id, const VExprContextSPtr&
conjunct,
+ const ColumnInt32::Container& expected,
+ bool expect_bloom_pruning = false) {
+ RuntimeProfile profile("profile");
+ auto reader = create_reader(0, -1, &profile);
+ TQueryOptions options;
+ options.__set_enable_parquet_filter_by_bloom_filter(true);
+ RuntimeState state {options, TQueryGlobals()};
+ ASSERT_TRUE(reader->init(&state).ok());
+
+ std::vector<format::ColumnDefinition> schema;
+ ASSERT_TRUE(reader->get_schema(&schema).ok());
+ auto request = std::make_shared<format::FileScanRequest>();
+ use_schema_order_positions(request.get(), schema);
+ format::FileScanRequestBuilder request_builder(request.get());
+
ASSERT_TRUE(request_builder.add_predicate_column(format::LocalColumnId(column_id)).ok());
+
ASSERT_TRUE(request_builder.add_non_predicate_column(format::LocalColumnId(2)).ok());
+
request->predicate_only_columns.push_back(format::LocalColumnId(column_id));
+ ASSERT_TRUE(conjunct->prepare(&state, RowDescriptor()).ok());
+ ASSERT_TRUE(conjunct->open(&state).ok());
+ request->conjuncts.push_back(conjunct);
+ ASSERT_TRUE(reader->open(request).ok());
+
+ ColumnInt32::Container actual;
+ bool eof = false;
+ while (!eof) {
+ Block block = build_file_block(schema);
+ size_t rows = 0;
+ ASSERT_TRUE(reader->get_block(&block, &rows, &eof).ok());
+ const auto& ids =
int32_data_column(*block.get_by_position(2).column).get_data();
+ actual.insert(actual.end(), ids.begin(), ids.end());
+ }
+ EXPECT_EQ(actual, expected);
+ if (expect_bloom_pruning) {
+ EXPECT_EQ(counter_value(profile,
"RowGroupsFilteredByBloomFilter"), 1);
+ }
+ conjunct->close();
+ };
+
+ const auto check_type = [&]<PrimitiveType Type>(int column_id) {
+ using CppType = typename PrimitiveTypeTraits<Type>::CppType;
+ const CppType query_nan = [] {
+ if constexpr (Type == TYPE_FLOAT) {
+ return std::bit_cast<float>(uint32_t {0x7fc00002U});
+ } else {
+ return std::bit_cast<double>(uint64_t {0x7ff8000000000002ULL});
+ }
+ }();
+ const auto nan = Field::create_field<Type>(query_nan);
+ const auto zero = Field::create_field<Type>(CppType {0});
+ const auto one = Field::create_field<Type>(CppType {1});
+ const auto absent = Field::create_field<Type>(CppType {10});
+
+ expect_ids(column_id,
+ create_floating_function_conjunct(column_id, Type, "eq",
TExprOpcode::EQ, nan),
+ {4});
+ expect_ids(column_id, create_floating_in_conjunct(column_id, Type,
{absent, nan}, false),
+ {4});
+ expect_ids(column_id, create_floating_in_conjunct(column_id, Type,
{zero}, true),
+ {0, 2, 3, 4, 5, 6, 7});
+ expect_ids(column_id,
+ create_floating_function_conjunct(column_id, Type, "eq",
TExprOpcode::EQ, zero),
+ {1}, true);
+ expect_ids(column_id,
+ create_floating_function_conjunct(column_id, Type, "gt",
TExprOpcode::GT, one),
+ {4, 6, 7});
+ };
+
+ // The file stores different NaN payloads and -0.0, so these scans
exercise both semantic
+ // equivalence classes through the real V2 footer-statistics and
Bloom-filter path.
+ check_type.template operator()<TYPE_FLOAT>(0);
+ check_type.template operator()<TYPE_DOUBLE>(1);
+}
+
TEST(ParquetScanSelectionTest, CompactFilterShrinksCurrentSelection) {
format::parquet::SelectionVector selection(4);
selection.set_index(0, 0);
diff --git a/be/test/format_v2/parquet/parquet_statistics_test.cpp
b/be/test/format_v2/parquet/parquet_statistics_test.cpp
index 8f7e8c2b285..47b56730bea 100644
--- a/be/test/format_v2/parquet/parquet_statistics_test.cpp
+++ b/be/test/format_v2/parquet/parquet_statistics_test.cpp
@@ -19,6 +19,7 @@
#include <gtest/gtest.h>
+#include <bit>
#include <cstdint>
#include <cstring>
#include <limits>
@@ -40,6 +41,7 @@
#include "core/data_type/data_type_variant_v2.h"
#include "core/field.h"
#include "exprs/expr_zonemap_filter.h"
+#include "exprs/function/functions_comparison.h"
#include "exprs/vcompound_pred.h"
#include "exprs/vexpr.h"
#include "exprs/vexpr_context.h"
@@ -115,6 +117,115 @@ private:
const std::string _expr_name = "BloomInExpr";
};
+class BloomEqExpr final : public VExpr {
+public:
+ BloomEqExpr(int column_id, DataTypePtr data_type, Field value)
+ : VExpr(std::make_shared<DataTypeUInt8>(), false),
+ _slot(VSlotRef::create_shared(0, column_id, -1,
std::move(data_type), "c0")),
+ _value(std::move(value)) {}
+
+ const std::string& expr_name() const override { return _expr_name; }
+ Status execute_column_impl(VExprContext*, const Block*, const Selector*,
size_t,
+ ColumnPtr&) const override {
+ return Status::InternalError("BloomEqExpr is only used by parquet
statistics tests");
+ }
+ bool can_evaluate_bloom_filter() const override { return true; }
+ ZoneMapFilterResult evaluate_bloom_filter(const BloomFilterEvalContext&
ctx) const override {
+ return expr_zonemap::eval_eq_bloom_filter(
+ ctx, expr_zonemap::SlotLiteral {.slot_index =
_slot->column_id(),
+ .slot_type =
_slot->data_type(),
+ .literal = _value,
+ .literal_type =
_slot->data_type(),
+ .literal_on_left = false});
+ }
+ void collect_slot_column_ids(std::set<int>& column_ids) const override {
+ _slot->collect_slot_column_ids(column_ids);
+ }
+
+private:
+ std::shared_ptr<VSlotRef> _slot;
+ Field _value;
+ const std::string _expr_name = "BloomEqExpr";
+};
+
+class MetadataFloatingEqualityExpr final : public VExpr {
+public:
+ enum class Mode { EQ, IN, NE, GT, GE, REVERSED_LT, REVERSED_LE, NOT_IN };
+
+ MetadataFloatingEqualityExpr(int column_id, DataTypePtr data_type, Field
nan_value, Mode mode)
+ : VExpr(std::make_shared<DataTypeUInt8>(), false),
+ _slot(VSlotRef::create_shared(0, column_id, -1, data_type,
"c0")),
+ _nan_literal(VLiteral::create_shared(create_texpr_node_from(
+ nan_value,
remove_nullable(data_type)->get_primitive_type(), 0, 0))),
+ _mode(mode),
+ _values {Field::create_field<TYPE_DOUBLE>(10.0),
std::move(nan_value)} {
+ const auto primitive_type =
remove_nullable(data_type)->get_primitive_type();
+ if (primitive_type == TYPE_FLOAT) {
+ _values[0] = Field::create_field<TYPE_FLOAT>(10.0F);
+ _zero_literal = VLiteral::create_shared(create_texpr_node_from(
+ Field::create_field<TYPE_FLOAT>(0.0F), TYPE_FLOAT, 0, 0));
+ _one_literal = VLiteral::create_shared(create_texpr_node_from(
+ Field::create_field<TYPE_FLOAT>(1.0F), TYPE_FLOAT, 0, 0));
+ _not_in_values = {Field::create_field<TYPE_FLOAT>(0.0F)};
+ } else {
+ _zero_literal = VLiteral::create_shared(create_texpr_node_from(
+ Field::create_field<TYPE_DOUBLE>(0.0), TYPE_DOUBLE, 0, 0));
+ _one_literal = VLiteral::create_shared(create_texpr_node_from(
+ Field::create_field<TYPE_DOUBLE>(1.0), TYPE_DOUBLE, 0, 0));
+ _not_in_values = {Field::create_field<TYPE_DOUBLE>(0.0)};
+ }
+ }
+
+ const std::string& expr_name() const override { return _expr_name; }
+ Status execute_column_impl(VExprContext*, const Block*, const Selector*,
size_t,
+ ColumnPtr&) const override {
+ return Status::InternalError("MetadataFloatingEqualityExpr is
metadata-only");
+ }
+ bool can_evaluate_zonemap_filter() const override { return true; }
+ ZoneMapFilterResult evaluate_zonemap_filter(const ZoneMapEvalContext& ctx)
const override {
+ switch (_mode) {
+ case Mode::EQ:
+ return comparison_zonemap_detail::evaluate(ctx, {_slot,
_nan_literal},
+
comparison_zonemap_detail::Op::EQ);
+ case Mode::IN:
+ return expr_zonemap::eval_in_zonemap(ctx, _slot, false, _values,
true, _values[0],
+ _values[1]);
+ case Mode::NE:
+ return comparison_zonemap_detail::evaluate(ctx, {_slot,
_zero_literal},
+
comparison_zonemap_detail::Op::NE);
+ case Mode::GT:
+ return comparison_zonemap_detail::evaluate(ctx, {_slot,
_one_literal},
+
comparison_zonemap_detail::Op::GT);
+ case Mode::GE:
+ return comparison_zonemap_detail::evaluate(ctx, {_slot,
_one_literal},
+
comparison_zonemap_detail::Op::GE);
+ case Mode::REVERSED_LT:
+ return comparison_zonemap_detail::evaluate(ctx, {_one_literal,
_slot},
+
comparison_zonemap_detail::Op::LT);
+ case Mode::REVERSED_LE:
+ return comparison_zonemap_detail::evaluate(ctx, {_one_literal,
_slot},
+
comparison_zonemap_detail::Op::LE);
+ case Mode::NOT_IN:
+ return expr_zonemap::eval_in_zonemap(ctx, _slot, true,
_not_in_values, false,
+ _not_in_values[0],
_not_in_values[0]);
+ }
+ __builtin_unreachable();
+ }
+ void collect_slot_column_ids(std::set<int>& column_ids) const override {
+ _slot->collect_slot_column_ids(column_ids);
+ }
+
+private:
+ VExprSPtr _slot;
+ VExprSPtr _nan_literal;
+ VExprSPtr _zero_literal;
+ VExprSPtr _one_literal;
+ Mode _mode;
+ std::vector<Field> _values;
+ std::vector<Field> _not_in_values;
+ const std::string _expr_name = "MetadataFloatingEqualityExpr";
+};
+
class DictionaryStringInExpr final : public VExpr {
public:
DictionaryStringInExpr() : VExpr(std::make_shared<DataTypeUInt8>(), false)
{}
@@ -340,6 +451,11 @@ VExprContextSPtrs bloom_conjuncts(DataTypePtr data_type,
std::vector<Field> valu
std::make_shared<BloomInExpr>(0, std::move(data_type),
std::move(values)))};
}
+VExprContextSPtrs bloom_eq_conjunct(DataTypePtr data_type, Field value) {
+ return {VExprContext::create_shared(
+ std::make_shared<BloomEqExpr>(0, std::move(data_type),
std::move(value)))};
+}
+
format::FileScanRequest request_with_bloom_conjunct(DataTypePtr data_type,
std::vector<Field> values)
{
format::FileScanRequest request;
@@ -376,6 +492,96 @@ TEST(NativeParquetStatisticsTest,
InvalidNullableDateBoundsDisableMinMax) {
EXPECT_FALSE(result.has_min_max);
}
+TEST(NativeParquetStatisticsTest,
FloatingNanEqualityKeepsFiniteOnlyFooterAndPageRanges) {
+ const auto check_type = []<PrimitiveType Type, typename DataType, typename
UInt>(
+ tparquet::Type::type physical_type, UInt
nan_bits) {
+ using T = typename PrimitiveTypeTraits<Type>::CppType;
+ auto column_schema =
std::make_unique<format::parquet::ParquetColumnSchema>();
+ column_schema->kind =
format::parquet::ParquetColumnSchemaKind::PRIMITIVE;
+ column_schema->local_id = 0;
+ column_schema->leaf_column_id = 0;
+ column_schema->type = std::make_shared<DataType>();
+ column_schema->type_descriptor.doris_type = column_schema->type;
+ column_schema->type_descriptor.physical_type = physical_type;
+ std::vector<std::unique_ptr<format::parquet::ParquetColumnSchema>>
schema;
+ schema.push_back(std::move(column_schema));
+
+ const T finite_bound = T {0};
+ const std::string encoded_bound(reinterpret_cast<const
char*>(&finite_bound), sizeof(T));
+ tparquet::Statistics statistics;
+ statistics.__set_min_value(encoded_bound);
+ statistics.__set_max_value(encoded_bound);
+ statistics.__set_null_count(0);
+ tparquet::ColumnMetaData column_metadata;
+ column_metadata.__set_type(physical_type);
+ column_metadata.__set_num_values(2);
+ column_metadata.__set_total_compressed_size(0);
+ column_metadata.__set_statistics(statistics);
+ tparquet::ColumnChunk chunk;
+ chunk.__set_meta_data(column_metadata);
+ tparquet::RowGroup row_group;
+ row_group.__set_columns({chunk});
+ row_group.__set_num_rows(2);
+ tparquet::ColumnOrder order;
+ order.__set_TYPE_ORDER(tparquet::TypeDefinedOrder());
+ tparquet::FileMetaData metadata;
+ metadata.__set_column_orders({order});
+ metadata.__set_row_groups({row_group});
+
+ format::parquet::NativeParquetPageIndex page_index;
+ page_index.column_index.__set_min_values({encoded_bound});
+ page_index.column_index.__set_max_values({encoded_bound});
+ page_index.column_index.__set_null_pages({false});
+ page_index.column_index.__set_null_counts({0});
+ tparquet::PageLocation location;
+ location.__set_offset(0);
+ location.__set_compressed_page_size(10);
+ location.__set_first_row_index(0);
+ page_index.offset_index.__set_page_locations({location});
+ std::unordered_map<int, format::parquet::NativeParquetPageIndex>
page_indexes;
+ page_indexes.emplace(0, std::move(page_index));
+
+ const auto nan_field =
Field::create_field<Type>(std::bit_cast<T>(nan_bits));
+ for (const auto mode :
+ {MetadataFloatingEqualityExpr::Mode::EQ,
MetadataFloatingEqualityExpr::Mode::IN,
+ MetadataFloatingEqualityExpr::Mode::NE,
MetadataFloatingEqualityExpr::Mode::GT,
+ MetadataFloatingEqualityExpr::Mode::GE,
+ MetadataFloatingEqualityExpr::Mode::REVERSED_LT,
+ MetadataFloatingEqualityExpr::Mode::REVERSED_LE,
+ MetadataFloatingEqualityExpr::Mode::NOT_IN}) {
+ format::FileScanRequest request;
+ request.local_positions.emplace(format::LocalColumnId(0),
format::LocalIndex(0));
+ request.predicate_columns = {
+
format::LocalColumnIndex::top_level(format::LocalColumnId(0))};
+ request.conjuncts = {
+
VExprContext::create_shared(std::make_shared<MetadataFloatingEqualityExpr>(
+ 0, schema[0]->type, nan_field, mode))};
+
+ std::vector<int> selected_row_groups;
+ ASSERT_TRUE(format::parquet::select_row_groups_by_metadata(
+ metadata, schema, request, nullptr,
&selected_row_groups, false,
+ nullptr)
+ .ok());
+ EXPECT_EQ(selected_row_groups, std::vector<int>({0}));
+
+ std::vector<format::parquet::RowRange> selected_ranges;
+ std::map<int, format::parquet::ParquetPageSkipPlan> skip_plans;
+
ASSERT_TRUE(format::parquet::select_row_group_ranges_by_native_page_index(
+ metadata, metadata.row_groups[0],
page_indexes, schema, request, 2,
+ &selected_ranges, &skip_plans, nullptr)
+ .ok());
+ ASSERT_EQ(1, selected_ranges.size());
+ EXPECT_EQ(0, selected_ranges[0].start);
+ EXPECT_EQ(2, selected_ranges[0].length);
+ }
+ };
+
+ check_type.template operator()<TYPE_FLOAT,
DataTypeFloat32>(tparquet::Type::FLOAT,
+ uint32_t
{0x7fc00002U});
+ check_type.template operator()<TYPE_DOUBLE,
DataTypeFloat64>(tparquet::Type::DOUBLE,
+ uint64_t
{0x7ff8000000000002ULL});
+}
+
TEST(NativeParquetStatisticsTest, InvalidNullableDecimalBoundsDisableMinMax) {
format::parquet::ParquetColumnSchema column_schema;
column_schema.type = make_nullable(std::make_shared<DataTypeDecimal32>(2,
0));
@@ -650,6 +856,130 @@ TEST(ParquetBloomFilterPruningTest,
NativeUint32BloomUsesPhysicalInt32Hash) {
bloom_filter));
}
+TEST(ParquetBloomFilterPruningTest, NativeFloatingBloomPreservesDorisEquality)
{
+ const auto check_type = []<PrimitiveType Type, typename DataType>(
+ tparquet::Type::type physical_type,
+ typename
PrimitiveTypeTraits<Type>::CppType stored_value,
+ typename
PrimitiveTypeTraits<Type>::CppType predicate_value) {
+ format::parquet::ParquetColumnSchema column_schema;
+ column_schema.type = std::make_shared<DataType>();
+ column_schema.type_descriptor.doris_type = column_schema.type;
+ column_schema.type_descriptor.physical_type = physical_type;
+
+ format::parquet::native::BlockSplitBloomFilter bloom_filter;
+ ASSERT_TRUE(bloom_filter
+ .init(segment_v2::BloomFilter::MINIMUM_BYTES,
+ segment_v2::HashStrategyPB::XX_HASH_64)
+ .ok());
+ bloom_filter.add_bytes(reinterpret_cast<const char*>(&stored_value),
sizeof(stored_value));
+ ASSERT_FALSE(bloom_filter.test_bytes(reinterpret_cast<const
char*>(&predicate_value),
+ sizeof(predicate_value)));
+ const auto field = Field::create_field<Type>(predicate_value);
+
+
EXPECT_FALSE(format::parquet::ParquetStatisticsUtils::NativeBloomFilterExcludes(
+ column_schema, 0, bloom_eq_conjunct(column_schema.type,
field), bloom_filter));
+
EXPECT_FALSE(format::parquet::ParquetStatisticsUtils::NativeBloomFilterExcludes(
+ column_schema, 0, bloom_conjuncts(column_schema.type,
{field}), bloom_filter));
+ };
+
+ check_type.template operator()<TYPE_FLOAT,
DataTypeFloat32>(tparquet::Type::FLOAT, -0.0F, 0.0F);
+ check_type.template operator()<TYPE_FLOAT,
DataTypeFloat32>(tparquet::Type::FLOAT, 0.0F, -0.0F);
+ check_type.template operator()<TYPE_DOUBLE,
DataTypeFloat64>(tparquet::Type::DOUBLE, -0.0, 0.0);
+ check_type.template operator()<TYPE_DOUBLE,
DataTypeFloat64>(tparquet::Type::DOUBLE, 0.0, -0.0);
+ check_type.template operator()<TYPE_FLOAT, DataTypeFloat32>(
+ tparquet::Type::FLOAT, std::bit_cast<float>(uint32_t
{0x7fc00001U}),
+ std::bit_cast<float>(uint32_t {0x7fc00002U}));
+ check_type.template operator()<TYPE_DOUBLE, DataTypeFloat64>(
+ tparquet::Type::DOUBLE, std::bit_cast<double>(uint64_t
{0x7ff8000000000001ULL}),
+ std::bit_cast<double>(uint64_t {0x7ff8000000000002ULL}));
+}
+
+TEST(ParquetBloomFilterPruningTest,
NativeRowGroupKeepsDorisEqualFloatingValues) {
+ const auto check_type = []<PrimitiveType Type, typename DataType>(
+ tparquet::Type::type physical_type,
+ typename
PrimitiveTypeTraits<Type>::CppType stored_value,
+ typename
PrimitiveTypeTraits<Type>::CppType predicate_value) {
+ format::parquet::native::BlockSplitBloomFilter bloom_filter;
+ ASSERT_TRUE(bloom_filter
+ .init(segment_v2::BloomFilter::MINIMUM_BYTES,
+ segment_v2::HashStrategyPB::XX_HASH_64)
+ .ok());
+ bloom_filter.add_bytes(reinterpret_cast<const char*>(&stored_value),
sizeof(stored_value));
+
+ tparquet::BloomFilterAlgorithm algorithm;
+ algorithm.__set_BLOCK(tparquet::SplitBlockAlgorithm());
+ tparquet::BloomFilterHash hash;
+ hash.__set_XXHASH(tparquet::XxHash());
+ tparquet::BloomFilterCompression compression;
+ compression.__set_UNCOMPRESSED(tparquet::Uncompressed());
+ tparquet::BloomFilterHeader bloom_header;
+ bloom_header.__set_numBytes(static_cast<int32_t>(bloom_filter.size()));
+ bloom_header.__set_algorithm(algorithm);
+ bloom_header.__set_hash(hash);
+ bloom_header.__set_compression(compression);
+ std::vector<uint8_t> bloom_bytes;
+ ThriftSerializer serializer(/*compact=*/true, 64);
+ ASSERT_TRUE(serializer.serialize(&bloom_header, &bloom_bytes).ok());
+ bloom_bytes.insert(bloom_bytes.end(), bloom_filter.data(),
+ bloom_filter.data() + bloom_filter.size());
+
+ tparquet::ColumnMetaData column_metadata;
+ column_metadata.__set_type(physical_type);
+ column_metadata.__set_codec(tparquet::CompressionCodec::UNCOMPRESSED);
+ column_metadata.__set_num_values(1);
+ column_metadata.__set_total_compressed_size(0);
+ column_metadata.__set_data_page_offset(0);
+ column_metadata.__set_bloom_filter_offset(0);
+
column_metadata.__set_bloom_filter_length(static_cast<int32_t>(bloom_bytes.size()));
+ tparquet::ColumnChunk chunk;
+ chunk.__set_meta_data(column_metadata);
+ tparquet::RowGroup row_group;
+ row_group.__set_columns({chunk});
+ row_group.__set_total_byte_size(0);
+ row_group.__set_num_rows(1);
+ tparquet::FileMetaData metadata;
+ metadata.__set_version(1);
+ metadata.__set_num_rows(1);
+ metadata.__set_row_groups({row_group});
+
+ const auto field = Field::create_field<Type>(predicate_value);
+ for (const bool use_eq : {true, false}) {
+ auto column_schema =
std::make_unique<format::parquet::ParquetColumnSchema>();
+ column_schema->type = std::make_shared<DataType>();
+ column_schema->type_descriptor.doris_type = column_schema->type;
+ column_schema->type_descriptor.physical_type = physical_type;
+ column_schema->local_id = 0;
+ column_schema->leaf_column_id = 0;
+
+ format::FileScanRequest request;
+ request.local_positions.emplace(format::LocalColumnId(0),
format::LocalIndex(0));
+ request.conjuncts = use_eq ?
bloom_eq_conjunct(column_schema->type, field)
+ : bloom_conjuncts(column_schema->type,
{field});
+ std::vector<std::unique_ptr<format::parquet::ParquetColumnSchema>>
schema;
+ schema.push_back(std::move(column_schema));
+ format::parquet::ParquetFileContext file_context;
+ file_context.native_file =
std::make_shared<StatisticsMemoryFileReader>(bloom_bytes);
+ std::vector<int> selected_row_groups;
+ format::parquet::ParquetPruningStats pruning_stats;
+ ASSERT_TRUE(format::parquet::select_row_groups_by_metadata(
+ metadata, schema, request, nullptr,
&selected_row_groups, true,
+ &pruning_stats, nullptr, nullptr,
&file_context)
+ .ok());
+ EXPECT_EQ(selected_row_groups, std::vector<int>({0}));
+ EXPECT_EQ(pruning_stats.filtered_row_groups_by_bloom_filter, 0);
+ }
+ };
+
+ check_type.template operator()<TYPE_FLOAT,
DataTypeFloat32>(tparquet::Type::FLOAT, -0.0F, 0.0F);
+ check_type.template operator()<TYPE_DOUBLE,
DataTypeFloat64>(tparquet::Type::DOUBLE, 0.0, -0.0);
+ check_type.template operator()<TYPE_FLOAT, DataTypeFloat32>(
+ tparquet::Type::FLOAT, std::bit_cast<float>(uint32_t
{0x7fc00001U}),
+ std::bit_cast<float>(uint32_t {0x7fc00002U}));
+ check_type.template operator()<TYPE_DOUBLE, DataTypeFloat64>(
+ tparquet::Type::DOUBLE, std::bit_cast<double>(uint64_t
{0x7ff8000000000001ULL}),
+ std::bit_cast<double>(uint64_t {0x7ff8000000000002ULL}));
+}
+
TEST(ParquetBloomFilterPruningTest,
NativeRowGroupKeepsPresentUint32AboveInt32Max) {
auto column_schema =
std::make_unique<format::parquet::ParquetColumnSchema>(uint32_parquet_bloom_schema());
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]