Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/actions/provision-meos/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@ runs:
echo "Catalog written: $CATALOG ($(wc -c < "$CATALOG") bytes)"

# Optional: build and install libmeos with all families. Mirrors the green
# all-families recipe (MobilityDB meos-arrow.yml + the four JVM bindings):
# purge the runner's preinstalled PostgreSQL and use apt.postgresql.org
# PG17 so the vendored pgPointCloud PGXS build finds clang-19.
# all-families recipe used by the JVM bindings: purge the runner's
# preinstalled PostgreSQL and use apt.postgresql.org PG17 so the vendored
# pgPointCloud PGXS build finds clang-19.
- name: Remove preinstalled PostgreSQL [prefer apt.postgresql.org]
if: inputs.build-libmeos == 'true'
shell: bash
Expand Down
42 changes: 9 additions & 33 deletions parser/extractors.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,37 +19,14 @@ def _canonical_spelling(ty) -> str:
_BOOL_SPELLINGS = {"bool", "_Bool"}


# External ABI structs that appear in the MEOS public API only at the FFI
# boundary — the Arrow C Data Interface structs. They are forward-declared
# with no field layout and carry no MEOS semantics, so a pointer to one is
# ABI-identical to ``void *``. Emitting them as ``void *`` lets every binding's
# opaque-pointer-family handling (JNR ``Pointer``, cffi ``_ffi.CData``, Go
# ``unsafe.Pointer``, .NET ``IntPtr``, rust ``*mut c_void``) wrap them
# uniformly. The idiomatic Arrow bridge — allocating the struct and importing
# it through the language's Arrow library — lives in each binding's hand-written
# layer, keyed off the ``*_to_arrow`` / ``*_from_arrow`` function name, as with
# any other opaque-pointer-family value.
_EXTERNAL_OPAQUE_STRUCTS = ("ArrowSchema", "ArrowArray")


def _demote_external_opaque(spelling: str) -> str:
# Map a pointer to an external, layout-less ABI struct to the equivalent
# ``void``-pointer spelling, preserving const qualifiers and pointer depth.
for name in _EXTERNAL_OPAQUE_STRUCTS:
spelling = re.sub(rf"\bstruct\s+{name}\b", "void", spelling)
spelling = re.sub(rf"\b{name}\b", "void", spelling)
return spelling


def find_unlisted_foreign_structs(idl) -> list:
# A MEOS type is typedef'd, so its declared ``cType`` appears bare (``Pose
# *``) in at least one signature; a foreign, forward-declared ABI struct is
# never typedef'd, so it only ever appears elaborated (``struct ArrowSchema
# *``). Any base name seen only in the elaborated form, and not already
# normalised to ``void *`` by ``_EXTERNAL_OPAQUE_STRUCTS``, is an external
# type the bindings handle divergently (permissive ones map it to a raw
# pointer, conservative ones skip it). Surface it so it is classified
# explicitly instead of silently diverging per binding.
# never typedef'd, so it only ever appears elaborated (``struct Foo *``).
# Any base name seen only in the elaborated form is an external type the
# bindings handle divergently (permissive ones map it to a raw pointer,
# conservative ones skip it). Surface it so it is classified explicitly
# instead of silently diverging per binding.
elaborated, bare = set(), set()
for fn in idl.get("functions", []):
spellings = [p.get("cType") for p in fn.get("params", [])]
Expand All @@ -61,7 +38,7 @@ def find_unlisted_foreign_structs(idl) -> list:
if not base:
continue
(elaborated if re.search(r"\bstruct\b", sp) else bare).add(base)
return sorted(elaborated - bare - set(_EXTERNAL_OPAQUE_STRUCTS))
return sorted(elaborated - bare)


