Skip to content

GH-37072: [C++] MakeArrayOfNulls should respect Field::nullable - #38252

Closed
bkietz wants to merge 5 commits into
apache:mainfrom
bkietz:37072-make-array-of-nulls
Closed

bkietz wants to merge 5 commits into
apache:mainfrom
bkietz:37072-make-array-of-nulls

Conversation

@bkietz

@bkietz bkietz commented Oct 12, 2023 •

Copy link
Copy Markdown
Member

Rationale for this change

MakeArrayOfNulls didn't examine Field::nullable, so it could produce nested arrays whose children were null even if the schema said they couldn't have nulls. Additionally validation didn't look at Field::nullable so these malformed arrays don't fail tests.

Are these changes tested?

Yes, mostly by fixtures already in place.

Behavior

This PR includes breaking changes to public APIs.

The Field::nullable flag is now enforce in array validation, so now (for example) an array with nulls which corresponds to a non nullable field will fail validation.

@bkietz
bkietz requested a review from felipecrv October 12, 2023 20:58
@felipecrv

Copy link
Copy Markdown
Contributor

Many tests are failing now.

@bkietz
bkietz force-pushed the 37072-make-array-of-nulls branch from 4152f7a to 807b476 Compare October 13, 2023 17:35
@bkietz
bkietz requested a review from pitrou October 16, 2023 15:12
Comment thread cpp/src/arrow/extension_type.h Outdated
@github-actions github-actions Bot added awaiting changes Awaiting changes and removed awaiting committer review Awaiting committer review labels Oct 16, 2023

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.

Isn't a bit weird to have "Invalid: " twice in the message?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It is, but this is the format of other child array validation failures (since the parent array's error message contains the child array's). Possibly a follow up issue is in order to simplify those; I think it'd be most valuable to provide a field path to the child which failed validation and the "leaf" error message.

Comment thread cpp/src/arrow/array/util.cc Outdated

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.

Why delete this?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I didn't really delete it, just refactored its logic into NullArrayFactory. This way the code which requests preallocated zero buffer is in the same function as the code which uses the zero buffer, which seemed more clear to me.

@pitrou pitrou left a comment

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.

Some comments, questions and suggestions below.

The PR description should note that this is a behavior change.

Comment thread cpp/src/arrow/array/util.cc Outdated
Comment thread cpp/src/arrow/array/util.cc Outdated
Comment thread cpp/src/arrow/array/util.cc Outdated
Comment thread cpp/src/arrow/array/util.cc Outdated
Comment thread cpp/src/arrow/array/util.cc Outdated
Comment thread cpp/src/arrow/array/array_test.cc Outdated
Comment thread cpp/src/arrow/array/array_test.cc Outdated
Comment thread cpp/src/arrow/array/validate.cc Outdated
Comment thread cpp/src/arrow/array/validate.cc Outdated
Comment thread cpp/src/arrow/extension_type.h Outdated
@pitrou pitrou added the Breaking Change Includes a breaking change to the API label Oct 18, 2023
@github-actions github-actions Bot added awaiting change review Awaiting change review and removed awaiting changes Awaiting changes labels Oct 18, 2023
@bkietz
bkietz force-pushed the 37072-make-array-of-nulls branch from 2b2d779 to d73aa0b Compare November 16, 2023 14:28
@felipecrv

Copy link
Copy Markdown
Contributor

Getting merged and me rebasing the LIST_VIEW PR would be a good idea.

@bkietz
bkietz force-pushed the 37072-make-array-of-nulls branch from d73aa0b to b34ace5 Compare November 27, 2023 17:46
@bkietz
bkietz requested a review from mapleFU November 28, 2023 17:51
Comment thread cpp/src/arrow/array/validate.cc Outdated
Comment thread cpp/src/arrow/array/validate.cc

/// \brief The type of array used to represent this extension type's data
const std::shared_ptr<DataType>& storage_type() const { return storage_type_; }
std::shared_ptr<DataType> storage_type() const override { return storage_type_; }

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.

Why not also override storage_type_ref?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I intended to remove storage_type_ref altogether

Comment thread cpp/src/arrow/type.h
Comment thread cpp/src/arrow/array/util.cc Outdated
Comment thread cpp/src/arrow/array/util.cc Outdated
Comment thread cpp/src/arrow/array/util.cc
Comment thread cpp/src/arrow/array/array_test.cc Outdated
auto req = [](auto type) { return field("", std::move(type), /*nullable=*/false); };

// union with no nullable fields cannot represent a null
ASSERT_RAISES(Invalid, MakeArrayOfNull(dense_union({req(int8())}), length));

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.

Shouldn't this be successful if length is 0?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

If the type cannot support any number of nulls, I would say that the special case of zero length is not worth allowing

