Skip to content

feat(rust/sedona-raster): trinary Override for BandOverrides; copy_into stops composing views - #1158

Open
james-willis wants to merge 3 commits into
mainfrom
jw/copy-into-override-semantics
Open

feat(rust/sedona-raster): trinary Override for BandOverrides; copy_into stops composing views#1158
james-willis wants to merge 3 commits into
mainfrom
jw/copy-into-override-semantics

Conversation

@james-willis

Copy link
Copy Markdown
Contributor

No description provided.

@github-actions
github-actions Bot requested a review from paleolimbot August 12, 2026 20:24
james-willis added a commit that referenced this pull request Aug 26, 2026
Follow-up to the previous commit, which worked around the missing accessor
by passing the name explicitly at the one broken call site. This removes the
cause instead.

`BandRef` had no name accessor, so `copy_into` could not inherit the band
name the way it inherits `nodata` / `outdb_uri` / `outdb_format` — every
caller had to remember to thread `RasterRef::band_name(i)` through, and
`copy_raster_from` did not.

Add `BandRef::name()` and have `copy_into` fall back to it, which lets
`copy_raster_from` go back to a plain `BandOverrides::default()`.

The method is required rather than defaulted: a backend silently returning
`None` would drop names on every derived band, which is the exact failure
this exists to prevent. Only two implementors exist — the Arrow-backed
`BandRefImpl` and a test stub.

Note for #1158 (trinary `Override`): its `copy_into` currently collapses
`Override::Keep | Override::Clear => None` for `name`, with a comment that a
`BandRef` has no source name to inherit. With this accessor that asymmetry
can go — `Keep => self.name(), Clear => None` — making `name` behave like
every other overridable field.
@james-willis
james-willis force-pushed the jw/copy-into-override-semantics branch from e46d2ab to ba0eeda Compare August 27, 2026 17:48
@james-willis
james-willis marked this pull request as ready for review August 27, 2026 18:22
@james-willis
james-willis marked this pull request as draft August 27, 2026 18:37
@james-willis
james-willis force-pushed the jw/copy-into-override-semantics branch from ba0eeda to 91a2e26 Compare August 27, 2026 18:54
@james-willis
james-willis changed the base branch from main to james/viewentries-interface August 27, 2026 18:54
@james-willis
james-willis force-pushed the jw/copy-into-override-semantics branch from 91a2e26 to 48f8a42 Compare August 27, 2026 19:12
@james-willis
james-willis force-pushed the jw/copy-into-override-semantics branch from 48f8a42 to 287e9d8 Compare August 27, 2026 20:34
@james-willis
james-willis force-pushed the jw/copy-into-override-semantics branch from 287e9d8 to 1e751a5 Compare August 27, 2026 20:53
Base automatically changed from james/viewentries-interface to main August 28, 2026 16:44
…to stops composing views

Replace the all-Option BandOverrides fields with a trinary Override<T>
(Keep/Clear/Set) for the clearable fields (name, nodata, outdb_uri,
outdb_format, view); dim_names stays Option because a band always has dim
names, so there is no absent state for Clear to express. copy_into now
resolves each field by matching on the Override and never composes the
view: Keep inherits the source view, Clear resets to the canonical
identity view, and Set uses the given view verbatim. Callers that want
the old compose behaviour pass Set(&source.view().compose(&next)?).

RasterBuilder::with_view adopts this model: it composes the supplied
delta onto the input's own view itself and passes the absolute result as
Override::Set, inheriting nodata and the outdb hints from the input via
Override::Keep (a None override maps to Keep, an explicit value to Set).
This keeps both the source-nodata inheritance and the compose-time bound
against the parent's visible window that copy_into used to provide.
…a on null

A null nodata argument previously nulled the whole raster row. It now
flows through as None and clears the addressed band's nodata via
Override::Clear, rebuilding the raster with that band carrying no nodata
sentinel. A null raster or band still yields a null raster. set_band_nodata
takes Option<f64>; Some(v) sets Override::Set(packed bytes), None clears,
and other bands keep their own nodata (Override::Keep).
@james-willis
james-willis force-pushed the jw/copy-into-override-semantics branch from 1e751a5 to 93a9e75 Compare August 28, 2026 16:44
@james-willis
james-willis marked this pull request as ready for review August 28, 2026 17:42
james-willis added a commit that referenced this pull request Aug 28, 2026
…etters

Closes DB-99.

`RS_SetCRS`, `RS_SetSRID` and `RS_SetGeoReference` change only top-level
metadata — one of five columns on the raster `StructArray`. The bands column,
which holds every pixel byte, is untouched by definition.

Two ways to produce that output existed, and the setters were split across
both:

* `swap_crs_column` (private to `rs_setsrid.rs`, hardcoded to the CRS column)
  replaced one column and carried the rest over as the same `Arc`. Correct,
  but unreachable from anywhere else.
* `RasterBuilder::copy_raster_from` walked every band and re-emitted every
  band field into a fresh builder. `RS_SetGeoReference` used this. The pixel
  buffers were shared by refcount, but all the band metadata was rebuilt to
  change one number — and the bands came out identical only if the rebuild
  was faithful. DB-506 is what happens when it isn't: that rebuild silently
  dropped band names.

Generalize the first into a shared `with_column_overrides` and move all three
setters onto it.

* `RasterColumnOverrides { crs, transform }` uses the `Override<T>` trinary
  from #1158. `Keep` leaves a column in place sharing its `Arc`, `Clear` nulls
  it, `Set(a)` installs a per-row array. `Override` rather than `Option`
  because `RS_SetSRID(raster, 0)` must *clear* the CRS while keeping the
  raster — distinct from leaving the column alone, which `Option` cannot
  express. That is why `swap_crs_column` was bespoke.
* The helper also absorbs the input-null merge that was duplicated inline at
  both `rs_setsrid.rs` call sites, including the length-1 broadcast for scalar
  arguments. A null *argument* nulls the whole raster row; that is a separate
  axis from `Clear`ing a column.
* It is a free function taking `&StructArray`, not a method on
  `RasterStructArray`, so it does not require parsing and validating every
  column for what is pure column surgery — and so introduces no failure mode
  `swap_crs_column` did not have.
* `RS_SetGeoReference` builds a `List<Float64>` transform column directly and
  drops `copy_raster_from`. Its bands are now provably untouched.

Note this keeps two override vocabularies on purpose. `RasterOverrides` stays
the *builder's* language — a scalar applied while a row is constructed. The
array path replaces whole columns and takes per-row values already in an
array. Forcing the setters onto the scalar struct would regress `RS_SetSRID`
from per-row CRS (`broadcast_string_view` passes an N-row array straight
through) to a single CRS per call. They share `Override<T>`, which is the
vocabulary that matters.

`with_column_overrides_shares_untouched_columns` asserts `Arc::ptr_eq` on
every carried-over column, which is the only assertion that distinguishes a
shared column from an equal-looking rebuild.
@jiayuasu

Copy link
Copy Markdown
Member

I think it makes sense to use a trinary representation for nodata, or an equivalent dedicated mechanism. However, converting name, outdb_uri, outdb_format, and view at the same time, and changing copy_into from visible-coordinate composition to raw-source replacement is not required for that fix.

Can we scope this PR down to trinary nodata handling?

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.

2 participants