Skip to content

Put dictionary value extension metadata on the C Data dictionary child - #474

Merged
CurtHagenlocher merged 4 commits into
apache:mainfrom
CurtHagenlocher:fix/473-dictionary-extension-metadata
Oct 19, 2026
Merged

CurtHagenlocher merged 4 commits into
apache:mainfrom
CurtHagenlocher:fix/473-dictionary-extension-metadata

Conversation

@CurtHagenlocher

@CurtHagenlocher CurtHagenlocher commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

What's Changed

For a dictionary-encoded extension type such as dictionary<int32, arrow.uuid>, Arrow C++ puts the value type's ARROW:extension:name on the dictionary child ArrowSchema. On the field itself, the same keys mean the extension's storage is the whole dictionary. CArrowSchemaExporter put a value type's extension metadata on the field. As a result, pyarrow rejected such fields (Invalid storage type for UuidType: dictionary<...>), and the importer didn't resolve them in either direction (#473).

Export

  • When a dictionary's value type is an ExtensionType, its extension metadata is written on the dictionary child. If the field metadata names that same extension, which is how the IPC reader leaves it, those keys move to the child as well. Other field metadata stays on the field.
  • Unresolved extension metadata on a dictionary field, where the value type isn't an ExtensionType, stays on the field. There, it describes an extension over the whole dictionary, which is how Arrow C++ exports one.
  • ExportType now writes extension metadata when given an ExtensionType, as Arrow C++ does. The dictionary child is exported through it. Behavior change: ExportType(GuidType.Default, ptr) previously produced a bare w:16 with no extension name.

Import

  • The dictionary child's extension metadata resolves the value type against the registry. It isn't copied to the field, where it would mean something else. Like Arrow C++, an unresolved value type's extension metadata is dropped.
  • Extension metadata on a dictionary field is resolved against the whole dictionary type only, as in Arrow C++. Behavior change: schemas exported by earlier versions of this library put a value type's extension metadata there, so importing one gives a dictionary of the storage type. C Data schemas aren't persisted, so this only affects exchanges between different Apache.Arrow versions in the same process.

Tests

  • .NET: where export puts the metadata, including field-level keys from IPC. Unresolved field-level metadata stays on the field across an import and re-export without a registry. Also schema round trips with and without a registry, field-level metadata on a dictionary not being applied to the values, and a DictionaryArray of GuidArray round trip.
  • pyarrow: .NET → pyarrow gives dictionary<values=extension<arrow.uuid>, indices=int32> with equal values, and pyarrow → .NET gives a DictionaryArray whose Dictionary is a GuidArray.

IPC has the same ambiguity, and Apache.Arrow's IPC reader resolves it the opposite way from Arrow C++. That's filed separately as #475.

Closes #473.

🤖 Generated with Claude Code

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

For a dictionary-encoded extension type, Arrow C++ puts the value type's
ARROW:extension:name on the dictionary child ArrowSchema, but the C Data
exporter put it on the field, where IPC keeps it. pyarrow rejected such
fields, and the importer never resolved them in either direction.

Export: write a dictionary field's extension keys on the dictionary
child, and have ExportType emit extension metadata for extension types.

Import: resolve the dictionary child's extension metadata against the
value type and copy it to the field metadata, matching the IPC reader.
For data exported by older versions, a field-level extension name on a
dictionary field is also tried against the value type.

Fixes apache#473

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.

🟡 Changes recommended

Re-exporting an unresolved dictionary-storage extension moves its metadata to the values, changing its meaning.

1 open issue
What changed in this PR

Aligns dictionary value extension metadata with the C Data Interface placement used by Arrow C++, addressing #473.

Changes:

  • Exports value-extension metadata on dictionary children.
  • Resolves child extensions during import and supports legacy field-level placement.
  • Adds .NET round-trip and bidirectional pyarrow interoperability tests.
File Description
test/​Apache.Arrow.Tests/​ExtensionTypeTests.cs Tests metadata placement and dictionary extension round trips.
test/​Apache.Arrow.Tests/​CDataInterfacePythonTests.cs Tests dictionary UUID exchange with pyarrow.
src/​Apache.Arrow/​C/​CArrowSchemaImporter.cs Resolves dictionary child extensions and preserves metadata.
src/​Apache.Arrow/​C/​CArrowSchemaExporter.cs Routes extension metadata to the appropriate schema node.

Review effort: Balanced

Comment thread src/Apache.Arrow/C/CArrowSchemaExporter.cs Outdated
On a dictionary field, C Data extension metadata means the extension's
storage is the whole dictionary. The exporter moved any such metadata to
the dictionary child, so an unresolved extension over a dictionary was
re-exported as an extension over its values. Move it only when it names
the resolved value type, as the IPC reader leaves it.

The importer no longer copies dictionary child extension metadata to the
field, where it would take on that other meaning. As in Arrow C++, an
unresolved value type's extension metadata is dropped.

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.

🔵 Needs a closer look

A test leaks the unmanaged CArrowSchema allocation and should free it after import.

No open issues 🎉

1 previously missed issue

In code that hasn't changed since last review

Medium severity Test leaks unmanaged CArrowSchema allocation

test/​Apache.Arrow.Tests/​CDataInterfacePythonTests.cs:1319

ImportField invokes the foreign schema's release callback but does not free the CArrowSchema allocation itself (the API docs require callers to call CArrowSchema.Free afterward). This test frees cArray but never frees cSchema, leaking the unmanaged schema allocation on every successful run; add CArrowSchema.Free(cSchema) after importing (ideally in a finally).

1 resolved since last review

Review effort: Lite

CurtHagenlocher and others added 2 commits October 18, 2026 17:08
Extension metadata on a dictionary field describes the whole dictionary.
Stop also trying it against the value type to resolve schemas exported
by earlier versions of this library; C Data schemas aren't persisted,
and Arrow C++ has no such fallback.

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

🟢 Approval recommended

The changes are covered by interoperability and round-trip tests; only a minor documentation nit remains.

No open issues 🎉

Review effort: Lite

@CurtHagenlocher
CurtHagenlocher merged commit ad0018e into apache:main Oct 19, 2026
13 of 14 checks passed
@CurtHagenlocher
CurtHagenlocher deleted the fix/473-dictionary-extension-metadata branch October 19, 2026 01:20
CurtHagenlocher added a commit that referenced this pull request Oct 19, 2026
## What's Changed

`ImportArray`, `ImportRecordBatch`, `ImportGuidArray` and
`ImportBool8Array` in `CDataInterfacePythonTests.cs` allocate a
`CArrowSchema`, have pyarrow export into it, and import it, but never
call `CArrowSchema.Free`. The importer calls pyarrow's release callback,
so what leaked was the `CArrowSchema` struct itself. They now free the
schema right after importing it, as `ImportDictionaryOfGuidArray` does
since #474, so a failing assertion can't skip the free.

This only changes tests. Every `CArrowSchema.Create` in the test
projects now has a matching `CArrowSchema.Free`.

## Tests

The full `Apache.Arrow.Tests` suite passes on net8.0, net462 and net472,
with the pyarrow tests enabled.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

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.

C Data Interface puts a dictionary-encoded extension type's metadata on the wrong schema

2 participants