Skip to content

Advance the facade to the ecosystem-pin-2026-06-22a MEOS surface#28

Closed
estebanzimanyi wants to merge 23 commits into
MobilityDB:mainfrom
estebanzimanyi:feat/facade-surface-22a
Closed

Advance the facade to the ecosystem-pin-2026-06-22a MEOS surface#28
estebanzimanyi wants to merge 23 commits into
MobilityDB:mainfrom
estebanzimanyi:feat/facade-surface-22a

Conversation

@estebanzimanyi

Copy link
Copy Markdown
Member

Regenerates codegen/input/meos-idl.json and the generated facade from the MEOS-API catalog at ecosystem-pin-2026-06-22a (4463 functions, 89 structs, 20 enums), and absorbs the two MEOS surface changes since the pin-15c regen.

Generator: managed wrapper for the value out-param family

The *_value_at_timestamptz accessors renamed their out-param resultvalue. FunctionsGenerator now recognises value as an output-result parameter, so it emits the managed three-argument wrapper (allocate the buffer, call the boolean native, return the typed value) uniformly for tbool, tint, tfloat, tbigint, ttext, tcbuffer, tjsonb, and tnpoint. Detection is gated on a pointer C type so a by-value Datum input named value (e.g. contains_set_value) is never mistaken for an out-param, and the forwarded native argument is decoupled from the parameter name.

OO call sites adapted to the catalog

  • spanset_spans no longer takes the count out-param; the .spans() accessors drop the stale argument.
  • The value / time split operations now return the Temporal ** fragment array directly and report the bins and the fragment count through out-params; value_split, value_time_split, and time_split allocate those buffers and read the count.

Tests

Adds runtime coverage for the value accessors and the split operations (previously uncovered). Full jmeos-core suite: 1739 tests, 0 failures, 0 errors against an all-families libmeos built at the pin.


