Skip to content

feat: append array views to arrays - #930

Open
rustyconover wants to merge 7 commits into
apache:mainfrom
Query-farm:feat/array-view-appender
Open

feat: append array views to arrays#930
rustyconover wants to merge 7 commits into
apache:mainfrom
Query-farm:feat/array-view-appender

Conversation

@rustyconover

Copy link
Copy Markdown
Contributor

Summary

  • add ArrowArrayAppendArrayView() as a core array-building API
  • append primitive, nested, union, list-view, and run-end encoded values from an ArrowArrayView
  • preserve source slicing semantics and report unsupported sliced run-end encoded inputs
  • add direct native coverage for primitive, nested/sliced, union, and run-end encoded arrays

Context

This extracts the generic array-view appender from #928 as requested in review. It is independently useful and will be the prerequisite for a separate dictionary-delta decoding PR; #928 no longer contains the appender or decoder changes.

Validation

  • native CMake build passed
  • native CTest suite: 334/334 passed (4 optional codec tests skipped)

@codecov-commenter

codecov-commenter commented Sep 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 78.06%. Comparing base (f55f85b) to head (0603bc1).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #930      +/-   ##
==========================================
- Coverage   79.30%   78.06%   -1.24%     
==========================================
  Files         106      106              
  Lines       16223    16484     +261     
  Branches     1882     1884       +2     
==========================================
+ Hits        12865    12869       +4     
- Misses       2177     2434     +257     
  Partials     1181     1181              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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

Thank you!

Some things to take care of here but I think this is great functionality.

I can follow up with R/Python bindings, which are mostly useful because testing this stuff in C++ is a pain. I think this may also make a few of the R and Python constructors quite a bit simpler.

