Skip to content
This repository was archived by the owner on Jul 11, 2026. It is now read-only.

Add Zarr v3 OME-Arrow Compatibility Layer via ome_arrow(...)#3

Closed
d33bs wants to merge 2 commits into
mainfrom
ome-arrow-compat
Closed

Add Zarr v3 OME-Arrow Compatibility Layer via ome_arrow(...)#3
d33bs wants to merge 2 commits into
mainfrom
ome-arrow-compat

Conversation

@d33bs

@d33bs d33bs commented Mar 29, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Added ome_arrow(...) SQL functions for dimension-aware access to Zarr v3 arrays (overviews and cell scans, optional version override).
  • Documentation

    • Updated README, ARCHITECTURE and ROADMAP to describe the OME-Arrow adapter, usage examples, and Zarr v3 notes.
  • Tests

    • Expanded test suite and smoke tests to cover ome_arrow(...) behavior and expected errors on v2 stores.
  • Sample Data

    • Updated sample Zarr v3 store metadata to include dimension names.

@d33bs
d33bs requested a review from Copilot March 29, 2026 17:53
@coderabbitai

coderabbitai Bot commented Mar 29, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3bd52cbc-9867-4921-acc0-4da6591ad297

📥 Commits

Reviewing files that changed from the base of the PR and between 95f9ccb and 4a2ddf8.

📒 Files selected for processing (1)
  • src/zarr_metadata.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/zarr_metadata.cpp

📝 Walkthrough

Walkthrough

Added an ome_arrow(...) Zarr v3 compatibility adapter: parses and normalizes dimension_names, exposes dimension-aware relational columns via new table-function binders/scanners, registers the function in the DuckDB extension, updates docs/examples, and adds tests for v3 success and v2 rejection.

Changes

Cohort / File(s) Summary
Documentation & Roadmap
ARCHITECTURE.md, README.md, ROADMAP.md, test/README.md
Documented ome_arrow(...) SQL entrypoints and OME-Arrow adapter behavior; marked convention adapters as partially complete; added Quick Start examples and notes describing Zarr v3-only detection and Arrow-writing limitations.
Sample Data
scripts/create_sample_zarr.py
Added dimension_names to the three Zarr v3 array definitions (temperature_v3, mask_v3, fortran_v3).
Extension Registration & Headers
src/duckdb_zarr_extension.cpp, src/include/zarr_metadata.hpp
Registered ZarrMetadata::GetOmeArrowFunction() in the extension loader and declared the new static method in the header.
Core Metadata & Table Functions
src/zarr_metadata.cpp
Parsed and normalized optional v3 dimension_names; stored them on ZarrArrayEntry; implemented BindOmeArrowArraysInternal, BindOmeArrowCells (via BindCellsWithDimensionNamesInternal), and ScanOmeArrowArrays to emit dimension-name columns, reject v2 stores, and support projection/filter pushdown.
Tests
test/python_smoke.py, test/sql/duckdb_zarr.test
Added Python smoke query using ome_arrow(..., 'temperature_v3') and SQL tests: v2 rejection error case, multiple v3 ome_arrow queries, and dimension_names assertions.

Sequence Diagram

sequenceDiagram
    participant User as User Query
    participant DuckDB as DuckDB Engine
    participant Binder as ome_arrow Binder
    participant Store as Zarr v3 Store
    participant Parser as Metadata Parser
    participant Scanner as ome_arrow Scanner

    User->>DuckDB: ome_arrow(path, array_path)
    DuckDB->>Binder: Bind function call
    Binder->>Store: Validate that store is Zarr v3
    alt Valid v3 Store
        Binder->>Parser: Read zarr.json and extract dimension_names
        Parser->>Parser: Normalize names (trim, lowercase, sanitize, ensure unique/count==rank)
        Parser-->>Binder: dimension_names list
        Binder->>DuckDB: Return prepared statement with dimension columns
        DuckDB->>Scanner: Execute scan
        Scanner->>Store: Read array chunks/cells
        Scanner->>Scanner: Map dimensions -> columns and emit rows
        Scanner-->>DuckDB: Result rows
        DuckDB-->>User: Query results
    else Invalid / v2 Store
        Binder-->>DuckDB: Error: unrecognizable v3 metadata
        DuckDB-->>User: Error
    end
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly Related PRs

  • d33bs/duckdb_zarr#1: Adds Zarr v3 dimension_names parsing and the new GetOmeArrowFunction() table-function registration in the same ZarrMetadata/extension codepaths.