Comment thread cpp/src/arrow/array/array_test.cc
@github-actions github-actions Bot added awaiting changes Awaiting changes awaiting change review Awaiting change review and removed awaiting change review Awaiting change review awaiting changes Awaiting changes labels Dec 12, 2023
@github-actions github-actions Bot added the Status: stale-warning Issues and PRs flagged as stale which are due to be closed if no indication otherwise label Nov 18, 2025
@thisisnic

Copy link
Copy Markdown
Member

Thank you for your contribution. Unfortunately, this
pull request has been marked as stale because it has had no activity in the past 365 days. Please remove the stale label
or comment below, or this PR will be closed in 14 days. Feel free to re-open this if it has been closed in error. If you
do not have repository permissions to reopen the PR, please tag a maintainer.

@github-actions github-actions Bot removed the Status: stale-warning Issues and PRs flagged as stale which are due to be closed if no indication otherwise label Dec 25, 2025
@thisisnic
thisisnic force-pushed the 37072-make-array-of-nulls branch from ed3dd12 to 442342b Compare September 28, 2026 13:33
@thisisnic

thisisnic commented Sep 28, 2026 •

Copy link
Copy Markdown
Member

I'm going through old/abandoned PRs which would be advantageous to be able to merge and taking a look at ones which are mostly done and I can use AI to finish them off. This one looks mostly done so I did try to rebase and see how things look on CI. However, an AI review flagged up some missing things needed before merging, assuming the tests pass. Will paste below:


Rebase notes: conflicts were all mechanical. Upstream renamed HasValidityBitmap to may_have_validity_bitmap, moved logging.h and sort.h to logging_internal.h and sort_internal.h, and the parquet-testing submodule pointer had moved. storage_type_ref is gone as intended. Locally, arrow-array-test passes (1071 tests) and arrow-ipc-read-write-test passes apart from one test that needs a test-data file my checkout doesn't have, so unrelated. C++ format and lint hooks pass.

Findings from an AI review of the rebased diff. The first one I checked by hand and it's real. The rest I'm relaying as-is for whoever picks this up.

  1. validate.cc, RecurseInto: when a field is non-nullable, full validation is on, and the child's null_count is unknown, related_validator.GetNullCount() runs before related_validator.Validate(). GetNullCount() reads data.buffers[0] and calls CountSetBits over a bitmap whose size hasn't been checked yet, so a malformed child can read out of bounds. The existing comment in Validate() ("Check nulls after validating the buffer sizes") describes exactly this ordering. Fix is to call Validate() first and only then check the null count.

  2. validate.cc, RecurseInto: cheap Validate() now rejects children with nulls under a non-nullable field, not just ValidateFull(). That's a behaviour change visible to every binding, including IPC and Parquet reads that call Validate(), and there's no test asserting on the new "was non-nullable but had" error. Needs tests for struct, list, fixed-size list, union and run-end encoded children, and an explicit decision on whether cheap validation should enforce it.

  3. util.cc, NullArrayFactory::Visit(const UnionType&) and Visit(const RunEndEncodedType&): both ignore nullable_ and raise TypeError when no member is nullable, even when the union or REE array is itself a non-nullable child that never needs to hold a null. So MakeArrayOfNull(struct_({field("", dense_union({req(int8())}), false)})) fails although the struct's own bitmap would mask the child. When !nullable_ the nullable-member requirement can be skipped.

  4. util.cc, GetZeroBufferLength: the presizing pass wraps VisitTypeInline in DCHECK_OK, and the fallback Visit(const DataType&) returns NotImplemented, so an unsupported type now aborts in debug builds instead of returning a Status.

  5. type.h and extension_type.h: storage_type() becomes virtual and returns by value, adding a vtable slot to DataType and a shared_from_this lock on every RecurseIntoField. Calling it on a DataType not owned by a shared_ptr throws bad_weak_ptr.

  6. validate.cc, ValidateArrayImpl::GetNullCount: uses data.type->id() where neighbouring code uses storage_id(), so an extension type with a validity bitmap reports 0 nulls. The comment "Do not call GetNullCount()" now sits inside a method called GetNullCount and should say ArrayData::GetNullCount().

  7. validate.cc, RecurseInto: st.WithMessage(description..., " invalid: ", st.ToString()) embeds the status prefix, giving "invalid: Invalid: ...". Using st.message() fixes it. This is the doubled prefix felipecrv asked about in the still-open thread on array_run_end_test.cc.

  8. random.cc: the structured binding field in the struct case shadows the field parameter.

The one open design thread from the 2023 review is whether a zero-length array of a type that can't hold nulls should succeed (pitrou asked, bkietz preferred not to special-case it).

@thisisnic

Copy link
Copy Markdown
Member

Closing as stale, I don't have capacity to finish this off, but feel free to reopen if anyone is working on it.

@thisisnic thisisnic closed this Sep 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting change review Awaiting change review Breaking Change Includes a breaking change to the API Component: C++

Projects

None yet

Development

Successfully merging this pull request may close these issues.

null structarrays are poorly handled by cast

4 participants