Comment thread src/nanoarrow/common/array.c Outdated
Comment on lines +221 to +224
ArrowErrorCode ArrowArrayAppendArrayView(struct ArrowArray* dst,
const struct ArrowArrayView* src,
struct ArrowError* error) {
if (src->storage_type == NANOARROW_TYPE_RUN_END_ENCODED) {

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.

At the top of this function we should sanity check that the append makes sense, because I am not sure that all combinations of spurious comparisons will generate a reasonable error here. ArrowArrayView only gives you storage type, so we probably should name the function accordingly (ArrowArrayAppendStorageFromArrayView()) to make it a bit more obvious to the caller that they're on their own with respect to whether they should do this or not.

Also a note that this needs to check src->dictionary, which I don't believe is reflected in the storage type (for better or worse).

@rustyconover rustyconover Sep 3, 2026

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.

Addressed in bab29ad. The API is now ArrowArrayAppendStorageFromArrayView, with recursive preflight checks for compatible storage, child layouts, fixed-size widths, and dictionary presence. Dictionary values are deliberately caller-managed: both source and destination must be dictionary-encoded, and the API docs state that values are not copied. Follow-up 693718d narrows fixed-size layout equality checks to fixed-size-to-fixed-size appends and covers fixed-to-variable binary/list conversion.

Comment thread src/nanoarrow/common/array.c Outdated
Comment on lines +225 to +228
if (src->offset != 0) {
ArrowErrorSet(error, "Can't append a sliced run-end encoded array view");
return ENOTSUP;
}

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.

No need to get too into the weeds supporting this one, but we do have a utility that I think can resolve the first run:

static inline int64_t ArrowResolveChunk32(int32_t index, const int32_t* offsets,
int32_t lo, int32_t hi) {

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.

Addressed in bab29ad. Sliced run-end encoded views now resolve the first run with ArrowResolveChunk32 or ArrowResolveChunk64 (with the int16 fallback), clip the boundary runs to the slice, and rewrite run ends relative to the destination.

Comment thread src/nanoarrow/common/array.c Outdated
Comment on lines +80 to +83
static ArrowErrorCode ArrowArrayAppendArrayViewElement(struct ArrowArray* dst,
const struct ArrowArrayView* src,
int64_t i,
struct ArrowError* error) {

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.

While we are here, I think it is worth the effort to special case primitive fixed-size appends of the same storage type (e.g., int32 getting appended to an int32). These should be much faster because they are just appending buffers to each other (the nulls bitmap is a bit more complex...may have to go through a uint8 buffer for the bitmap append if either the source or the target has any nulls).

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.

Addressed in 4974cd7. Matching byte-aligned primitive storage takes a bulk buffer-copy path. Validity is copied in bounded uint8 chunks so sliced bitmaps and all four source/destination bitmap-presence combinations are handled.

Comment thread src/nanoarrow/common/array.c Outdated
Comment on lines +232 to +234
NANOARROW_RETURN_NOT_OK(ArrowArrayAppendInt(
dst->children[0],
run_end_offset + ArrowArrayViewGetIntUnsafe(src->children[0], i)));

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.

These (and below) should use NANOARROW_RETURN_NOT_OK_WITH_ERROR()...we are in general very careful about ensuring that the contents of error are populated if an error code is returned.

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.

Addressed in bab29ad and refined in e56f47b. Direct append and buffer operations use NANOARROW_RETURN_NOT_OK_WITH_ERROR so failures populate the error; calls to helpers that already populate it use the plain propagation macro so their more specific message is preserved.

Comment thread src/nanoarrow/common/array_test.cc Outdated
Comment on lines +5091 to +5093
static ArrowErrorCode AppendArrayViewForTest(const struct ArrowArrayView* src,
struct ArrowArray* dst,
struct ArrowError* error) {

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.

In addition to these lovely nanoarrow only tests, an Arrow C++ gated test I think would help cover some of the things that can happen here in a way that can be parameterized to cover more cases. Perhaps one test per type family (unigned int, signed int, strings, binaries, lists), testing against the Arrow C++ builder for the matrix of source and destination types? Arrow C++ makes asserting the equality a bit easier too.

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.

Addressed in e56f47b. Added Arrow C++-gated source/destination matrices for signed integers, unsigned integers, string/large_string, binary/large_binary, and list/large_list, with Arrow C++ equality checks on the imported result.

Comment thread src/nanoarrow/common/array_test.cc Outdated
Comment on lines +5127 to +5130
TEST(ArrayTest, ArrayAppendArrayViewPrimitiveTypes) {
struct ArrowArray array;

ASSERT_EQ(ArrowArrayInitFromType(&array, NANOARROW_TYPE_INT64), NANOARROW_OK);

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 things that I am not sure are covered here are:

  • Appending an integer that is out of range to a narrower integer type (should pass on the error from the integer appender)
  • Appending too many elements to a narrower list type (you can use a list of Null type to avoid allocating huge buffers), which should pass on an error from the list element finisher
  • Dictionary encoded source or target

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.

Addressed in e56f47b. Added tests for integer narrowing overflow, int32 list-offset overflow using a Null child, successful dictionary storage copying with caller-managed dictionary values, and rejection when only one side is dictionary-encoded.

Add Arrow C++ conversion matrices plus sliced validity, overflow, dictionary, and sliced run-end tests. Preserve detailed errors produced by nested append helpers.
Avoid reading past short int8 or int32 input arrays when an append begins in a partially filled bitmap byte.
@rustyconover

Copy link
Copy Markdown
Contributor Author

CI follow-up: b4f4adc fixes the shared failure behind clang-tidy and the four Valgrind jobs. ArrowBitmapAppendInt8Unsafe could read more values than supplied when a short append began in a partially filled byte; the same issue existed in the int32 variant. Both helpers now bound the partial-byte copy, with regression tests for short unaligned appends. Native, Arrow C++, AddressSanitizer, and local clang-tidy checks pass.

Ensure clang-tidy can prove that all values passed through the bitmap packing path are initialized.
@rustyconover

Copy link
Copy Markdown
Contributor Author

clang-tidy follow-up: the Valgrind failures are resolved by b4f4adc, but clang-tidy 18 could not prove that ArrowBitsUnpackInt8 initialized every element later read from the scratch buffer. Commit 0603bc1 explicitly zero-initializes that buffer; this preserves behavior while guaranteeing analyzer-visible initialization.

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.

3 participants