Stacks on the open facade-regen chain (#27 pin-12l) and the pushed pin-14l / pin-15c regens, which do not yet have their own PRs — their commits appear in this PR's cumulative diff against main.

… surface

Bump codegen/input/meos-idl.json to the MEOS-API IDL and regenerate
functions.GeneratedFunctions over the full consolidated superset: mul_* (incl.
tbigint); minDistance; the circular-buffer and network-point MF-JSON readers; the
ever- and always-covers families (ecovers_*/acovers_*); trgeo_*; the H3 /
th3index family (ever_eq_h3indexset_th3index, h3index_in/out, H3Index lowered to
long); PostgreSQL type I/O; tgeogpoint_great_circle_distance;
meos_initialize_noexit_error_handler. 2916 functions.
Add the codegen exec step to jmeos-core so functions/GeneratedFunctions.java is
produced from codegen/input/meos-idl.json during the build, and drop the
committed copy.
Run meos_initialize() from the binding's load path so the allocator and
collation are installed before any call. MEOS splits startup into granular
steps; without the collation step, text comparisons route into varstr_cmp with
an uninitialized collation and crash.
Call the generated functions class from TText and TextSet instead of the
hand-maintained facade, so the text conversions use the canonical master names
(cstring_to_text, text_to_cstring).
…ld flags

The functions.GeneratedFunctions facade is generated at build time from the MEOS
IDL with the optional type families selected by the same flag names and ON|OFF
(also 1|0) values as the MobilityDB/MEOS build: -DCBUFFER, -DNPOINT, -DPOSE,
-DRGEO, -DH3. Every family is included by default; passing -DCBUFFER=OFF (or =0)
drops that family's functions from the generated binding so a subset jar ships
without it (RGEO needs POSE). FunctionsGenerator maps each function's source
header to its family and omits excluded families; jmeos-core runs the generator
at generate-sources (so the flag flows through mvn) and compiles the generated
functions.GeneratedFunctions.
Hoists the tier-aware MeosOps* facade (62 classes) into JMEOS so every
JVM binding inherits the one canonical Java idiom from the shared jar
instead of duplicating it per engine. The facade forwards to
functions.GeneratedFunctions under a package-private MeosOpsRuntime probe
gated by the canonical -Dmeos.enabled property; javadoc is engine-neutral.
Relocates the maintained generator (regen_facade_from_jar + the gap / sql /
tbigint / h3 emitters + parity_audit + meos-ref) under jmeos-core/tools so
the facade stays regenerated, not hand-edited; regeneration is idempotent
against the pin jar.
…surface

Apply the 77-symbol drift from pin 588768d7ab to master: 20 trgeo→trgeometry
renames (A); 12 pg_*/cstring2text/text2cstring/pose_normalise/temporal_instants_p/
temporal_hausdorff_distance renames (B); 21 removed scalar helpers dropped with
no callers (C: add_date_int, add_interval_interval, add_timestamptz_interval,
date_to_timestamp, date_to_timestamptz, float_exp, float_ln, float_log10,
float_round, int32_cmp, int64_cmp, interval_make, mul_interval_double,
timestamp_to_date, timestamptz_shift, timestamptz_to_date, minus_date_date,
minus_date_int, minus_timestamptz_interval, minus_timestamptz_timestamptz,
meos_initialize_pointcloud); 23 signature updates (D): value_at_timestamptz
gains a result out-param and returns bool; value_split/value_time_split/
temporal_time_split gain bins/count out-params; set_spans/spanset_spans/
spanset_spanarr drop the count out-param; super_union_span_span drops the
boolean merge flag; text_cmp gains a collation pointer; gbox_make gains hasm
and geodetic booleans and min/max range pairs; geom_min_bounding_radius gains
a radius out-param; pose_make_2d/3d gain a geodetic boolean; trgeoinst_make is
renamed trgeometryinst_make. All xxx_hash_extended functions change their seed
and return type from long to int (bigint_to_span/spanset likewise). 1735 tests
pass, BUILD SUCCESS.
MeosSetSetJoin exposes the MEOS *_tgeoarr_tgeoarr family as eDwithinPairs /
tDwithinPairs / aDisjointPairs over two arrays of temporal-geometry handles:
it marshals the native pointer arrays the kernel prunes in C, keeps them
reachable across the call with reachabilityFence, and reads back the
flattened 0-based index pairs (and, for tDwithin, the per-pair tstzspanset of
in-range times). Both JVM engines call it from the shared org.mobilitydb.meos
layer, so the NxN spatial-join surface derives once. Verified against
libmeos.
The IDL and bundled libmeos carry the 54a9d4bc54 public surface: the per-thread
PROJ context, the box3d_in/gbox_in parsers, and tpose_to_tpoint. The parity-gap
forwarders bind the value-at-timestamptz wrappers through their result-returning
form and drop the pointcloud initializer absent from the surface.
Add extract_named_surface.py, which produces meos-named-surface.json from the two
canonical sources already in the MobilityDB tree: the SQL CREATE FUNCTION catalog
(named functions, overloads, per-argument DEFAULTs -> valid call arities) and the
doxygen chain (@sqlfn on the PG wrapper, @csqlfn on the MEOS function) linking
each SQL name to its PG and MEOS C functions. This is the layer above the C-FFI
IDL from which a binding's named surface and its Spark Connect registrar are
generated, rather than hand-maintained. 1284 named functions, asMFJSON resolving
to temporal_as_mfjson with minArity 1 / maxArity 4.
…tter

extract_spark_impls.py scans the MobilitySpark UDFs (register name + field +
body GeneratedFunctions call) and joins on the named surface's SQL->MEOS C
linkage to recover canonical name -> Spark impl mechanically, so the emitter
needs no hand-written remap. The join classifies each function for emission:
single-impl (identity name over one impl), multi-impl (identity name with a
WKB-type-tag dispatch builder), and join gaps to close.
…face

generate_spark_registrar.py joins the canonical named surface with the Spark impl
scan and emits MobilitySparkConnectExtensionsGen.scala: a SparkSessionExtensions
that injects each canonical function under its identity name (asMFJSON, not
temporalAsMfjson), no hand-written remap. Shipped ScalaUDF closures live in a
companion object so they capture only the serializable UDF; the builder null-pads
the impl's optional args to the call-site arity. The 81 single-impl functions are
generated, compiled, and serve live over Spark Connect under their identity names;
the 139 multi-impl names are listed for the per-row meos_typeof_hexwkb dispatch.
…trar

A multi-impl canonical name (one SQL name over several type-specific Spark impls)
whose first argument differs in MEOS type is emitted as a single ScalaUDF that
peeks meostype_name(meos_typeof_hexwkb(arg0)) per row and routes to the impl whose
receiver type matches, with the Temporal-receiver impl as the catch-all default.
The receiver category is read from the impl's primary MEOS function (the last
non-marshaling GeneratedFunctions call in the UDF body) first C-parameter type.

The registrar serves the /items-collection OGC function set under identity names:
asMFJSON, stbox, the Xmin/Ymin/Xmax/Ymax/Tmin/Tmax accessors, numSequences,
sequenceN, trajectory. Functions that differ only on a later argument (atTime on
its time argument) are listed for the arg-N dispatch extension.
…th the SQL default

Several MobilitySpark UDFs register one MEOS operation under both a bare name and a
camelCase name (asText/tpointAsText, getTime/time, cumulativeLength/...). When a
canonical name's impls all share one primary MEOS function, bind the identity name
to a single impl rather than treating it as a type dispatch.

Capture each optional argument's SQL DEFAULT literal in the named surface and fill
an omitted optional argument with it, but only when the impl exposes a full
overload's worth of arguments (impl arity equals that overload's maxArity, so the
positions align); otherwise null-pad and let the impl's own default hold. This
serves asText/asEWKT (maxdecimaldigits default 15) while keeping asMFJSON at full
coordinate precision (its impl exposes a non-leading argument subset).
Generalize the multi-impl dispatch from arg0 to the first argument position at
which the type-specific impls differ, peeking that argument's MEOS type tag: atTime
routes on its time argument (tstzspan/tstzset/tstzspanset), duration routes on its
span argument. The concrete type for a generic Span/Set receiver is taken from the
MEOS type-name embedded in the impl's primary function name.

Each dispatch route carries its impl's arity and SQL-default fills, so an omitted
optional argument of the chosen impl is filled with the canonical default rather
than a null pad (duration on a tstzspanset supplies boundspan=FALSE); boolean SQL
defaults are emitted alongside integer ones.

The named surface is regenerated from the pin, so speed(tgeompoint) resolves
through the dedicated Tpoint_speed wrapper to tpoint_speed and binds to the speed
impl.
Functions that return a struct larger than 16 bytes by value (the seven
*Split returns plus MvtGeom) use the SysV/AArch64 sret calling convention:
the caller allocates the struct and passes a hidden pointer as an implicit
first argument; the callee fills it and returns it. The emitter previously
collapsed such a return to a bare Pointer, so jnr-ffi mis-read the return
register and the struct fields (notably count) came back as garbage.

Parse the IDL "structs" section, compute each struct's size, and for a
by-value return larger than 16 bytes emit a hidden leading Pointer _sret
parameter in the interface, allocate it in the wrapper, and return the
filled buffer. Register-returned structs (<=16B) are logged, not silently
mis-bound. Regenerates GeneratedFunctions with the new bindings.
Repoint the last three main-code classes from the hand-rolled functions.functions
facade onto the generated GeneratedFunctions surface, completing the main-code
side of the dual-facade wipe. No main-code class imports functions.functions now.

Renames and reshapes applied (verified against the generated signatures):
- pg_timestamptz_in/pg_interval_in -> timestamptz_in/interval_in (identical sigs).
- the temporal spatial-relationship calls (tcontains/tdisjoint/tdwithin/
  tintersects/ttouches) drop the trailing restr,atvalue booleans the current
  MEOS signatures no longer take (dwithin keeps its distance argument).
- value_at_timestamp uses the generated bool+out-param wrapper, which returns the
  GSERIALIZED* directly, instead of reading the out buffer at the wrong offset.

Defects surfaced and fixed while migrating (all confirmed via smoke):
- count out-parameters were read at offset 4 (getInt(Integer.BYTES)) instead of 0
  in values/make_simple/stboxes, yielding out-of-bounds garbage counts.
- Memory.allocate(Runtime.getRuntime(runtime), n) threw ClassCastException
  (Runtime.getRuntime expects a library proxy, not a Runtime); pass runtime.

Smoke: values=3, make_simple=1, stboxes=2, value_at=POINT(5 5). Type suites
TGeomPoint/TGeogPoint/TInt/TFloat/TBool all green (624 tests, 0 fail/0 err); the
only residual is the pre-existing varstr_cmp ttext_in crash in TTextTest.
Repoint value_split / value_time_split / time_split / space_split /
space_time_split (TNumber, Temporal, TPoint) onto the generated
GeneratedFunctions split wrappers, which now return the *Split struct via
the sret convention. The methods read fragments at offset 0 and count at
the struct's count offset (16 for the 3-field splits, 24 for the 4-field
time splits) instead of the stale pre-735f out-parameter signature.

Also fixes defects surfaced while migrating:
- duration/start were parsed in an inverted branch so the duration was
  dropped (or null-dereferenced) on the default-start path; parse the
  duration unconditionally and default only the start.
- timedelta_to_interval passed cumulative units (toHours/toMinutes/
  toSeconds give the whole span in each unit) to interval_make, which then
  re-added them on top of the days. It now decomposes per field and parses
  a textual interval with interval_in, sidestepping a jnr-ffi quirk that
  mis-passes interval_make's trailing double after its six int arguments.

Verified end to end on both the Duration and the String duration paths:
all five split methods return the correct fragment counts through the OO API.
… public)

Wipe step 10: STBox onto the generated facade. gserialized_in -> geom_in
(identical sig); geo_expand_spatial(gs, d) -> stbox_expand_space(geo_to_stbox(gs),
d) (the public composition). 77 STBox tests green.
Wipe step 9: tstzspan (adjacent_period_timestamp -> adjacent_span_timestamptz,
pg_timestamptz_in -> timestamptz_in) and tstzset (timestampset_out ->
tstzset_out; tstzset_values gained a count out-param) onto the generated facade.
…+ value_at)

Wipe step 8: TText onto the generated facade. cstring2text/text2cstring ->
cstring_to_text/text_to_cstring (now public in 11g). value_at reshaped: the
generated ttext_value_at_timestamptz returns the text* directly (or null), so
read it via text_to_cstring; null-safe (throws on no-value, was the offset-8
garbage read). Verified "hello" via smoke.
Repoint every test from functions.functions to the generated GeneratedFunctions
surface (the calls are same-name same-signature, so this is a mechanical repoint)
and remove the hand-rolled functions.java. The dual-facade irregularity is gone:
the whole library — main code and tests — now uses the single generated facade.
The other functions-package classes (GeneratedFunctions, the Meos* error types,
error_handler/error_handler_fn) are unaffected.

Full suite after deletion: 1735 tests, 0 failures, 0 errors, 0 native crashes.
@estebanzimanyi

Copy link
Copy Markdown
Member Author

Superseded by #30, which regenerates at the current MobilityDB master (4529 functions), newer than the 2026-06-22a pin. The shared facades are consumer projections generated in the consumers. Closing.

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.

1 participant