def _bool_norm(spelling: str) -> str:
Expand All @@ -78,7 +55,7 @@ def _c_spelling(ty) -> str:
# in play:
# - PostgreSQL headers: ``typedef char bool`` -> spelling already ``"bool"``
# - Stub header: ``#define bool _Bool`` -> spelling is ``"_Bool"``
return _demote_external_opaque(_bool_norm(ty.spelling))
return _bool_norm(ty.spelling)


# Canonical spellings of plain C scalars/builtins.
Expand Down Expand Up @@ -136,8 +113,8 @@ def _canonical_c_spelling(ty) -> str:
return "bool"
preserved = _preserved_opaque(ty)
if preserved is not None:
return _bool_norm(_demote_external_opaque(preserved))
return _bool_norm(_demote_external_opaque(_canonical_spelling(ty)))
return _bool_norm(preserved)
return _bool_norm(_canonical_spelling(ty))


# -----------------------------------------------------------------------------
Expand Down Expand Up @@ -174,7 +151,6 @@ def _canonical_c_spelling(ty) -> str:
"meos_quadbin.h": "QUADBIN",
"meos_pointcloud.h": "POINTCLOUD",
"meos_json.h": "JSON",
"meos_arrow.h": "ARROW",
"meos_raster.h": "RASTER",
}

Expand Down
2 changes: 1 addition & 1 deletion parser/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def merge_meta(idl: dict, meta_path: Path) -> dict:
# headers themselves are unguarded, but a handful of core-header declarations
# are gated. Kept in sync with MobilityDB CMakeLists.txt's ``if(ALL) foreach``.
_ALL_FAMILIES = (
"ARROW", "CBUFFER", "H3", "JSON", "NPOINT", "POINTCLOUD", "POSE",
"CBUFFER", "H3", "JSON", "NPOINT", "POINTCLOUD", "POSE",
"QUADBIN", "RASTER", "RGEO",
)

Expand Down
9 changes: 4 additions & 5 deletions run.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,14 +171,13 @@ def main():
idl, ngrp = attach_groups(idl, MEOS_SRC)
print(f"[5/5] Attached {ngrp} doxygen @ingroup groups", file=sys.stderr)

# Surface any forward-declared external ABI struct pointer not yet
# normalised to void * (see parser.extractors._EXTERNAL_OPAQUE_STRUCTS),
# so a new one is classified explicitly instead of diverging per binding.
# Surface any forward-declared external ABI struct pointer in the API, so a
# new one is classified explicitly instead of diverging per binding.
unlisted = find_unlisted_foreign_structs(idl)
if unlisted:
print(f" WARNING: unlisted external struct pointer(s) in the API: "
f"{', '.join(unlisted)} — add to _EXTERNAL_OPAQUE_STRUCTS to map "
f"them to void * uniformly across bindings", file=sys.stderr)
f"{', '.join(unlisted)} — classify them explicitly so bindings "
f"handle them uniformly", file=sys.stderr)

# 6. Attach the temporal-covering descriptor (Parquet/Iceberg projection)
print(f" Attaching temporal covering from {COVERING_PATH}...",
Expand Down
4 changes: 2 additions & 2 deletions tests/test_family.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

OPTIONAL_FAMILIES = {
"CBUFFER", "NPOINT", "POSE", "RGEO", "H3",
"QUADBIN", "POINTCLOUD", "JSON", "ARROW", "RASTER",
"QUADBIN", "POINTCLOUD", "JSON", "RASTER",
}


Expand Down Expand Up @@ -66,7 +66,7 @@ def test_each_optional_family_is_populated(self):
# Hard guard: a healthy full-surface IDL populates every optional family;
# an empty one means the header layout or the classifier regressed.
present = {f["family"] for f in self.functions}
for family in OPTIONAL_FAMILIES - {"ARROW", "RASTER"}:
for family in OPTIONAL_FAMILIES - {"RASTER"}:
self.assertIn(family, present, f"{family} unpopulated — classifier regression?")

def test_families_are_known_labels(self):
Expand Down
Loading