1616 */
1717#include " operators/functions/SparkExprToSubfieldFilterParser.h"
1818
19+ #include " utils/Exception.h"
20+ #include " velox/common/base/BloomFilter.h"
21+ #include " velox/functions/sparksql/XxHash64.h"
22+ #include " velox/expression/Expr.h"
23+ #include " velox/vector/ComplexVector.h"
24+
1925namespace gluten {
2026
2127using namespace facebook ::velox;
2228
2329namespace {
30+
31+ // Evaluates an expression as a constant. Returns nullptr if the expression is
32+ // not constant or evaluation fails. Errors are intentionally swallowed because
33+ // a non-evaluable expression simply means the filter cannot be pushed down.
34+ VectorPtr toConstant (const core::TypedExprPtr& expr, core::ExpressionEvaluator* evaluator) {
35+ auto exprSet = evaluator->compile (expr);
36+ if (!exprSet->exprs ()[0 ]->isConstantExpr ()) {
37+ return nullptr ;
38+ }
39+ RowVector input (evaluator->pool (), ROW ({}, {}), nullptr , 1 , std::vector<VectorPtr>{});
40+ SelectivityVector rows (1 );
41+ VectorPtr result;
42+ try {
43+ evaluator->evaluate (exprSet.get (), rows, input, result);
44+ } catch (const VeloxUserError&) {
45+ return nullptr ;
46+ }
47+ return result;
48+ }
49+
50+ // / Subfield filter backed by Velox's BloomFilter from bloom_filter_agg / might_contain.
51+ // / Values are hashed with Spark-compatible XXH64 using the seed extracted from
52+ // / xxhash64_with_seed, then re-hashed with folly hasher for bloom filter bucket
53+ // / selection, matching bloom_filter_agg's insertion path.
54+ class SparkMightContain final : public common::BigintValuesUsingBloomFilter {
55+ public:
56+ SparkMightContain (VectorPtr constantVector, bool nullAllowed, int64_t seed)
57+ : common::BigintValuesUsingBloomFilter(0 , nullAllowed),
58+ constantVector_ (std::move(constantVector)),
59+ seed_(seed) {
60+ auto sv = constantVector_->as <SimpleVector<StringView>>()->valueAt (0 );
61+ view_ = std::make_unique<BloomFilterView>(sv.data ());
62+ }
63+
64+ bool testInt64 (int64_t value) const override {
65+ return view_->mayContain (folly::hasher<int64_t >()(
66+ facebook::velox::functions::sparksql::XxHash64::hashInt64 (value, seed_)));
67+ }
68+
69+ bool testDouble (double value) const override {
70+ return view_->mayContain (folly::hasher<int64_t >()(
71+ facebook::velox::functions::sparksql::XxHash64::hashDouble (value, seed_)));
72+ }
73+
74+ bool testFloat (float value) const override {
75+ return view_->mayContain (folly::hasher<int64_t >()(
76+ facebook::velox::functions::sparksql::XxHash64::hashFloat (value, seed_)));
77+ }
78+
79+ bool testBytes (const char * data, int32_t len) const override {
80+ return view_->mayContain (folly::hasher<int64_t >()(
81+ facebook::velox::functions::sparksql::XxHash64::hashBytes (StringView (data, len), seed_)));
82+ }
83+
84+ bool testTimestamp (const Timestamp& value) const override {
85+ return view_->mayContain (folly::hasher<int64_t >()(
86+ facebook::velox::functions::sparksql::XxHash64::hashTimestamp (value, seed_)));
87+ }
88+
89+ bool testInt128 (const int128_t & value) const override {
90+ return view_->mayContain (folly::hasher<int64_t >()(
91+ facebook::velox::functions::sparksql::XxHash64::hashLongDecimal (value, seed_)));
92+ }
93+
94+ bool testInt64Range (int64_t /* min*/ , int64_t /* max*/ , bool /* hasNull*/ ) const override {
95+ return true ;
96+ }
97+
98+ std::unique_ptr<Filter> clone (std::optional<bool > nullAllowed) const override {
99+ return std::make_unique<SparkMightContain>(
100+ constantVector_, nullAllowed.value_or (nullAllowed_), seed_);
101+ }
102+
103+ bool testingEquals (const Filter& other) const override {
104+ return dynamic_cast <const SparkMightContain*>(&other) != nullptr ;
105+ }
106+
107+ folly::dynamic serialize () const override {
108+ VELOX_UNSUPPORTED (" Serialization is not supported for SparkMightContain" );
109+ }
110+
111+ private:
112+ VectorPtr constantVector_;
113+ std::unique_ptr<BloomFilterView> view_;
114+ int64_t seed_;
115+ };
116+
24117std::optional<std::pair<facebook::velox::common::Subfield, std::unique_ptr<facebook::velox::common::Filter>>> combine (
25118 facebook::velox::common::Subfield& subfield,
26119 std::unique_ptr<facebook::velox::common::Filter>& filter) {
@@ -30,6 +123,7 @@ std::optional<std::pair<facebook::velox::common::Subfield, std::unique_ptr<faceb
30123
31124 return std::nullopt ;
32125}
126+
33127} // namespace
34128
35129std::optional<std::pair<facebook::velox::common::Subfield, std::unique_ptr<facebook::velox::common::Filter>>>
@@ -93,6 +187,34 @@ SparkExprToSubfieldFilterParser::leafCallToSubfieldFilter(
93187 }
94188 return std::make_pair (std::move (subfield), facebook::velox::exec::isNotNull ());
95189 }
190+ } else if (call.name () == " might_contain" && !negated) {
191+ // Matches: might_contain(bloomFilter, xxhash64_with_seed(seed, field)).
192+ GLUTEN_CHECK (call.inputs ().size () == 2 ,
193+ " might_contain expects 2 arguments: bloomFilter and xxhash64_with_seed(seed, field)" );
194+ const auto * hashCall =
195+ dynamic_cast <const core::CallTypedExpr*>(call.inputs ()[1 ].get ());
196+ if (hashCall && hashCall->name () == " xxhash64_with_seed" ) {
197+ GLUTEN_CHECK (hashCall->inputs ().size () == 2 , " xxhash64_with_seed expects 2 arguments" );
198+ auto seedValue = toConstant (hashCall->inputs ()[0 ], evaluator);
199+ if (!seedValue || seedValue->isNullAt (0 )) {
200+ LOG (WARNING ) << " might_contain: seed value is null or not constant, "
201+ << " cannot push down to subfield filter" ;
202+ return std::nullopt ;
203+ }
204+ auto seed = seedValue->as <SimpleVector<int64_t >>()->valueAt (0 );
205+ if (!toSubfield (hashCall->inputs ()[1 ].get (), subfield)) {
206+ LOG (WARNING ) << " might_contain: second argument to xxhash64_with_seed "
207+ << " is not a subfield, cannot push down to subfield filter" ;
208+ return std::nullopt ;
209+ }
210+ auto bloomFilterValue = toConstant (call.inputs ()[0 ], evaluator);
211+ if (bloomFilterValue && !bloomFilterValue->isNullAt (0 )) {
212+ std::unique_ptr<common::Filter> filter =
213+ std::make_unique<SparkMightContain>(bloomFilterValue, false /* nullAllowed*/ , seed);
214+ return combine (subfield, filter);
215+ }
216+ }
217+ LOG (WARNING ) << " might_contain could not be converted to a subfield filter" ;
96218 }
97219 return std::nullopt ;
98220}
0 commit comments