2525#include " iceberg/expression/expressions.h"
2626#include " iceberg/expression/literal.h"
2727#include " iceberg/result.h"
28+ #include " iceberg/schema.h"
2829#include " iceberg/transform.h"
2930#include " iceberg/type.h"
3031#include " iceberg/util/checked_cast.h"
3132#include " iceberg/util/formatter_internal.h"
3233#include " iceberg/util/macros.h"
34+ #include " iceberg/util/type_util.h"
3335
3436namespace iceberg {
3537
@@ -230,7 +232,7 @@ Result<std::shared_ptr<Expression>> UnboundPredicateImpl<B>::Bind(
230232 ICEBERG_ASSIGN_OR_RAISE (auto bound_term, BASE::term ()->Bind (schema, case_sensitive));
231233
232234 if (values_.empty ()) {
233- return BindUnaryOperation (std::move (bound_term));
235+ return BindUnaryOperation (schema, std::move (bound_term));
234236 }
235237
236238 if (BASE::op () == Expression::Operation::kIn ||
@@ -257,30 +259,63 @@ bool StartsWith(const Literal& lhs, const Literal& rhs) {
257259 return false ;
258260}
259261
262+ // / \brief Returns true if all ancestors of the field with the given ID are required.
263+ // /
264+ // / A required field can still produce null values if it is nested in an optional struct,
265+ // / list, or map.
266+ Result<bool > AllAncestorsAreRequired (const Schema& schema, int32_t field_id) {
267+ auto id_to_parent = IndexParents (schema);
268+ auto parent_it = id_to_parent.find (field_id);
269+ while (parent_it != id_to_parent.end ()) {
270+ ICEBERG_ASSIGN_OR_RAISE (auto parent, schema.FindFieldById (parent_it->second ));
271+ if (!parent.has_value ()) {
272+ return InvalidSchema (" Cannot find parent field {} of field {}" , parent_it->second ,
273+ field_id);
274+ }
275+ if (parent.value ().get ().optional ()) {
276+ return false ;
277+ }
278+ parent_it = id_to_parent.find (parent_it->second );
279+ }
280+ return true ;
281+ }
282+
260283} // namespace
261284
262285template <typename B>
263286Result<std::shared_ptr<Expression>> UnboundPredicateImpl<B>::BindUnaryOperation(
264- std::shared_ptr<B> bound_term) const {
287+ const Schema& schema, std::shared_ptr<B> bound_term) const {
265288 switch (BASE::op ()) {
266- case Expression::Operation::kIsNull :
289+ case Expression::Operation::kIsNull : {
267290 if (!bound_term->MayProduceNull ()) {
268- return Expressions::AlwaysFalse ();
291+ ICEBERG_ASSIGN_OR_RAISE (
292+ bool all_ancestors_required,
293+ AllAncestorsAreRequired (schema, bound_term->reference ()->field_id ()));
294+ if (all_ancestors_required) {
295+ return Expressions::AlwaysFalse ();
296+ }
269297 }
270298 if (bound_term->type ()->type_id () == TypeId::kUnknown ) {
271299 return Expressions::AlwaysTrue ();
272300 }
273301 return BoundUnaryPredicate::Make (Expression::Operation::kIsNull ,
274302 std::move (bound_term));
275- case Expression::Operation::kNotNull :
303+ }
304+ case Expression::Operation::kNotNull : {
276305 if (!bound_term->MayProduceNull ()) {
277- return Expressions::AlwaysTrue ();
306+ ICEBERG_ASSIGN_OR_RAISE (
307+ bool all_ancestors_required,
308+ AllAncestorsAreRequired (schema, bound_term->reference ()->field_id ()));
309+ if (all_ancestors_required) {
310+ return Expressions::AlwaysTrue ();
311+ }
278312 }
279313 if (bound_term->type ()->type_id () == TypeId::kUnknown ) {
280314 return Expressions::AlwaysFalse ();
281315 }
282316 return BoundUnaryPredicate::Make (Expression::Operation::kNotNull ,
283317 std::move (bound_term));
318+ }
284319 case Expression::Operation::kIsNan :
285320 case Expression::Operation::kNotNan :
286321 if (!IsFloatingType (bound_term->type ()->type_id ())) {
0 commit comments