zarr-datafusion is not a query engine. It is a DataFusion data source plus a small bundle of domain-specific optimizer rules. This doc names the DataFusion concepts we plug into and draws a clean line between the logic we own and the machinery we borrow.
Companions: architecture-overview.md (how modules
connect) and design-decisions.md (the why).
DataFusion is built to be extended at specific seams. We plug into exactly these:
| Seam (DataFusion trait / API) | Our implementation | What it buys us |
|---|---|---|
TableProvider |
ZarrTable (datasource/zarr.rs) |
Makes a Zarr store look like a SQL table; its scan() is our hook for projection / filters / limit |
TableProviderFactory |
ZarrTableFactory (datasource/factory.rs) |
Wires CREATE EXTERNAL TABLE ... STORED AS ZARR into SQL |
ExecutionPlan |
ZarrExec (physical_plan/zarr_exec.rs) |
A leaf node in the physical plan; execute(partition) returns our stream |
PhysicalOptimizerRule |
ZarrLimitPushdownRule (optimizer/limit_pushdown.rs) |
Folds LIMIT into the scan |
OptimizerRule (logical) |
CountStatisticsRule, MinMaxStatisticsRule (optimizer/) |
Replaces aggregates with constants before planning |
TableFunctionImpl (UDTF) |
zarr_describe() (udtf.rs) |
Custom table function in SQL |
ScalarUDF / AggregateUDF |
rmse, mae, … (udfs/) |
Domain functions usable in SQL |
TaskEstimator / WorkerResolver (datafusion-distributed) |
ZarrTaskEstimator, StaticWorkerResolver (distributed.rs) |
Spread our scan across worker nodes |
PhysicalExtensionCodec (datafusion-proto) |
ZarrPhysicalCodec (physical_plan/codec.rs) |
Serialize our custom ZarrExec over the wire |
Expr— the logical expression tree we read inparse_coord_filtersto find coordinate predicates.ScalarValue— the literal type carried insideCoordFilterKindand serialized by the codec.SchemaRef,RecordBatch,ArrayRef,DictionaryArray— Arrow, strictly, but the columnar substrate DataFusion mandates.SendableRecordBatchStream/RecordBatchStreamAdapter— the pull-based output contract.PlanProperties(Partitioning,EquivalenceProperties,Boundedness,EmissionType) — metadata anExecutionPlanmust advertise so the planner can reason about us.SessionContext/SessionStateBuilder— the registry we bolt everything onto.TableProviderFilterPushDown(Exact/Inexact/Unsupported) — howscantells DataFusion which filters it handled.
The cleanest mental model: DataFusion owns the trunk and branches of the query; we own one leaf and a few pruning shears.
SQL text
│ ◀── DataFusion: parser, binder
LogicalPlan
│ ◀── DataFusion: optimizer framework
│ ◀── OURS: Count / MinMax logical rules (a few leaves)
PhysicalPlan
│ ◀── DataFusion: HashAggregate, Sort, Filter, Repartition,
│ CoalescePartitions, GlobalLimit, joins, windows…
│ ◀── OURS: ZarrLimitPushdown physical rule
┌──┴──────────────────────────────┐
│ everything above the scan │ ← borrowed, in full
├───────────────────────────────────┤
│ ZarrExec (the leaf) │ ← OURS
│ read_zarr → RecordBatch stream │
└───────────────────────────────────┘
- SQL parsing, name/type binding, type coercion.
- The entire relational algebra above the scan:
JOIN,GROUP BY,HAVING,ORDER BY, window functions, subqueries,DISTINCT, set ops. - The physical operators that run them (hash aggregate, sort-merge, repartition, coalesce, the generic limit / filter execs).
- Arrow compute kernels (comparisons, arithmetic, casting).
- The streaming, partition-parallel execution driver (
collect,task_ctx). - The optimizer framework and most generic rules (projection-pushdown plumbing, constant folding, common-subexpression elimination, …).
- datafusion-distributed's Arrow Flight transport and stage planning.
Everything we own is plugged into DataFusion through one of its extension traits — that is the ownership boundary. Each owned type below is annotated with the DataFusion seam it implements (full list in the table up top):
- The data-source seam —
ZarrTableimplementsTableProvider, constructed byZarrTableFactoryimplementingTableProviderFactory. This is how a Zarr store enters DataFusion as a SQL table. - The execution seam —
ZarrExecimplementsExecutionPlan: the leaf node DataFusion executes, advertisingPlanPropertiesand returning our stream fromexecute(partition). - The leaf logic — everything in
reader/: Zarr decoding, the nD→2D flattening model, CF-time, dtype mapping, dictionary coordinate encoding. Reached throughZarrExecviaread_zarr/read_zarr_async, but implements no DataFusion trait itself — DataFusion has no concept of Zarr; this is 100% ours. - Our own filter IR —
CoordFilters/CoordFilterKind/CoordSelection. We read DataFusion'sExpr(and answerTableProviderFilterPushDowninsupports_filters_pushdown) but translate it into our own structures and resolve them to array positions (resolve_coord_selection, set intersection). - Domain-aware optimizations DataFusion can't make —
ZarrLimitPushdownRuleimplementsPhysicalOptimizerRule(pushingLIMITpast a filter, which the generic planner refuses but which is provably safe here);CountStatisticsRule/MinMaxStatisticsRuleimplement the logicalOptimizerRule(constant-folding aggregates fromZarrStoreMeta). - Partitioning strategy —
PartitionSpec,split_selection/split_indices: chunk-aware slicing of a scan, surfaced throughZarrExec'sPartitioning. - Distributed wiring —
ZarrTaskEstimator/StaticWorkerResolverimplement datafusion-distributed'sTaskEstimator/WorkerResolver;ZarrPhysicalCodecimplementsPhysicalExtensionCodecto serialize ourZarrExec. - Functions —
zarr_describe()implementsTableFunctionImpl(UDTF);rmse/maeimplementScalarUDF/AggregateUDF. - Observability —
ZarrIoStats+TrackedStore(compressed-vs-uncompressed byte accounting); pure internal logic, no DataFusion trait.
Three places are worth understanding, because that is where "borrow vs. own" gets subtle.
-
Filter pushdown — shared. DataFusion owns the mechanism (it offers filters to
scanand honors ourExact/Inexactverdict); we own the semantics (which predicates we can resolve to positions, and the promise that we actually applied them). If we returnExactbut fail to filter, DataFusion produces wrong results — the contract is ours to keep. -
Limit pushdown — we deliberately stepped outside DataFusion's rules. The generic planner will not push a limit below a filter. Our custom physical rule does, justified entirely by a domain invariant: sorted coordinate filters are resolved in-scan, so the surviving rows are known up front. This is the clearest case of owning logic that contradicts DataFusion's defaults.
-
Statistics — we bypass execution entirely. The
Count/MinMaxrules answer fromZarrStoreMetaso the scan never runs. DataFusion also has a statistics framework (StatisticsonExecutionPlan); we chose explicit rewrite rules instead. Follow-up worth considering: implementingExecutionPlan::statistics()might let DataFusion's own aggregate rules do this, shrinking our owned surface.
We are a data source plus a small bundle of domain-specific optimizer rules. We own the leaf of the plan (reading and flattening Zarr) and a few rewrites that exploit scientific-data invariants; DataFusion owns the entire query engine above the scan.