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
31 changes: 31 additions & 0 deletions parser/portable.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,34 @@ def attach_portable_aliases(idl: dict, path: Path) -> dict:
"count": len(pairs),
}
return idl


def classify_backing_sqlfn(idl: dict) -> dict:
"""Mark the bounding-box topological BACKING ``@sqlfn`` tags.

MobilityDB backs the five topological operators (~=/@>/<@/-|-/&&) with a SHARED C
``@sqlfn`` tag named ``<op>_bbox`` (same_bbox, contains_bbox, contained_bbox,
overlaps_bbox, adjacent_bbox). That tag is NEVER emitted as a ``CREATE FUNCTION`` —
the deployed, user-facing SQL name is the operator's bare portable alias
(same/contains/…). The raw ``sqlfn`` is therefore a backing name, not a public one;
a binding that registers it leaks a function MobilityDB does not expose. Flag those
records with ``sqlfnBackingOnly`` + the ``publicSqlName`` (the bare alias) so every
binding uniformly registers the bare name + operator and drops the ``_bbox`` tag.

Not a heuristic: grounded in two catalog-native facts — the ``_bbox`` shared-backing
convention AND the operator→bareName map. ``publicSqlName`` is always defined because
every ``_bbox`` sqlfn carries one of the five topological operators.

MUST run AFTER ``attach_sqlfn_map`` (sqlfn/sqlop) AND ``attach_portable_aliases``
(byOperator) — it reads all three.
"""
by_operator = (idl.get("portableAliases") or {}).get("byOperator") or {}
if not by_operator:
return idl
for f in idl.get("functions", []):
sqlfn = f.get("sqlfn") or ""
op = f.get("sqlop") or ""
if sqlfn.endswith("_bbox") and op in by_operator:
f["sqlfnBackingOnly"] = True
f["publicSqlName"] = by_operator[op]
return idl
11 changes: 10 additions & 1 deletion run.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from pathlib import Path

from parser.parser import parse_all_headers, merge_meta
from parser.portable import attach_portable_aliases
from parser.portable import attach_portable_aliases, classify_backing_sqlfn
from parser.covering import attach_temporal_covering
from parser.typerecover import recover_collapsed_types
from parser.header_types import reconcile
Expand Down Expand Up @@ -110,6 +110,15 @@ def main():
for _lo, spellings in case_bad:
print(f" {' vs '.join(spellings)}", file=sys.stderr)

# Now that both the @sqlfn/@sqlop map (step 4) and the portable bare-name map
# (step 3) are attached, classify the shared bbox-topological BACKING tags
# (same_bbox/contains_bbox/…) so bindings register the bare public name, not the
# catalog-only backing tag.
idl = classify_backing_sqlfn(idl)
nbo = sum(1 for f in idl.get("functions", []) if f.get("sqlfnBackingOnly"))
print(f" Flagged {nbo} bbox-topological backing @sqlfn tag(s) "
f"(sqlfnBackingOnly)", file=sys.stderr)

# 5. Attach the doxygen module group (@ingroup) from the vendored source, so
# bindings organize their generated surface like the reference manual.
if MEOS_SRC.exists():
Expand Down
30 changes: 29 additions & 1 deletion tests/test_portable.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))

from parser.portable import attach_portable_aliases
from parser.portable import attach_portable_aliases, classify_backing_sqlfn

MAP = ROOT / "meta" / "portable-aliases.json"
SCHEMA = ROOT / "meta" / "portable-aliases.schema.json"
Expand Down Expand Up @@ -120,6 +120,34 @@ def test_missing_file_is_noop(self):
idl = attach_portable_aliases({"x": 1}, ROOT / "nope.json")
self.assertNotIn("portableAliases", idl)

def test_backing_sqlfn_classification(self):
# A bbox-topological function carries a shared `<op>_bbox` @sqlfn backing tag
# that is never deployed as a CREATE FUNCTION; its public name is the operator's
# bare alias. classify_backing_sqlfn must flag it and record publicSqlName.
idl = attach_portable_aliases({"functions": [
{"name": "Same_stbox_stbox", "sqlfn": "same_bbox", "sqlop": "~="},
{"name": "Contains_tbox_tnumber", "sqlfn": "contains_bbox", "sqlop": "@>"},
{"name": "Left_stbox_stbox", "sqlfn": "temporal_left", "sqlop": "<<"},
{"name": "Tpoint_trajectory", "sqlfn": "trajectory"},
]}, MAP)
idl = classify_backing_sqlfn(idl)
by = {f["name"]: f for f in idl["functions"]}
# the two _bbox backing tags are flagged with the bare public name
self.assertTrue(by["Same_stbox_stbox"]["sqlfnBackingOnly"])
self.assertEqual(by["Same_stbox_stbox"]["publicSqlName"], "same")
self.assertTrue(by["Contains_tbox_tnumber"]["sqlfnBackingOnly"])
self.assertEqual(by["Contains_tbox_tnumber"]["publicSqlName"], "contains")
# a positional op whose @sqlfn IS the deployed name (temporal_left) is untouched
self.assertNotIn("sqlfnBackingOnly", by["Left_stbox_stbox"])
# a plain function with no operator is untouched
self.assertNotIn("sqlfnBackingOnly", by["Tpoint_trajectory"])

def test_backing_sqlfn_noop_without_aliases(self):
# No portableAliases attached -> nothing to classify, no crash.
idl = classify_backing_sqlfn({"functions": [
{"name": "X", "sqlfn": "same_bbox", "sqlop": "~="}]})
self.assertNotIn("sqlfnBackingOnly", idl["functions"][0])

def test_duplicate_detection(self):
bad = {"families": {"a": [{"operator": "&&", "bareName": "x"},
{"operator": "@>", "bareName": "x"}]},
Expand Down
Loading