Skip to content

fix(python/sedonadb): resolve GeoArrow scalars with their edge type, and common scalar values - #1229

Merged
jiayuasu merged 2 commits into
apache:mainfrom
jiayuasu:fix/literal-scalar-resolution
Sep 4, 2026
Merged

fix(python/sedonadb): resolve GeoArrow scalars with their edge type, and common scalar values#1229
jiayuasu merged 2 commits into
apache:mainfrom
jiayuasu:fix/literal-scalar-resolution

Conversation

@jiayuasu

@jiayuasu jiayuasu commented Sep 3, 2026

Copy link
Copy Markdown
Member

What changes

Closes #1214.

lit() resolves Python values through a class-name → handler table with a pa.array([obj]) fallback. Two gaps in the GeoArrow handling and a set of common scalar values that the fallback rejects (or handles lossily) came up while building the sedonadb-geopandas assignment surface (#1195), where they are currently worked around before the value reaches lit(). Handling them here benefits every SedonaDB user, and lets that wrapper code shrink to a pass-through later.

GeoArrow scalars

  • The type was rebuilt from the CRS alone, so a spherical (geography) WKB scalar came back as planar geometry. The edge type now travels with the CRS.
  • Only the WKB scalar class was registered; WKT and native-encoded (point, linestring, …) scalars fell through to pa.array([obj]) and failed. Every GeoArrow scalar exposes its WKB, so one handler now serves them all, valid or null.

Scalar values

value before after
pandas.NA, numpy.ma.masked ValueError NULL
pandas.NaT ValueError timestamp NULL
pandas.Timestamp / Timedelta truncated to microseconds own resolution, time zone kept
numpy.datetime64 / timedelta64 in non-Arrow units (D, W, Y, …) ValueError lossless Arrow resolution; ambiguous or lossy units rejected the way pandas rejects them, with an overflow check
0-d numpy.ndarray ValueError its typed scalar (dtype kept)
structured numpy.void ValueError typed struct
null nested / extension Arrow scalar ValueError typed one-element array

pandas 3 renamed these classes (pandas.api.typing.NAType, pandas.Timestamp, …), so both sets are registered, the way the table already handles DataFrame and Series. Existing paths (valid Arrow scalars, arrays with dimensions, shapely, GeoSeries, pandas frames) are unchanged.

Testing

23 new tests in tests/expr/test_literal.py, run with warnings promoted to errors against pandas 2.3 / NumPy 2.3 and pandas 3.0 / NumPy 2.5. The full expr/, test_dataframe.py, and test_context.py suites pass unchanged.

…and common scalar values

The literal resolver rebuilt a GeoArrow scalar's type from its CRS
alone, so a spherical (geography) scalar came back as planar geometry,
and it registered only the WKB scalar class, so WKT and native-encoded
scalars fell through to pa.array([obj]) and failed. Carry the edge
type through and route every GeoArrow scalar class through the same
WKB path. Also resolve values that pa.array([obj]) rejects or handles
lossily: pandas.NA and numpy.ma.masked become NULL, pandas.NaT a
timestamp NULL, pandas Timestamp and Timedelta keep their full
resolution and time zone instead of being truncated to microseconds,
NumPy datetime64/timedelta64 values in any unit convert at a lossless
Arrow resolution (ambiguous or sub-nanosecond units are rejected the
way pandas rejects them, with an overflow check), 0-d arrays resolve
as their typed scalar, structured numpy.void values become structs,
and null Arrow scalars of nested or extension types resolve through
their typed one-element array. pandas 3 class names are registered
alongside the pandas 2 ones.

Closes apache#1214.


def _lit_from_wkb_and_crs(wkb, crs):
def _lit_from_wkb_and_crs(wkb, crs, edge_type=None):

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.

function name probably needs an update

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done, it's _lit_from_wkb now (named for what it builds from rather than its argument list, since the edge type joined the CRS).

# than an untyped NULL.
import pyarrow as pa

return pa.array([None], pa.timestamp("ns"))

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.

how do we know it should be ns? I supposed it doesn't matter since the system can cast it to other TS types as needed in the query?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Right on both counts. NaT itself carries no unit (it's a unit-less singleton), so some unit has to be picked, and nanoseconds is where pandas stores it and the finest one, so it can never be a lossy choice. And it does coerce: coalesce(us_col, lit(pd.NaT)) resolves to timestamp(µs) and coalesce(s_tz_col, lit(pd.NaT)) to timestamp(s, UTC), so the null takes the surrounding expression's unit and zone. Added that as a comment on the function.

… NaT unit

The helper now takes an edge type as well as a CRS, so its name names
the value it builds from rather than its arguments. Note why NaT
resolves at nanoseconds: it carries no unit of its own, nanoseconds is
where pandas stores it, and as a null it coerces to the unit and zone
the surrounding expression needs.
@jiayuasu
jiayuasu merged commit 7b2864a into apache:main Sep 4, 2026
5 checks passed
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.

lit() should resolve GeoArrow scalars with their edge type, and common missing/temporal scalars

2 participants