GH-50027: [Format][C++][Python] Add fixed and variable closedness range canonical extension types - #50028
Open
Hoeze wants to merge 18 commits into
Open
GH-50027: [Format][C++][Python] Add fixed and variable closedness range canonical extension types#50028Hoeze wants to merge 18 commits into
Hoeze wants to merge 18 commits into
Conversation
|
|
Hoeze
marked this pull request as ready for review
May 24, 2026 13:36
Member
|
See comment. |
rok
reviewed
Jun 4, 2026
hoeze-minion
force-pushed
the
feat/arrow-range-extension
branch
from
September 13, 2026 00:24
f3ec0b6 to
c414320
Compare
| RangeClosed closed) | ||
| : ExtensionType(std::move(storage_type)), closed_(closed) {} | ||
|
|
||
| std::string extension_name() const override { return "arrow.fixed_closedness_range"; } |
| convention: SQL uses ``INTERVAL`` for durations and ``RANGE`` (or | ||
| ``PERIOD``) for bounded sets. | ||
|
|
||
| * Extension name: ``arrow.fixed_closedness_range``. |
Add a canonical extension type for bounded ranges (mathematical intervals),
distinct from Arrow's calendar Interval (duration) type.
- Spec: docs/source/format/CanonicalExtensions.rst adds the Range section.
Storage is Struct<lower, upper> with both bounds nullable (null = +/-infinity,
treated as exclusive). A closed parameter (left/right/both/neither, pandas
vocabulary) is carried as JSON extension metadata; the subtype is read from
storage. Disambiguates from the calendar Interval type per DB convention
(INTERVAL = duration, RANGE/PERIOD = bounded set).
- C++ reference impl: cpp/src/arrow/extension/range.{h,cc} (RangeType/RangeArray)
with serialize/deserialize, storage validation, registration in the global
registry, tests, and CMake/meson wiring.
The closedness is no longer defaulted on the wire: empty metadata or a JSON object without a "closed" key is now rejected by Deserialize, so a serialized arrow.range is always unambiguous. The C++ convenience default argument for constructing a RangeType in code is left-closed ([lower, upper)), matching the PostgreSQL/Rust/Python range convention. Spec and tests updated.
Verified by building the arrow-canonical-extensions-test target (50/50 pass, 10/10 RangeType). Two fixes to the previously-uncompiled test: - include arrow/array/array_nested.h for the full StructArray definition (it is only forward-declared in type_fwd.h). - wrap the CheckDeserialize helper in an anonymous namespace to avoid a link-time collision with the identically named helper in opaque_test.cc.
Add a sibling canonical extension type to arrow.range that stores bound
inclusivity per value via non-nullable boolean lower_inc/upper_inc fields,
storage Struct<lower:T, upper:T, lower_inc:bool, upper_inc:bool>.
arrow.range carries a single type-level closed parameter, sufficient for
discrete ranges that canonicalize to one closedness (int4range, int8range,
daterange). Continuous ranges (numrange, tsrange, tstzrange) cannot be
canonicalized, so closedness must travel with each value. arrow.range_inc
mirrors PostgreSQL's internal range representation for that case; both types
coexist.
The type has no metadata parameters: inclusivity lives in storage, so
Serialize emits {} and Deserialize accepts empty/{}/extra keys. A null
(infinite) bound is always exclusive regardless of its flag.
Covers C++ (type, array, registration, tests), pyarrow bindings and tests,
and the format spec, status table, and C++/Python API docs.
Under CMAKE_UNITY_BUILD (Windows CI), range_test.cc and opaque_test.cc are merged into one translation unit. Both declared a CheckDeserialize helper (range's in an anonymous namespace, opaque's in namespace arrow), making the unqualified call ambiguous and failing the MSVC build with C2668. Rename the range helper to CheckRangeDeserialize to remove the collision.
Use JsonWriter for Serialize and the simdjson DOM helpers for Deserialize, matching the other canonical extension types.
Name the pair after arrow.fixed_shape_tensor and arrow.variable_shape_tensor: closedness is either one type parameter or stored per value. - arrow.range -> arrow.fixed_closedness_range - arrow.range_inc -> arrow.variable_closedness_range - C++: RangeType/RangeArray -> FixedClosednessRangeType/Array, RangeIncType/RangeIncArray -> VariableClosednessRangeType/Array, range()/range_inc() -> fixed_closedness_range()/variable_closedness_range() - pyarrow: range_/range_inc and the Range* classes follow the same names
Allow any orderable type as the range subtype and compare bounds with the order of that type; the spec defines only the storage layout. State that only null means unbounded and that all empty ranges denote the same set. Add string subtype cases to the C++ and Python tests.
__reduce__ rebuilt both range types from their parameters only, so a type with non-nullable bounds came back nullable after unpickling. Rebuild them from the storage type through the C++ Deserialize instead. This also keeps a type where only one bound is nullable, and tests cover both cases.
hoeze-minion
force-pushed
the
feat/arrow-range-extension
branch
from
September 26, 2026 21:38
5ff2abd to
ffcfd4d
Compare
The emptiness rule compared lower and upper without saying what happens with a null bound. A null bound means unbounded and cannot be compared, so the rule now applies only when both bounds are non-null.
_fixed_closedness_range_from_storage put closed into the JSON metadata without escaping. It now builds its prototype with closed, so fixed_closedness_range() rejects any value other than left, right, both and neither first.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Rationale for this change
This PR implements #50027. It adds two canonical extension types for ranges, i.e. intervals with a lower and an upper bound:
arrow.fixed_closedness_range: the closedness (left,right,bothorneither) is a type parameter shared by all values. This fits discrete ranges such as PostgreSQL'sint4rangeordaterange, and pandas'IntervalArray.arrow.variable_closedness_range: the inclusivity of each bound is stored per value. Continuous ranges such as PostgreSQL'snumrangeortstzrangeneed this, because one column can hold both[1, 5]and(1, 5).The names follow the existing
arrow.fixed_shape_tensor/arrow.variable_shape_tensorpair.What changes are included in this PR?
CanonicalExtensions.rst, and a row for each in the status table. Both use aStruct<lower: T, upper: T>storage, and the variable type adds two non-nullable booleanslower_incandupper_inc. The spec defines only this storage layout: T may be any orderable type, bounds are compared with the order of T, and only a null bound means an unbounded side.arrow/extension/range.h:FixedClosednessRangeType,VariableClosednessRangeType, their array classes, and the factoriesfixed_closedness_range()andvariable_closedness_range(). Both types are registered in the global extension type registry. The JSON metadata uses the simdjson helpers andJsonWriter, like the other canonical extension types.pa.fixed_closedness_range(),pa.variable_closedness_range()and the matching type, array and scalar classes.Are these changes tested?
Yes, locally: the C++ tests in
arrow-canonical-extensions-test(suitesFixedClosednessRangeTypeandVariableClosednessRangeType) and the PyArrow tests intest_extension_type.pypass, including ranges over strings and pickling. I did not try the types in any other project yet.Are there any user-facing changes?
Yes, but only additions: two new canonical extension types and their C++ and PyArrow APIs. There are no breaking changes.
Was AI used for this PR?
In accordance to the AI generation guidelines, please disclose below whether and how AI was used in this PR.
PR code and description written by:
Reviewed before submission by:
Note that I made heavy use of AI to create this PR and copied many structures from the fixed shape tensor extension type. I reviewed each change and hope the changes I made are meaningful.
Nevertheless, I am not sure whether the C++ parts are comprehensive or if I missed anything; this is my first contribution to Arrow.