Poem

🐰 A tiny arrow hops into Zarr v3 land,
Naming each axis with a tidy hand,
Columns align where dimensions play,
Queries bloom in a relational way,
Hop, map, and return—data ready and grand.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately summarizes the main change: adding a new Zarr v3 OME-Arrow compatibility layer via the ome_arrow(...) function, which is the primary feature implemented across all modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ome-arrow-compat

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

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.

Pull request overview

Adds an ome_arrow(...) table function set that exposes a Zarr v3 / OME-Arrow-friendly view by (a) returning v3 array metadata including dimension_names, and (b) using those dimension names as the relational coordinate column names for cell scans.

Changes:

  • Introduces ome_arrow(path) (v3-only array overview) and ome_arrow(path, array_path[, version_override]) (v3-only cells scan with dimension-named columns).
  • Extends v3 metadata parsing to read/normalize dimension_names and plumbs them through array entries.
  • Updates SQL + Python smoke tests and fixtures/docs to cover ome_arrow(...) and v3 dimension_names.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/zarr_metadata.cpp Parses/normalizes v3 dimension_names; adds binder/scan paths for ome_arrow overview + dimension-named cell scans.
src/include/zarr_metadata.hpp Exposes GetOmeArrowFunction() in the public header.
src/duckdb_zarr_extension.cpp Registers the new ome_arrow function set with the extension loader.
test/sql/duckdb_zarr.test Adds SQL coverage for v3-only behavior, dimension name reporting, and dimension-named cell output.
test/python_smoke.py Adds a Python-side ome_arrow query and column-name assertion.
test/data/simple_v3.zarr/*/zarr.json Updates v3 fixtures to include dimension_names.
scripts/create_sample_zarr.py Updates sample v3 store generator to emit dimension_names.
README.md Documents new ome_arrow(...) entrypoints and intent/limitations.
test/README.md Notes ome_arrow(...) coverage in the test suite overview.
ARCHITECTURE.md Mentions ome_arrow(...) as a thin convention adapter in the MVP.
ROADMAP.md Marks convention adapters as partially implemented via ome_arrow(...).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/zarr_metadata.cpp Outdated
Comment on lines +2340 to +2344
return_types.reserve(cell_array.rank + 1);
for (idx_t dim = 0; dim < NumericCast<idx_t>(cell_array.rank); dim++) {
return_types.push_back(LogicalType::BIGINT);
}
names.push_back("value");

Copilot AI Mar 29, 2026

Copy link

Choose a reason for hiding this comment

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

ome_arrow derives output column names from v3 dimension_names and later appends a fixed "value" column. If a store uses a dimension name that normalizes to "value", this will produce duplicate output column names (dimension + value), which can break queries or result binding. Consider rejecting/resolving reserved names (e.g., disallow "value" in NormalizeDimensionNames, or auto-disambiguate by renaming to something like "dim_value" / "value_0").

Suggested change
return_types.reserve(cell_array.rank + 1);
for (idx_t dim = 0; dim < NumericCast<idx_t>(cell_array.rank); dim++) {
return_types.push_back(LogicalType::BIGINT);
}
names.push_back("value");
// Ensure that the appended value column name does not collide with any dimension name
unordered_set<string> existing_names(names.begin(), names.end());
string value_col_name = "value";
if (existing_names.find(value_col_name) != existing_names.end()) {
// Find a unique variant: value_0, value_1, ...
const string base_name = value_col_name;
idx_t suffix = 0;
do {
value_col_name = StringUtil::Format("%s_%llu", base_name.c_str(),
static_cast<unsigned long long>(suffix));
suffix++;
} while (existing_names.find(value_col_name) != existing_names.end());
}
return_types.reserve(cell_array.rank + 1);
for (idx_t dim = 0; dim < NumericCast<idx_t>(cell_array.rank); dim++) {
return_types.push_back(LogicalType::BIGINT);
}
names.push_back(value_col_name);

Copilot uses AI. Check for mistakes.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/zarr_metadata.cpp`:
- Around line 279-283: OptionalStringArray currently returns an empty vector for
both missing/null keys and present-but-empty arrays, which skips the rank
validation in NormalizeDimensionNames; change OptionalStringArray to return an
optional<vector<string>> (or a pair<bool, vector<string>>/out-present flag) so
callers can distinguish "key absent/null" from "present empty array", update
call sites (notably NormalizeDimensionNames) to treat std::nullopt as
"unspecified" (keep old unnamed behavior) but if present and vector.size() !=
rank (including size==0 when rank>0) return/emit the validation error; apply the
same change to the other occurrence around lines 349-355 so both paths validate
array size against rank.
- Around line 2339-2345: names currently unconditionally appends a "value"
column which can collide with existing dimension names from
GetOutputDimensionNames(cell_array); change the logic after names is populated
to check for an existing "value" entry and, if present, pick a non-colliding
name (e.g., "value", "value_1", "value_2", ...) before pushing to names and
return_types so the pushed dtype.logical_type remains aligned; update the code
paths that push to return_types (the vector reserved and the push_back of
dtype.logical_type) to use the chosen unique column name and avoid duplicate
output column names when using GetOutputDimensionNames, cell_array.rank, names,
return_types, and dtype.logical_type.
- Around line 360-364: Assign the return value of StringUtil::Trim to the local
variable so trimming actually happens: replace the discarded call
StringUtil::Trim(name) with name = StringUtil::Trim(name); then the subsequent
name = StringUtil::Lower(name) and the empty check that throws
InvalidInputException for Zarr v3 dimension_names will behave correctly; look
for this logic around the dimension_names loop in zarr_metadata.cpp where
variable name is used.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6107bd5a-aca3-4caa-b902-9f4821ed7b22

📥 Commits

Reviewing files that changed from the base of the PR and between a4113ed and 95f9ccb.

⛔ Files ignored due to path filters (3)
  • test/data/simple_v3.zarr/fortran_v3/zarr.json is excluded by !test/data/**/*.zarr/**
  • test/data/simple_v3.zarr/mask_v3/zarr.json is excluded by !test/data/**/*.zarr/**
  • test/data/simple_v3.zarr/temperature_v3/zarr.json is excluded by !test/data/**/*.zarr/**
📒 Files selected for processing (10)
  • ARCHITECTURE.md
  • README.md
  • ROADMAP.md
  • scripts/create_sample_zarr.py
  • src/duckdb_zarr_extension.cpp
  • src/include/zarr_metadata.hpp
  • src/zarr_metadata.cpp
  • test/README.md
  • test/python_smoke.py
  • test/sql/duckdb_zarr.test

Comment thread src/zarr_metadata.cpp Outdated
Comment thread src/zarr_metadata.cpp Outdated
Comment on lines +360 to +364
auto name = dimension_names[i];
StringUtil::Trim(name);
name = StringUtil::Lower(name);
if (name.empty()) {
throw InvalidInputException("Zarr v3 dimension_names cannot contain empty names");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

DuckDB C++ StringUtil::Trimsignature induckdb/common/string_util.hpp — does it return a new string or trim in place?

💡 Result:

The DuckDB C++ StringUtil::Trim function in duckdb/common/string_util.hpp returns a new string rather than trimming in place. DuckDB's string_t type is immutable (as shown in string_type.hpp source code from GitHub result 6), so string utility functions like Trim create and return new string_t instances with trimmed content allocated in a string heap or inlined if small. In-place modification would violate immutability. No source explicitly shows the signature, but design principles confirm it returns new string. Recent PRs mentioning StringUtil (e.g., #14391) use it for enum-string conversions, consistent with functional style (new strings).

Citations:


🏁 Script executed:

# First, let's locate the string_util.hpp file in the repository or check if it's available
find . -name "string_util.hpp" -o -name "*string_util*" | head -20

Repository: d33bs/duckdb_zarr

Length of output: 43


🏁 Script executed:

# Let's also examine the exact code at lines 360-364 in src/zarr_metadata.cpp
sed -n '355,370p' src/zarr_metadata.cpp

Repository: d33bs/duckdb_zarr

Length of output: 506


🏁 Script executed:

# Search for StringUtil in the repository - might be in DuckDB headers or local includes
find . -type f -name "*.hpp" -o -name "*.h" | xargs grep -l "class StringUtil\|namespace StringUtil" 2>/dev/null | head -10

Repository: d33bs/duckdb_zarr

Length of output: 43


🏁 Script executed:

# Also search for the Trim function definition
rg "StringUtil::Trim|void Trim|string Trim" -A 2 --type cpp --type h | head -40

Repository: d33bs/duckdb_zarr

Length of output: 208


🏁 Script executed:

# Check if DuckDB is a submodule or vendored
ls -la | grep -i duckdb
git submodule status

Repository: d33bs/duckdb_zarr

Length of output: 491


🏁 Script executed:

# Look for StringUtil files in the duckdb submodule
find ./duckdb -name "*string_util*" -type f | head -10

Repository: d33bs/duckdb_zarr

Length of output: 43


🏁 Script executed:

# Search for Trim and Lower function definitions in duckdb submodule
rg "Trim|Lower" ./duckdb --type cpp --type h -B 2 -A 5 | grep -E "(static.*Trim|static.*Lower|string.*Trim|string.*Lower)" | head -20

Repository: d33bs/duckdb_zarr

Length of output: 43


🏁 Script executed:

# Check if duckdb directory is properly populated
ls -la ./duckdb | head -20

Repository: d33bs/duckdb_zarr

Length of output: 232


🏁 Script executed:

# Try broader search for StringUtil
find ./duckdb -type f \( -name "*.hpp" -o -name "*.h" \) -exec grep -l "StringUtil" {} \; | head -10

Repository: d33bs/duckdb_zarr

Length of output: 43


🏁 Script executed:

# Also check what's in include/duckdb directory if it exists
ls -la ./duckdb/src/include/duckdb/common/ 2>/dev/null | grep -i string | head -10

Repository: d33bs/duckdb_zarr

Length of output: 43


Assign the return value of StringUtil::Trim.

The Trim function returns a new string rather than modifying in place. Line 361 calls StringUtil::Trim(name) but discards the result, making the operation a no-op. Whitespace-only dimension names would incorrectly pass the empty check. Change to name = StringUtil::Trim(name); to match the correct pattern used on line 362 with StringUtil::Lower.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zarr_metadata.cpp` around lines 360 - 364, Assign the return value of
StringUtil::Trim to the local variable so trimming actually happens: replace the
discarded call StringUtil::Trim(name) with name = StringUtil::Trim(name); then
the subsequent name = StringUtil::Lower(name) and the empty check that throws
InvalidInputException for Zarr v3 dimension_names will behave correctly; look
for this logic around the dimension_names loop in zarr_metadata.cpp where
variable name is used.

Comment thread src/zarr_metadata.cpp
@d33bs

d33bs commented Mar 29, 2026

Copy link
Copy Markdown
Member Author

This ended up too specific to non-Zarr work. Closing this.

@d33bs d33bs closed this Mar 29, 2026
@d33bs
d33bs deleted the ome-arrow-compat branch March 29, 2026 23:42
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants