Skip to content

API: Add Schema.isOptional() - #17513

Closed
nastra wants to merge 3 commits into
apache:mainfrom
nastra:typeutil-isnullable
Closed

API: Add Schema.isOptional()#17513
nastra wants to merge 3 commits into
apache:mainfrom
nastra:typeutil-isnullable

Conversation

@nastra

@nastra nastra commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Determining whether a field may contain nulls requires checking the field itself as well as every field that contains it. Expose this as a reusable utility so callers don't have to reimplement the ancestor walk.

I've pulled this out of c59ed0d and the plan is to use this functionality later in the stats evaluation

Comment thread api/src/main/java/org/apache/iceberg/types/TypeUtil.java Outdated
Comment thread api/src/test/java/org/apache/iceberg/types/TestTypeUtil.java Outdated
@nastra
nastra force-pushed the typeutil-isnullable branch from 859280a to cb45906 Compare August 4, 2026 13:43

@pvary pvary 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.

Left some minor suggestions for the comments, but otherwise LGTM

Comment thread api/src/test/java/org/apache/iceberg/types/TestTypeUtil.java Outdated
Comment thread api/src/test/java/org/apache/iceberg/types/TestTypeUtil.java Outdated
Comment thread api/src/test/java/org/apache/iceberg/types/TestTypeUtil.java Outdated
Comment thread api/src/test/java/org/apache/iceberg/types/TestTypeUtil.java Outdated
Comment thread api/src/test/java/org/apache/iceberg/types/TestTypeUtil.java Outdated
Comment thread api/src/test/java/org/apache/iceberg/types/TestTypeUtil.java Outdated
Comment thread api/src/test/java/org/apache/iceberg/types/TestTypeUtil.java Outdated
Comment thread api/src/test/java/org/apache/iceberg/types/TestTypeUtil.java Outdated
Comment thread api/src/test/java/org/apache/iceberg/types/TestTypeUtil.java Outdated
Comment thread api/src/test/java/org/apache/iceberg/types/TestTypeUtil.java Outdated

return field == null
|| field.isOptional()
|| ancestorFields(schema, fieldId).stream().anyMatch(Types.NestedField::isOptional);

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.

ancestorFields calls TypeUtil.indexParents for each invocation and ends up traverse the schema tree. I am wondering if worth caching the indexParents in Schema. Currently it's only used for identifier field in constructor and discarded right after

Map<Integer, Integer> idToParent = TypeUtil.indexParents(struct);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not caching is fine if we are doing it once per predicate. But if this is headed to the hot path in stats evaluation (which is O(files)), it is worth caching it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

we can defer caching to a separate PR. @dramaticlly would you be interested in adding that?

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.

I'm fine with deferring but I do agree with @anoopj and @dramaticlly that caching in this case is important especially if we're invoking this per entry. And I think this is actually another argument for maybe why this API should actually be on Schema itself since that idToParent state can be lazily kept there.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

given that multiple folks brought this up, I went ahead and moved this to Schema.isNullable() where we can cache the idToParent map

Comment thread api/src/test/java/org/apache/iceberg/types/TestTypeUtil.java Outdated
@nastra
nastra force-pushed the typeutil-isnullable branch from cb45906 to e56dd25 Compare August 5, 2026 04:49
Comment thread api/src/main/java/org/apache/iceberg/types/TypeUtil.java Outdated
Comment thread api/src/main/java/org/apache/iceberg/types/TypeUtil.java Outdated

return field == null
|| field.isOptional()
|| ancestorFields(schema, fieldId).stream().anyMatch(Types.NestedField::isOptional);

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.

I'm fine with deferring but I do agree with @anoopj and @dramaticlly that caching in this case is important especially if we're invoking this per entry. And I think this is actually another argument for maybe why this API should actually be on Schema itself since that idToParent state can be lazily kept there.

Determining whether a field may contain nulls requires checking the field
itself as well as every field that contains it. Expose this as a reusable
utility so callers don't have to reimplement the ancestor walk.
@nastra
nastra force-pushed the typeutil-isnullable branch from e56dd25 to afd57e4 Compare August 7, 2026 10:00
@nastra nastra changed the title API: Add TypeUtil.isNullable() API: Add Schema.isNullable() Aug 7, 2026
@nastra
nastra requested a review from amogh-jahagirdar August 7, 2026 10:08
@nastra
nastra force-pushed the typeutil-isnullable branch from afd57e4 to b0b7a9a Compare August 7, 2026 10:22

@singhpk234 singhpk234 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.

LGTM thanks @nastra !

Comment on lines +238 to +241
if (idToParent == null) {
this.idToParent = TypeUtil.indexParents(struct);
}
return idToParent;

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.

i wonder if we should synchronize this if this now part of a public which can be called concurrently ? wdyt

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I was mainly keeping it in-sync with all the other methods. I think it we want to synchronize, then we should probably do this across the board?

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.

Agree, we can do this in follow-up too / later, this looks great to me as is !

@nastra nastra changed the title API: Add Schema.isNullable() API: Add Schema.isOptional() Aug 12, 2026
assertThat(schema.isOptional(5)).isFalse();
assertThat(schema.isOptional(6)).isFalse();

// an optional field is nullable regardless of the fields that contain it

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.

nit: I think we renamed from isNullable to isOptional but some of the UT comment is lagging behind. Might consider rename as well, or better to inline using as(...)

* @return true if the field may be null, false if it cannot be null
* @throws IllegalArgumentException if the field is not present in this schema
*/
public boolean isOptional(int id) {

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.

I do not think this method should not be part of the public API. I pointed out a very similar problem on the other version of this method, here: https://github.com/apache/iceberg/pull/17413/changes#r3762320465

Whether a field is optional is clearly defined: there's a boolean flag that determines whether the field's value can be null. This method misuses the term "optional" to mean whether the field or any parent may be null. That difference is not clear and makes the term "optional" confusing. I also don't think that this gets any better by introducing a different "nullable" term.

My point on the other PR wasn't to move this method to the public API, it was that we should rename the utility method to be clear. To do that, I think we should switch to using required. If the field and all parents are required ("fully required" or "always present"?) that is the opposite of this method. Using a name like alwaysPresent combined with isRequired for the leaf makes the most sense to me, but we can come up with better ideas.

@rdblue rdblue 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.

I think that this PR should be closed in favor of a utility method in #17413.

This method does not need to be part of the public API and including it in this form makes the API harder to use because it is more confusing.

In addition, I don't think Schema is a good place for this. The calling code in #17413 converts a struct to a Schema every time because this uses an id to parent map. I think it's a bad practice to convert to a schema just to call a util method for a struct and would like to see a better implementation that can reuse an id to parent map.

Let's close this and continue the work on the other PR.

@nastra nastra closed this Aug 13, 2026
@nastra
nastra deleted the typeutil-isnullable branch August 13, 2026 06:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants