From 3f07b3dc2a50fc1967087666d7a797a23495e044 Mon Sep 17 00:00:00 2001 From: Esteban Zimanyi Date: Thu, 9 Jul 2026 10:56:26 +0200 Subject: [PATCH] Classify the shared bbox-topological @sqlfn tags as backing-only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The five bounding-box topological operators (~= @> <@ -|- &&) are backed by a shared C @sqlfn tag named `_bbox` (same_bbox, contains_bbox, contained_bbox, overlaps_bbox, adjacent_bbox). That tag is never deployed as a CREATE FUNCTION — the user-facing SQL name is the operator's bare portable alias (same, contains, contained, overlaps, adjacent). A binding that registers the raw sqlfn therefore exposes a function MobilityDB does not. Flag those records in the catalog with `sqlfnBackingOnly` and record the deployed `publicSqlName` (the bare alias), so every binding uniformly registers the bare name plus the operator and drops the `_bbox` backing tag, without any per-binding name-pattern special case. The classification is grounded in two catalog-native facts — the `_bbox` shared-backing convention and the operator-to-bareName map — and runs after both the @sqlfn map and the portable aliases are attached. --- parser/portable.py | 31 +++++++++++++++++++++++++++++++ run.py | 11 ++++++++++- tests/test_portable.py | 30 +++++++++++++++++++++++++++++- 3 files changed, 70 insertions(+), 2 deletions(-) diff --git a/parser/portable.py b/parser/portable.py index 41398ac..2ca17f4 100644 --- a/parser/portable.py +++ b/parser/portable.py @@ -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 ``_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 diff --git a/run.py b/run.py index 5b55452..49d70ef 100644 --- a/run.py +++ b/run.py @@ -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 @@ -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(): diff --git a/tests/test_portable.py b/tests/test_portable.py index 5eef798..860346c 100644 --- a/tests/test_portable.py +++ b/tests/test_portable.py @@ -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" @@ -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 `_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"}]},