-
Notifications
You must be signed in to change notification settings - Fork 66
feat: Add DictionaryBatch write support to IPC encoder and writer #926
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
paleolimbot
merged 3 commits into
apache:main
from
niekverw:feat/dictionary-batch-encoding
Sep 8, 2026
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -317,6 +317,86 @@ ArrowErrorCode ArrowIpcWriterWriteArrayView(struct ArrowIpcWriter* writer, | |
| return NANOARROW_OK; | ||
| } | ||
|
|
||
| ArrowErrorCode ArrowIpcWriterWriteDictionaryBatch( | ||
| struct ArrowIpcWriter* writer, int64_t dictionary_id, char is_delta, | ||
| const struct ArrowArrayView* values_view, struct ArrowError* error) { | ||
| NANOARROW_DCHECK(writer != NULL && writer->private_data != NULL && values_view != NULL); | ||
| struct ArrowIpcWriterPrivate* private = | ||
| (struct ArrowIpcWriterPrivate*)writer->private_data; | ||
|
|
||
| // This check is intentionally minimal: we're allowed to write one dictionary | ||
| // batch per ID in a file but we would need to add bookkeeping to keep track | ||
| // of written IDs (and usefully a fingerprint or reference to the dictionary | ||
| // so we can check if we need to emit it again). | ||
| if (private->writing_file && | ||
| (is_delta || private->footer.dictionary_blocks.size_bytes != 0)) { | ||
| ArrowErrorSet(error, | ||
| "IPC file writing supports exactly one non-delta dictionary batch"); | ||
| return ENOTSUP; | ||
| } | ||
|
|
||
| NANOARROW_ASSERT_OK(ArrowBufferResize(&private->buffer, 0, 0)); | ||
| NANOARROW_ASSERT_OK(ArrowBufferResize(&private->body_buffer, 0, 0)); | ||
|
|
||
| NANOARROW_RETURN_NOT_OK(ArrowIpcEncoderEncodeSimpleDictionaryBatch( | ||
| &private->encoder, dictionary_id, is_delta, values_view, &private->body_buffer, | ||
| error)); | ||
| NANOARROW_RETURN_NOT_OK_WITH_ERROR( | ||
| ArrowIpcEncoderFinalizeBuffer(&private->encoder, /*encapsulate=*/1, | ||
| &private->buffer), | ||
| error); | ||
|
|
||
| if (private->writing_file) { | ||
| _NANOARROW_CHECK_RANGE(private->buffer.size_bytes, 0, INT32_MAX); | ||
| struct ArrowIpcFileBlock block = { | ||
| .offset = private->bytes_written, | ||
| .metadata_length = (int32_t) private->buffer.size_bytes, | ||
| .body_length = private->body_buffer.size_bytes, | ||
| }; | ||
| NANOARROW_RETURN_NOT_OK_WITH_ERROR( | ||
| ArrowBufferAppend(&private->footer.dictionary_blocks, &block, sizeof(block)), | ||
| error); | ||
| } | ||
| private->bytes_written += private->buffer.size_bytes; | ||
| private->bytes_written += private->body_buffer.size_bytes; | ||
|
|
||
| NANOARROW_RETURN_NOT_OK(ArrowIpcOutputStreamWrite( | ||
| &private->output_stream, ArrowBufferToBufferView(&private->buffer), error)); | ||
| NANOARROW_RETURN_NOT_OK(ArrowIpcOutputStreamWrite( | ||
| &private->output_stream, ArrowBufferToBufferView(&private->body_buffer), error)); | ||
| return NANOARROW_OK; | ||
| } | ||
|
|
||
| // Walk the array in the same depth-first order the schema encoder uses to assign | ||
| // dictionary ids (see ArrowIpcDictionaryEncodingsAppendSchema): a dictionary-encoded | ||
| // node claims the next id before descending into its children and then its values. | ||
| // Emitting a full (non-delta) DictionaryBatch for each dictionary before every | ||
| // RecordBatch keeps each batch's indices valid against the dictionary that precedes | ||
| // it, which is required because each array in the stream carries its own dictionary. | ||
| // In the future we can reduce the number of dictionaries emitted by checking for | ||
| // identical dictionary arrays. | ||
| static ArrowErrorCode ArrowIpcWriterWriteDictionariesForArrayView( | ||
| struct ArrowIpcWriter* writer, const struct ArrowArrayView* array_view, | ||
| int64_t* next_id, struct ArrowError* error) { | ||
| if (array_view->dictionary != NULL) { | ||
| int64_t dictionary_id = (*next_id)++; | ||
| NANOARROW_RETURN_NOT_OK(ArrowIpcWriterWriteDictionaryBatch( | ||
| writer, dictionary_id, /*is_delta=*/0, array_view->dictionary, error)); | ||
| } | ||
|
|
||
| for (int64_t i = 0; i < array_view->n_children; i++) { | ||
| NANOARROW_RETURN_NOT_OK(ArrowIpcWriterWriteDictionariesForArrayView( | ||
| writer, array_view->children[i], next_id, error)); | ||
| } | ||
|
|
||
| if (array_view->dictionary != NULL) { | ||
| NANOARROW_RETURN_NOT_OK(ArrowIpcWriterWriteDictionariesForArrayView( | ||
| writer, array_view->dictionary, next_id, error)); | ||
| } | ||
|
Comment on lines
+392
to
+395
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The encoder rejects dictionary values that have a dictionary. Should this error instead? (Or if it works, can we add a test?) |
||
|
|
||
| return NANOARROW_OK; | ||
| } | ||
|
|
||
| static ArrowErrorCode ArrowIpcWriterWriteArrayStreamImpl( | ||
| struct ArrowIpcWriter* writer, struct ArrowArrayStream* in, | ||
| struct ArrowSchema* schema, struct ArrowArray* array, | ||
|
|
@@ -332,6 +412,11 @@ static ArrowErrorCode ArrowIpcWriterWriteArrayStreamImpl( | |
| } | ||
|
|
||
| NANOARROW_RETURN_NOT_OK(ArrowArrayViewSetArray(array_view, array, error)); | ||
|
|
||
| int64_t next_dictionary_id = 0; | ||
| NANOARROW_RETURN_NOT_OK(ArrowIpcWriterWriteDictionariesForArrayView( | ||
| writer, array_view, &next_dictionary_id, error)); | ||
|
|
||
| NANOARROW_RETURN_NOT_OK(ArrowIpcWriterWriteArrayView(writer, array_view, error)); | ||
| ArrowArrayRelease(array); | ||
| } | ||
|
|
||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a good scope for this PR, but we will want to ensure we have some way to avoid emitting duplicate dictionaries as tracked follow-up.