Skip to content

fix: Concatenate null and dictionary arrays - #450

Open
CurtHagenlocher wants to merge 2 commits into
apache:mainfrom
CurtHagenlocher:concatenate-null-dictionary
Open

CurtHagenlocher wants to merge 2 commits into
apache:mainfrom
CurtHagenlocher:concatenate-null-dictionary

Conversation

@CurtHagenlocher

@CurtHagenlocher CurtHagenlocher commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

What's Changed

ArrayDataConcatenator (and so ArrowArrayConcatenator) couldn't handle two array types:

  • Null arrays had no visitor case and threw NotImplementedException, at the top level or as a child (e.g. a null-typed struct field, which Parquet readers hit for pyarrow pa.null() columns). They now concatenate to a null array of the combined length.
  • Dictionary arrays went through Visit(FixedWidthType) because DictionaryType derives from FixedWidthType. That concatenated the indices but dropped the dictionary, so ArrowArrayConcatenator threw Dictionary must not be null and ArrayDataConcatenator silently returned dictionary-typed data with no dictionary.

For dictionaries:

  • If every non-empty input shares the same dictionary (the same ArrayData, or distinct ArrayData over the same memory, e.g. after Retain/SliceShared), the indices are concatenated and that dictionary is kept.
  • Otherwise the dictionaries of the non-empty inputs are concatenated and each input's indices are shifted by the combined length of the dictionaries before it. Null slots get index 0 instead of a shifted, possibly out-of-range value. If the combined dictionary is too big for the index type, this throws OverflowException instead of wrapping around.
  • Inputs with different index types or value types are rejected with ArgumentException.
  • When different dictionaries are appended, the result is never marked ordered: nothing shows the appended entries are in order. When every input shares one dictionary, the result is ordered only if every input says so.

Dictionaries that are equal in content but stored separately take the concatenation path. The result is correct but not deduplicated. Unifying dictionaries could be added later if needed.

New tests cover null arrays, a struct with a null field, shared and different dictionaries (including nulls, slices and empty inputs), index overflow at the int8 boundary, the Ordered flag, and ArrayDataConcatenator keeping the dictionary.

Closes #446.

🤖 Generated with Claude Code

ArrayDataConcatenator had no case for NullType, so null arrays (and
null-typed children such as struct fields) threw NotImplementedException.
Dictionary arrays went through the fixed-width path, which concatenated
the indices but dropped the dictionary.

Null arrays now concatenate to a null array of the combined length.
Dictionary arrays keep the dictionary when every non-empty input shares
it; otherwise the dictionaries are concatenated and each input's indices
are shifted past the entries before it, with an OverflowException if the
combined dictionary can't be addressed by the index type.

Closes apache#446.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

The ordered-dictionary correctness issue and exceptional-path memory leak must be addressed before approval.

Review effort: Lite
Findings: 1 High severity

Open (1)
What changed in this PR

Fixes concatenation of null and dictionary arrays, including nested null fields and dictionary index remapping.

Changes:

  • Adds null-array concatenation.
  • Preserves shared dictionaries and combines distinct dictionaries with overflow checks.
  • Adds regression coverage for nulls, dictionaries, slicing, and overflow.
File Summary Findings
src/​Apache.Arrow/​Arrays/​ArrayDataConcatenator.cs Implements null and dictionary concatenation. Critical (2 votes): Combined dictionaries can incorrectly retain the first input’s Ordered flag. Moderate (1 vote): Validity buffers may leak when later validation or concatenation throws.
test/​Apache.Arrow.Tests/​ArrowArrayConcatenatorTests.cs Tests null, dictionary, slicing, and overflow behavior. Nit (1 vote): Add coverage for shared dictionary memory across distinct ArrayData instances and verify retained lifetime.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +150 to +154
var otherType = (DictionaryType)arrayData.DataType;
if (otherType.IndexType.TypeId != indexType.TypeId)
{
throw new ArgumentException(
$"Cannot concatenate dictionary arrays with different index types: {indexType.Name} vs {otherType.IndexType.Name}");

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.

Good catch, fixed in e2d1b28. When different dictionaries are appended, the result is now never ordered: even if every input is ordered, the appended entries aren't necessarily in order, and nothing verifies that. When every input shares one dictionary, nothing is appended, so the result is ordered only if every input says so. That commit also moves the dictionary concatenation ahead of the buffer allocations and releases the buffers if building the indices throws, which covers the leak from the overview. It also adds a test for dictionaries held in separate ArrayData objects over the same memory that checks the result still works after the inputs are disposed.

Appending dictionaries end to end doesn't prove that the combined
dictionary is ordered, so the result of that path is never ordered.
When every input shares one dictionary, the result is ordered only if
every input says so.

Also concatenate the dictionaries before allocating the validity and
index buffers, and release them if building the indices throws, so
nothing leaks on the error paths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

ArrowArrayConcatenator: null arrays are refused, and dictionary arrays lose their dictionary

2 participants