Follow-up to the Box3D Phase 1 epic (#2973). Phase 1 deliberately scopes out the SQL parser keyword and cast resolution rule for Box3D, mirroring how Box2D's parser/cast support landed: foundation + scalar surface first, then a paired parser-keyword + Box*CastResolutionRule change (#2927).
Scope
Add the parallel of #2927 for Box3D:
SedonaSqlAstBuilder.visitPrimitiveDataType recognises BOX3D (alongside the existing GEOMETRY and BOX2D keywords) and returns Box3DUDT().
- A
Box3DCastResolutionRule analyzer rule that rewrites Catalyst Cast nodes during resolution (before CheckAnalysis runs):
Cast(geom, Box3DUDT) → ST_Box3D(geom)
Cast(box, GeometryUDT) for a Box3D-typed child → ST_GeomFromBox3D(box) (the latter doesn't exist yet — see below).
- Register the rule from
SedonaSqlExtensions.injectResolutionRule.
Open sub-question
Box2D shipped with ST_GeomFromBox2D so the cast back to Geometry had a destination. Box3D Phase 1 does not have a ST_GeomFromBox3D constructor. We'd need to decide:
- Add
ST_GeomFromBox3D(box) → Geometry (returns a closed 3D-polyhedral-surface-of-a-box, or just polygonFromEnvelope on XY with the Z-extent lost) — and only then add the inverse cast.
- Ship only the
CAST(geom AS box3d) direction in this issue; defer the inverse cast until Box3D → Geometry conversion has a clear use case and we've agreed on the geometry shape.
Option 2 keeps this PR focused. Option 1 expands the surface area without a concrete consumer asking for it. I'd lean option 2.
Why not in the foundation slice
The foundation slice (PR #2978) only lands the type + UDT + Catalyst plumbing. Wiring BOX3D in the SQL parser without the cast resolution rule would let users parse CAST(... AS box3d) and then fail at analysis time (Cast.canCast(GeometryUDT, Box3DUDT) returns false). That's worse than just deferring both pieces and shipping them together in this follow-up.
Tests
Mirror Box2DCastResolutionRuleSuite (rule unit test in spark/common) and Box2DCastSuite (SQL end-to-end across spark-3.4 / 3.5 / 4.0 / 4.1, gated on the parser-extension probe).
Follow-up to the Box3D Phase 1 epic (#2973). Phase 1 deliberately scopes out the SQL parser keyword and cast resolution rule for Box3D, mirroring how Box2D's parser/cast support landed: foundation + scalar surface first, then a paired parser-keyword +
Box*CastResolutionRulechange (#2927).Scope
Add the parallel of #2927 for Box3D:
SedonaSqlAstBuilder.visitPrimitiveDataTyperecognisesBOX3D(alongside the existingGEOMETRYandBOX2Dkeywords) and returnsBox3DUDT().Box3DCastResolutionRuleanalyzer rule that rewrites CatalystCastnodes during resolution (beforeCheckAnalysisruns):Cast(geom, Box3DUDT)→ST_Box3D(geom)Cast(box, GeometryUDT)for aBox3D-typed child →ST_GeomFromBox3D(box)(the latter doesn't exist yet — see below).SedonaSqlExtensions.injectResolutionRule.Open sub-question
Box2D shipped with
ST_GeomFromBox2Dso the cast back toGeometryhad a destination. Box3D Phase 1 does not have aST_GeomFromBox3Dconstructor. We'd need to decide:ST_GeomFromBox3D(box) → Geometry(returns a closed 3D-polyhedral-surface-of-a-box, or justpolygonFromEnvelopeon XY with the Z-extent lost) — and only then add the inverse cast.CAST(geom AS box3d)direction in this issue; defer the inverse cast until Box3D → Geometry conversion has a clear use case and we've agreed on the geometry shape.Option 2 keeps this PR focused. Option 1 expands the surface area without a concrete consumer asking for it. I'd lean option 2.
Why not in the foundation slice
The foundation slice (PR #2978) only lands the type + UDT + Catalyst plumbing. Wiring
BOX3Din the SQL parser without the cast resolution rule would let users parseCAST(... AS box3d)and then fail at analysis time (Cast.canCast(GeometryUDT, Box3DUDT)returns false). That's worse than just deferring both pieces and shipping them together in this follow-up.Tests
Mirror
Box2DCastResolutionRuleSuite(rule unit test in spark/common) andBox2DCastSuite(SQL end-to-end across spark-3.4 / 3.5 / 4.0 / 4.1, gated on the parser-extension probe).