feat: add predicate module with Literal, LeafPredicate, and CompoundPredicate by lxy-9602 · Pull Request #27 · apache/paimon-cpp · GitHub
Skip to content

feat: add predicate module with Literal, LeafPredicate, and CompoundPredicate - #27

Merged
leaves12138 merged 3 commits into
apache:mainfrom
lxy-9602:add-predicate
May 29, 2026
Merged

feat: add predicate module with Literal, LeafPredicate, and CompoundPredicate#27
leaves12138 merged 3 commits into
apache:mainfrom
lxy-9602:add-predicate

Conversation

@lxy-9602

Copy link
Copy Markdown
Member

Purpose

No Linked issue.

Introduce the core predicate framework:

  • Predicate — base interface for row-level filter predicates (predicate.h)
  • LeafPredicate — single-field predicate with typed function evaluation (leaf_predicate.h/cpp)
  • CompoundPredicate — And/Or composition of child predicates (compound_predicate.h/cpp)
  • Function / FunctionVisitor — base function hierarchy and visitor pattern (function.h, function_visitor.h)
  • Literal — type-erased value holder supporting all Paimon data types with comparison and serialization (literal.h/cpp)
  • LiteralConverter — Arrow array to Literal type conversion (literal_converter.h/cpp)
  • Function implementations: LeafFunction, LeafUnaryFunction, NullFalseLeafBinaryFunction, StringLeafBinaryFunction, MultiLiteralsLeafFunction, CompoundFunction
  • LeafPredicateImpl / CompoundPredicateImpl — internal predicate evaluation logic
  • PredicateFilter — row filtering via predicates

Tests

  • literal_test.cpp — Literal value construction, comparison, and serialization for all data types
  • literal_converter_test.cpp — Arrow array to Literal conversion for primitive, string, decimal, and temporal types

API and Format

New public headers under include/paimon/predicate/.

Documentation

Generative AI tooling

Migrate-by: Aone Copilot (Claude)

…redicate

Migrate the core predicate framework from the Alibaba repository:

Public headers (include/paimon/predicate/):
- predicate.h: base Predicate interface
- leaf_predicate.h: leaf predicate for single-field conditions
- compound_predicate.h: And/Or compound predicates
- function.h: LeafFunction/CompoundFunction base
- function_visitor.h: visitor pattern for predicate functions
- literal.h: type-erased Literal value holder

Internal implementation (src/paimon/common/predicate/):
- Function hierarchy: leaf_function, leaf_unary_function,
  null_false_leaf_binary_function, string_leaf_binary_function,
  multi_literals_leaf_function, compound_function
- Predicate impls: leaf_predicate_impl, compound_predicate_impl
- predicate_filter.h: row filtering via predicates
- literal.cpp: Literal construction/comparison/serialization
- literal_converter.h/.cpp: Arrow-to-Literal type conversion
- leaf_predicate.cpp, compound_predicate.cpp

Tests:
- literal_test.cpp: Literal value construction and comparison
- literal_converter_test.cpp: Arrow array to Literal conversion

Migrate-by: Aone Copilot (Claude)

@leaves12138 leaves12138 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR. I think this needs changes before it can be merged into main.

The new predicate sources currently depend on several project headers/classes that are not present in this PR or in the PR base branch, so the PR cannot compile as a standalone change against main. For example:

  • src/paimon/common/predicate/literal.cpp includes paimon/common/utils/field_type_utils.h and paimon/common/utils/fields_comparator.h.
  • src/paimon/common/predicate/literal_converter.cpp includes paimon/common/utils/date_time_utils.h, paimon/common/utils/field_type_utils.h, and paimon/common/utils/string_utils.h.
  • src/paimon/common/predicate/literal_converter_test.cpp includes paimon/common/data/binary_row.h, paimon/common/utils/decimal_utils.h, and paimon/testing/utils/binary_row_generator.h.
  • src/paimon/common/predicate/literal_test.cpp includes paimon/common/utils/decimal_utils.h.

Please either add/migrate these missing dependencies in this PR, or retarget/rebase this PR on the dependency PR/branch that introduces them. After the PR becomes buildable on its target branch, I can continue with a deeper review of the predicate logic.

@leaves12138 leaves12138 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the clarification. I re-reviewed the current predicate code while not treating the missing dependency files as a blocker in this pass. I still think this needs changes before merge because of the implementation issues below.

  1. Literal equality is inconsistent with std::hash<Literal> / HashCode().
    operator== treats FLOAT/DOUBLE values as equal with an absolute epsilon, while HashCode() hashes the exact floating-point value. Also, decimal equality delegates to Decimal::CompareTo(), so decimals with different scales can compare equal numerically, but HashCode() hashes the unscaled bits and scale. This violates the requirement that equal keys have equal hashes and can break std::unordered_* users of the provided std::hash<Literal> specialization. Please either make equality exact for these types or normalize the hash to match the equality semantics.

  2. LiteralConverter::GetLiteralFromDecimalArray() reconstructs negative decimal values by left-shifting decimal.high_bits() after casting it to signed __int128_t. Left-shifting a negative signed value is undefined behavior in C++, so negative Decimal128 values can hit UB. Please compose the 128-bit value through an unsigned type first and cast to Decimal::int128_t after the bits are assembled.

  3. NullFalseLeafBinaryFunction only rejects literals.size() < 1, then silently ignores all literals after literals[0]. Binary predicates such as equal/less-than/starts-with should require exactly one literal; otherwise malformed predicates with extra literals evaluate against the first literal and can return incorrect filter results. Please change these checks to require literals.size() == 1.

  4. LeafPredicateImpl does not validate negative field indexes, and the stats overload does not validate the index against min_values, max_values, or null_counts before accessing them. A negative field_index_ passes the current upper-bound checks, and out-of-range stats indexes can be read directly. Please validate field_index_ >= 0 and the relevant field counts before all accesses, ideally at construction or consistently in each Test() overload.

@leaves12138
leaves12138 dismissed stale reviews from themself May 28, 2026 08:07

Superseded by a corrected re-review after the latest PR update.

@leaves12138 leaves12138 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update. I rechecked the latest PR head and agree that the previous comments about extra literal count and defensive field-index checks should not block this PR. I also verified that the Decimal128 reconstruction issue has been fixed.

There is still one correctness issue left in the Literal equality/hash contract:

Literal::operator== now delegates to CompareTo(). For decimals, Decimal::CompareTo() treats numerically equal values with different scales as equal, for example Decimal(23, 3, 100000) and Decimal(23, 0, 100). However, Literal::HashCode() still hashes the raw decimal high bits, low bits, and scale, so those two equal Literals can produce different hashes. This violates the requirement for std::hash<Literal> that equal keys have equal hash values and can break std::unordered_* lookups.

Please either make decimal equality match the raw hash semantics, or normalize decimal hashing so it is consistent with Decimal::CompareTo() equality.

@leaves12138
leaves12138 dismissed their stale review May 28, 2026 08:31

Superseded after re-review: the fixed-schema decimal scale invariant is now documented and accepted.

@leaves12138 leaves12138 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the clarification and the added comment. Given the current Paimon usage invariant that decimal values stored in the same std::unordered_map<Literal, ...> come from the same column and therefore have fixed precision/scale, I agree the mixed-scale decimal hash/equality mismatch does not affect the current code path.

The Decimal128 reconstruction issue is fixed, and I do not have further blocking comments.

@leaves12138
leaves12138 merged commit 354bf18 into apache:main May 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants