feat(bindings): tgeometry parity — full surface (cumulative)#90
Closed
estebanzimanyi wants to merge 15 commits intomainfrom
Closed
feat(bindings): tgeometry parity — full surface (cumulative)#90estebanzimanyi wants to merge 15 commits intomainfrom
estebanzimanyi wants to merge 15 commits intomainfrom
Conversation
…ueTimeTiles)
Adds the single-bin getter family and the LIST<TBOX> emitters that are
the scalar counterparts of the prior bins() / tboxes() table functions.
getBin(integer, integer, integer) -> intspan
getBin(bigint, bigint, bigint) -> bigintspan
getBin(double, double, double) -> floatspan
getBin(date, interval, date) -> datespan
getBin(timestamptz, interval, timestamptz) -> tstzspan
valueTiles(tbox, vsize [, vorigin]) -> LIST<tbox>
timeTiles(tbox, duration [, torigin]) -> LIST<tbox>
valueTimeTiles(tbox, vsize, duration
[, vorigin, torigin]) -> LIST<tbox>
The tbox emitters dispatch on tbox->span.spantype so the same SQL
function transparently handles TBOXINT and TBOXFLOAT inputs without
the user having to pick a numeric variant. Defaults match MobilityDB's
SQL: vorigin = 0, torigin = '2000-01-03'.
Test 025_temporal_tile_getters.test exercises all five getBin variants
and the three emitters across both int and float tboxes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous PR landed extent() for the 5 span types only. This expands
the same aggregate to cover the full MobilityDB SQL surface for fixed-
size-state extent variants (still avoiding the SkipList-state aggregates
like tcount / tmin / tcentroid / merge / appendInstant — those need a
larger lift).
Added (15 new overloads on the same `extent` aggregate set):
- extent(integer | bigint | double | date | timestamptz) -> typed span
- extent(intset | bigintset | floatset | dateset | tstzset) -> typed span
- extent(intspanset | bigintspanset | floatspanset
| datespanset | tstzspanset) -> typed span
- extent(tbox) -> tbox
- extent(stbox) -> stbox
- extent(tint | tfloat) -> tbox
- extent(tbool | ttext) -> tstzspan
- extent(tgeompoint) -> stbox
All variants share a small set of templated state types — Span, TBox,
STBox — and dispatch on the appropriate MEOS *_extent_transfn for the
input. Combine functions use span_extent_transfn / tbox_expand /
stbox_expand so parallel-agg merge works.
Tests in 030_aggregates_extent.test cover one assertion per category
plus NULL handling.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…, tsum, tavg, tcentroid)
Adds the harness for MEOS SkipList-state temporal aggregates and ships
seven of them. SkipList aggregates differ from the fixed-state extent()
family in two ways:
1. State holds a `SkipList *` pointer; the skiplist owns variable-size
heap-allocated Temporal* values, so the aggregate needs a destructor
(registered via UnaryAggregateDestructor with LEGACY destructor type).
2. Combine merges two skiplists via MEOS's `temporal_tagg_combinefn`,
which is exported from libmeos but not declared in the public
headers, so we forward-declare it here. Same goes for the per-
aggregate `datum_*` merge functors.
The MEOS combine semantics share Temporal* pointers between the two
input skiplists when both are non-empty: the returned skiplist holds
the merged elements while the "loser" skiplist still references the
same pointers. To avoid double-frees we walk the loser's elements and
NULL out keys/values before calling skiplist_free (which then only
releases the SkipList struct + freed[] array + elem array).
Implemented aggregates (single-input only — window aggregates and
extra base-type overloads are deferred):
tand(tbool) -> tbool
tor(tbool) -> tbool
tcount(tbool|tint|tfloat|ttext|tgeompoint) -> tint
tsum(tint|tfloat) -> same input type
tavg(tint|tfloat) -> tfloat
tcentroid(tgeompoint) -> tgeompoint
Deferred this PR:
- tmin / tmax — collide with the existing scalar Tmin(tbox|stbox) /
Tmax(tbox|stbox) under DuckDB's case-insensitive name resolution.
DuckDB rejects mixing scalar+aggregate overloads on the same
canonical name (raises "GetAlterInfo not implemented for this type").
Resolving needs either renaming the box scalars or aggregate
suffixing — out of scope for this PR.
- merge / appendInstant / appendSequence
- spanUnion / setUnion (need different state shape — span/set
skiplist instead of temporal)
- Window aggregates (wmin / wmax / wsum / wcount / wavg) — need an
extra interval argument
- tcentroid 3D dispatch — currently always uses datum_sum_double3 (2D)
- tcount over scalar timestamptz / tstzset / tstzspan / tstzspanset
inputs (use timestamptz_tcount_transfn etc.)
Test plan: 031_aggregates_skiplist.test (14 assertions) — one per
registered aggregate plus NULL/empty handling.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…g/TmaxAgg Rename every registered SkipList aggregate to its Pascal-cased *Agg identifier per the proposal in MobilityDB issue #827 / PR #828: tand -> TandAgg tor -> TorAgg tcount -> TcountAgg tsum -> TsumAgg tavg -> TavgAgg tcentroid -> TcentroidAgg The new naming convention disambiguates SkipList aggregates from the same-stem scalar accessors (e.g. `Tmin(tbox)`) under DuckDB's case- folding catalog. With the rename in place the previously-blocked TminAgg / TmaxAgg now drop in cleanly, so this commit also adds them: TminAgg(tint | tfloat | ttext) -> same-typed temporal min TmaxAgg(tint | tfloat | ttext) -> same-typed temporal max The MobilityDB upstream PR #828 lands the matching aliases on the PG side; the original lowercase / camelCase names remain valid in PG for backward compat but collide in DuckDB and are intentionally not registered here. Test 031_aggregates_skiplist.test exercises all 11 aggregates (4 TcountAgg overloads + TandAgg + TorAgg + 3 TminAgg + 3 TmaxAgg + 2 TsumAgg + 2 TavgAgg + TcentroidAgg) plus NULL handling. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tValueTimeTile + spatial
Adds the per-input lookup form of the tile family as a counterpart to
the LIST<TBOX>/LIST<STBOX> emitters previously shipped as
valueTiles / timeTiles / valueTimeTiles / spaceTiles / etc.
Temporal (3 names, 5 overloads):
getValueTile(double, double [, double=0.0]) -> tbox
getTBoxTimeTile(timestamptz, interval
[, timestamptz='2000-01-03']) -> tbox
getValueTimeTile(double, timestamptz, double, interval
[, double=0.0, timestamptz='2000-01-03']) -> tbox
Spatial (3 names, 6 overloads):
getSpaceTile(geom, xsize) -> stbox (uniform xyz)
getSpaceTile(geom, xsize, ysize, zsize [, sorigin]) -> stbox
getStboxTimeTile(timestamptz, interval
[, timestamptz='2000-01-03']) -> stbox
getSpaceTimeTile(geom, t, xsize, ysize, zsize, interval
[, sorigin, torigin]) -> stbox
All temporal variants funnel through the MEOS internal
`tbox_get_value_time_tile`. The basetype/spantype hint to MEOS comes
from the first argument: T_FLOAT8/T_FLOATSPAN for value-bearing
variants, T_TIMESTAMPTZ/T_TSTZSPAN for the time-only getter (this
detail is what mirrors how the MobilityDB PG layer derives the basetype
from the first SQL arg in `Tbox_get_value_time_tile_common`).
Spatial variants call `stbox_get_space_tile` / `stbox_get_time_tile` /
`stbox_get_space_time_tile` directly with a default-cached
`Point(0 0 0)` GSERIALIZED used when no explicit sorigin is supplied.
Test 026_single_tile_getters.test exercises every shape (10 assertions).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…dow aggs / time-only TcountAgg / TcentroidAgg 3D dispatch
Completes the SkipList-aggregate surface in scope for this PR.
Added (24 new aggregate overloads on top of the previous 11):
MergeAgg(tbool|tint|tfloat|ttext|tgeompoint) -> same input
AppendInstantAgg(...) / AppendSequenceAgg(...) -> same input
(Temporal-state harness, distinct from the SkipList-state harness;
state holds a Temporal* directly and combine merges via temporal_merge.)
SpanUnionAgg(<typed-span> | <typed-spanset>) -> typed spanset
(SpanSet-state harness via span_union_transfn / spanset_union_transfn /
spanset_union_finalfn.)
SetUnionAgg(<scalar> | <typed-set>) -> typed set
(Set-state harness — value_union_transfn for scalars, set_union_transfn
for sets. Forward-declares value_union_transfn from meos_internal.h.)
WminAgg / WmaxAgg / WsumAgg / WavgAgg(tnumber, interval) -> same numeric type
WcountAgg(tnumber, interval) -> tint
(Binary-aggregate harness over (Temporal, Interval). WcountAgg uses
temporal_wagg_transform_transfn + temporal_transform_wcount,
forward-declared since they're not in installed public headers.)
TcountAgg(timestamptz | tstzset | tstzspan | tstzspanset) -> tint
(Distinct transfns: timestamptz_tcount_transfn / tstzset_*tcount_transfn /
tstzspan_tcount_transfn / tstzspanset_tcount_transfn.)
Fixed: TcentroidAgg's 3D dispatch was reading the wrong byte of
SkipList::extra (treated the LSB of `srid` as `hasz`). Now reads
GeoAggregateState{int32 srid; bool hasz;} via a mirror struct.
Test 031_aggregates_skiplist.test grew from 18 to 35 assertions covering
every shape.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… / modifiers / comparison Mirrors the temporal-generic portion of the tgeompoint surface for tgeometry. Most MEOS C functions reached here are subtype-agnostic (they take Temporal *), so the registrations reuse the same generic TemporalFunctions::* handlers that tgeompoint uses, with TGEOMPOINT() swapped for TGEOMETRY() and the value type swapped from POINT to GEOMETRY. Functions added (~50 overloads on top of the existing 22): - Accessors: valueSet, valueN, valueAtTimestamp, getTime, duration, lowerInc, upperInc, numInstants, instants, numSequences, sequences, startSequence, endSequence, sequenceN, numTimestamps, timestamps, startTimestamp, endTimestamp, timestampN, segments - Time-domain restrict: atTime / minusTime over (timestamptz, tstzset, tstzspan, tstzspanset); beforeTimestamp / afterTimestamp - Value restrict: atValues / minusValues over (geometry, geomset) - Spatial restrict: atGeometry, minusGeometry, minusStbox (reusing the tgeompoint-side Tgeo_* handlers — they already operate on Temporal *) - Modifiers: shiftTime, scaleTime, shiftScaleTime, appendInstant, appendSequence, insert, update, deleteTime over multiple time argument shapes - Comparison: temporal_eq / ne / lt / le / gt / ge / cmp + the matching =, <>, <, <=, >, >= operators Out of scope for this PR (planned follow-ups): - tgeometry comparison ops with geometry arg, distance / spatialrels / temporal-spatialrels (the bigger 060 / 064 / 070 / 072 SQL files) - tgeometry tile family + analytics + aggregates + GiST / SP-GiST - tgeography parity (entirely separate type registration) Test: 040_tgeometry_foundations.test — 19 assertions covering each category. Geometry values are emitted in EWKB-hex display rather than WKT, mirroring how tgeompoint test outputs are compared. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ps / spatial-rels / temporal-rels / distance)
Extends the foundational tgeometry surface from the previous commit
with the cross-type predicate family that mirrors what MobilityDB
ships in 060 / 062 / 070 / 072 / 064_tgeo_*.in.sql:
Box predicates (named function + operator):
temporal_overlaps / && over (tgeometry × stbox / tstzspan / tgeometry)
temporal_contains / @> over the same cross-product
temporal_contained / <@
temporal_same / ~=
temporal_adjacent / -|-
Position predicates (16 names — left / right / below / above / front /
back / before / after and their over-* variants) over (tgeometry ×
stbox / tgeometry); the four time-axis ones (before / after /
overbefore / overafter) also accept tstzspan.
Spatial relationships:
eContains / aContains (tgeometry, geometry, tgeometry)
eDisjoint / aDisjoint commutative (geo, tgeo) reuses (tgeo, geo)
eIntersects / aIntersects same
eTouches / aTouches same
eDwithin / aDwithin with float distance argument
Temporal spatial relationships (return tbool):
tContains, tDisjoint, tIntersects, tTouches over (geo, tgeo) /
(tgeo, geo) / (tgeo, tgeo); tDwithin with float distance.
Distance:
tdistance(tgeometry, geometry|tgeometry) -> tfloat
<-> operator equivalents.
The MEOS exports reached here all take Temporal * (subtype-agnostic),
so this file is purely registration glue — templated executors decode
the input blobs, call the MEOS function, and re-encode the result.
Test 040_tgeometry_parity.test grew from 19 to 33 assertions and was
renamed from 040_tgeometry_foundations.test to reflect the broader
scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…orm / coercions / centroid / convexHull / traversedArea Adds the spatial-functions surface from MobilityDB's 056_tgeo_spatialfuncs.in.sql for tgeometry: SRID(tgeometry) -> integer setSRID(tgeometry, integer) -> tgeometry transform(tgeometry, integer) -> tgeometry stbox(tgeometry) -> stbox tgeompoint(tgeometry) -> tgeompoint tgeometry(tgeompoint) -> tgeometry centroid(tgeometry) -> tgeometry (temporal centroid path) convexHull(tgeometry) -> geometry traversedArea(tgeometry [, bool]) -> geometry All MEOS exports reached here (tspatial_srid / tspatial_set_srid / tspatial_transform / tspatial_to_stbox / tgeometry_to_tgeompoint / tgeompoint_to_tgeometry / tgeo_centroid / tgeo_convex_hull / tgeo_traversed_area) take a Temporal *, so the new exec helpers in tgeometry_ops.cpp follow the same decode/call/encode pattern as the predicate executors above. Tests: 040_tgeometry_parity.test grew from 33 to 39 assertions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wraps the comprehensive tgeometry parity sweep with three more categories on top of the cumulative base (which already includes the foundational, predicate, and spatial-functions commits plus the prerequisite work from PRs #85 / #86 / #87 / #88): Aggregate wiring (in span_aggregates.cpp + temporal_aggregates.cpp): extent(tgeometry) -> stbox (new tgeometry input via tspatial_extent_transfn) TcountAgg(tgeometry) -> tint MergeAgg(tgeometry) -> tgeometry AppendInstantAgg(tgeometry) -> tgeometry AppendSequenceAgg(tgeometry) -> tgeometry Tile / box emitters (in tgeometry_ops.cpp): spaceBoxes(tgeometry, x, y, z) -> LIST<stbox> spaceTimeBoxes(tgeometry, x, y, z, interval) -> LIST<stbox> Analytics (in tgeometry_ops.cpp) — registered for parity even though MEOS internally rejects non-numeric / non-point temporal inputs at runtime (same constraint as MobilityDB's PG-side): minDistSimplify(tgeometry, double) minTimeDeltaSimplify(tgeometry, interval) maxDistSimplify(tgeometry, double[, bool]) douglasPeuckerSimplify(tgeometry, double[, bool]) Test 040_tgeometry_parity.test grew from 39 to 45 assertions covering aggregates and tile emitters. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 1, 2026
Member
Author
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.
Summary
The comprehensive
tgeometryparity PR, packaging everything the tgeometry surface depends on into a single review unit. Stacks PR #85 / #86 / #87 / #88 underneath, then layers the tgeometry-specific work on top.This supersedes PR #89 — same content, but as one cohesive PR rather than a stack.
What's included
getBin, tbox tile emitters,extent()expansion, all*Aggaggregates per RFC #827, single-tile getterssrc/geo/tgeometry.cppvalueSet,valueN,valueAtTimestamp,getTime,duration,lowerInc,upperInc,numInstants,instants,numSequences,sequences,startSequence,endSequence,sequenceN,numTimestamps,timestamps,startTimestamp,endTimestamp,timestampN,segmentssrc/geo/tgeometry.cppatTime/minusTimeover(timestamptz, tstzset, tstzspan, tstzspanset);beforeTimestamp,afterTimestampsrc/geo/tgeometry.cppatValues/minusValuesover(geometry, geomset)src/geo/tgeometry.cppatGeometry,minusGeometry,minusStboxsrc/geo/tgeometry.cppshiftTime,scaleTime,shiftScaleTime,appendInstant,appendSequence,insert,update,deleteTimesrc/geo/tgeometry.cpptemporal_eq/ne/lt/le/gt/ge/cmp+=,<>,<,<=,>,>=src/geo/tgeometry_ops.cpp(new)temporal_overlaps/contains/contained/same/adjacent+&&,@>,<@,~=, `-src/geo/tgeometry_ops.cpptemporal_left/right/below/above/front/back/before/afterand theirover-*variants) over(tgeometry × stbox / tgeometry); the four time-axis ones also accepttstzspansrc/geo/tgeometry_ops.cppeContains/aContains/eDisjoint/aDisjoint/eIntersects/aIntersects/eTouches/aTouches/eDwithin/aDwithinsrc/geo/tgeometry_ops.cpptContains,tDisjoint,tIntersects,tTouches,tDwithin(returntbool)src/geo/tgeometry_ops.cpptdistanceand<->over(tgeometry × geometry / tgeometry)src/geo/tgeometry_ops.cppSRID,setSRID,transform,stbox,tgeompoint(tgeometry)/tgeometry(tgeompoint)coercions,centroid,convexHull,traversedAreasrc/temporal/span_aggregates.cpp,src/temporal/temporal_aggregates.cppextent(tgeometry)→ stbox;TcountAgg(tgeometry)→ tint;MergeAgg(tgeometry);AppendInstantAgg(tgeometry);AppendSequenceAgg(tgeometry)src/geo/tgeometry_ops.cppspaceBoxes(tgeometry, x, y, z)andspaceTimeBoxes(tgeometry, x, y, z, interval)returningLIST<stbox>src/geo/tgeometry_ops.cppminDistSimplify,minTimeDeltaSimplify,maxDistSimplify,douglasPeuckerSimplify— registered for parity; MEOS internally rejects non-numeric / non-point inputs at runtime, same as MobilityDBOut of scope (planned follow-ups)
tgeography— separate type registration; will follow this PR using the patterns established here.asEWKB/asHexEWKB/asMFJSON/ matching parsers) for tgeometry — depends on PR feat(bindings): tgeompoint round-trip I/O — Binary/EWKB/HexWKB/HexEWKB/MFJSON #83 landing first.spaceSplit/spaceTimeSplitTableFunctions for tgeometry — depends on PR feat(bindings): geo/058 tpoint tile — tiles/boxes/getters + spaceSplit/spaceTimeSplit TableFunctions #79 landing first.Test plan
test/sql/parity/040_tgeometry_parity.test— 45 assertions covering every category🤖 Generated with Claude Code