From d00a64bcc722d20c7e94e1c63d7e5e01d2608dfe Mon Sep 17 00:00:00 2001 From: Esteban Zimanyi Date: Thu, 1 Oct 2026 13:46:22 +0200 Subject: [PATCH] State each function's sqlfn as the one SQL name its signatures carry A function whose SQL signatures all carry one name takes that name as its sqlfn, and the signatures carry no sqlName restating it (parser/sqlfn.py state_deployed_sqlfn, run once the backing tags are classified). A wrapper's @sqlfn names one member of the family it serves: Set_in is tagged intset_in() and backs floatset_in, geomset_in and fifteen more, while each one's CREATE FUNCTION deploys its own name. A function whose signatures carry several names, such as the eDwithin/aDwithin pair over one ea_* function, keeps the tag and each signature its own name. A sqlfnBackingOnly record keeps its backing tag beside its publicSqlName. Why. A binding registers a function under its sqlfn. floatset_in was stated as intset_in, tfloatinst_make as tint and tfloat_values as getValues, which in SQL names the function returning the span set. Measured. Over MobilityDB 72abdf86c2, 68 functions change their sqlfn and the signature of each drops its sqlName, every one public: 52 text readers and writers (floatset_in from intset_in, tfloat_in from tint_in), 10 instant constructors (tfloatinst_make from tint), 3 value sets (tfloat_values from getValues to valueSet), 2 point-to-geo casts (tgeogpoint_to_tgeography from tgeometry to tgeography) and contains_cbuffer_cbuffer (from contains to cbuffer_contains). Nothing else in the catalog changes, the 70 backing records included, and the portable parity report is byte for byte the same. Witness. tests/test_sqlfn_deployed.py states the deployed name, keeps the tag over several names, over a backing record and without signatures, and over the catalog finds the per-type names and no function whose signatures name one member other than its sqlfn; the catalog tests fail on the catalog derived without this change. The suite floor goes from 383 to 390. --- .github/workflows/pytest.yml | 2 +- parser/sqlfn.py | 29 +++++++++++ run.py | 6 ++- tests/test_sqlfn_deployed.py | 96 ++++++++++++++++++++++++++++++++++++ 4 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 tests/test_sqlfn_deployed.py diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index b5c899a..62af23d 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -96,7 +96,7 @@ jobs: # carries, or a change to them is not exercised until after it merges. # Consumers use the action; this repository owns the rules. - name: Refuse a skip, and a suite that shrank - run: tools/check-test-outcome.py "$RUNNER_TEMP/pytest.log" --min-tests 383 + run: tools/check-test-outcome.py "$RUNNER_TEMP/pytest.log" --min-tests 390 # The rules earn their place by refusing a log that carries what they # name. Both fixtures are written here rather than tracked, and the diff --git a/parser/sqlfn.py b/parser/sqlfn.py index 57e0487..a9796d4 100644 --- a/parser/sqlfn.py +++ b/parser/sqlfn.py @@ -992,3 +992,32 @@ def lint_sqlfn_case_collisions(idl, multi=None): if sf: by_lower.setdefault(sf.lower(), set()).add(sf) return sorted((lo, sorted(sp)) for lo, sp in by_lower.items() if len(sp) > 1) + + +def state_deployed_sqlfn(idl: dict) -> tuple[dict, int]: + """Set ``sqlfn`` to the one SQL name every signature of a function carries. + + A wrapper's ``@sqlfn`` names one member of the family it serves: ``Set_in`` is + tagged ``intset_in()`` and backs ``floatset_in``, ``geomset_in`` and fifteen more, + and the signatures #attach_sqlfn_map keeps for ``floatset_in`` all carry + ``sqlName: floatset_in``, the name its ``CREATE FUNCTION`` deploys. When every + signature of a function carries one name, that name is the function's ``sqlfn`` + and the signatures drop the ``sqlName`` that only restated it. A function whose + signatures carry several names keeps the tag, each signature its own name; a + ``sqlfnBackingOnly`` record, as #classify_backing_sqlfn of parser/portable.py marks + it, keeps its backing tag beside its ``publicSqlName``.""" + n = 0 + for f in idl.get("functions", []): + sigs = f.get("sqlSignatures") + if not sigs or f.get("sqlfnBackingOnly"): + continue + names = {s.get("sqlName", f["sqlfn"]) for s in sigs} + if len(names) != 1: + continue + name = names.pop() + for s in sigs: + s.pop("sqlName", None) + if name != f["sqlfn"]: + f["sqlfn"] = name + n += 1 + return idl, n diff --git a/run.py b/run.py index 9911a7f..d5e544a 100644 --- a/run.py +++ b/run.py @@ -23,7 +23,7 @@ from parser.sqlfn import (attach_sqlfn_map, attach_aggfn_map, attach_row_sources, attach_sqlaggfn_map, lint_container_family_csqlfn, lint_ea_sqlfn, lint_positional_sqlfn, - lint_sqlfn_case_collisions) + lint_sqlfn_case_collisions, state_deployed_sqlfn) from parser.doxygroup import attach_groups from parser.extractors import find_unlisted_foreign_structs from parser.families import all_families, use_headers_dir @@ -276,6 +276,10 @@ def main(): 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) + # A function whose signatures all carry one SQL name takes it as its sqlfn, + # in place of the family member its wrapper's tag names. + idl, ndep = state_deployed_sqlfn(idl) + print(f" sqlfn set to the one name its signatures carry: {ndep}", file=sys.stderr) # The same map names each position operator once per class (setLeft … # stboxLeft): derive those names from its @sqlfn/@sqlop tags. idl = attach_position_names(idl) diff --git a/tests/test_sqlfn_deployed.py b/tests/test_sqlfn_deployed.py new file mode 100644 index 0000000..a9aee34 --- /dev/null +++ b/tests/test_sqlfn_deployed.py @@ -0,0 +1,96 @@ +"""A function's `sqlfn` is the one SQL name its signatures carry. + +A wrapper's @sqlfn names one member of the family it serves, so a per-type function +read through it (`floatset_in` under `Set_in`, tagged `intset_in()`) would carry a +family member's name. #state_deployed_sqlfn states the name the function's own +CREATE FUNCTION deploys, as #attach_sqlfn_map stamps it on each signature, and keeps +the tag where the signatures carry several names or the record is a backing tag +#classify_backing_sqlfn marks. + +Plain unittest, no pytest dependency; synthetic records, then the generated catalog. +""" +import json +import unittest +from pathlib import Path + +from parser.sqlfn import state_deployed_sqlfn + +IDL = Path(__file__).resolve().parents[1] / "output" / "meos-idl.json" + + +def _state(*functions): + idl, n = state_deployed_sqlfn({"functions": list(functions)}) + return {f["name"]: f for f in idl["functions"]}, n + + +class DeployedNameTests(unittest.TestCase): + + def test_the_one_name_the_signatures_carry_is_the_sqlfn(self): + fns, n = _state({"name": "floatset_in", "sqlfn": "intset_in", + "sqlSignatures": [{"args": ["cstring"], "ret": "floatset", + "sqlName": "floatset_in"}]}) + self.assertEqual(fns["floatset_in"]["sqlfn"], "floatset_in") + self.assertEqual(fns["floatset_in"]["sqlSignatures"], + [{"args": ["cstring"], "ret": "floatset"}]) + self.assertEqual(n, 1) + + def test_signatures_carrying_several_names_keep_the_tag(self): + sigs = [{"args": ["tgeompoint", "geometry", "float"], "ret": "boolean", + "sqlName": "eDwithin"}, + {"args": ["tgeompoint", "geometry", "float"], "ret": "boolean", + "sqlName": "aDwithin"}] + fns, n = _state({"name": "ea_dwithin_tgeo_geo", "sqlfn": "eDwithin", + "sqlSignatures": [dict(s) for s in sigs]}) + self.assertEqual(fns["ea_dwithin_tgeo_geo"]["sqlfn"], "eDwithin") + self.assertEqual(fns["ea_dwithin_tgeo_geo"]["sqlSignatures"], sigs) + self.assertEqual(n, 0) + + def test_a_backing_tag_stays_beside_its_public_name(self): + fns, n = _state({"name": "adjacent_tbox_tnumber", "sqlfn": "adjacent_bbox", + "sqlfnBackingOnly": True, "publicSqlName": "adjacent", + "sqlSignatures": [{"args": ["tbox", "tint"], "ret": "boolean", + "sqlName": "adjacent"}]}) + self.assertEqual(fns["adjacent_tbox_tnumber"]["sqlfn"], "adjacent_bbox") + self.assertEqual(fns["adjacent_tbox_tnumber"]["sqlSignatures"][0]["sqlName"], + "adjacent") + self.assertEqual(n, 0) + + def test_a_function_without_signatures_keeps_its_tag(self): + fns, n = _state({"name": "temporal_in", "sqlfn": "tint_in"}) + self.assertEqual(fns["temporal_in"]["sqlfn"], "tint_in") + self.assertEqual(n, 0) + + +class DeployedNameCatalogTests(unittest.TestCase): + """Over the generated catalog, the per-type functions carry their own SQL name and + no signature restates the function's sqlfn.""" + + def setUp(self): + if not IDL.exists(): + self.skipTest(f"{IDL} not generated; run `python run.py` first") + self.fns = {f["name"]: f for f in json.loads(IDL.read_text())["functions"]} + + def test_per_type_functions_carry_their_own_name(self): + for name, sqlfn in (("floatset_in", "floatset_in"), ("tfloat_in", "tfloat_in"), + ("tfloatinst_make", "tfloat"), ("tfloat_values", "valueSet"), + ("contains_cbuffer_cbuffer", "cbuffer_contains")): + self.assertEqual(self.fns[name]["sqlfn"], sqlfn, name) + + def test_backing_tags_keep_their_family_name(self): + f = self.fns["adjacent_tbox_tnumber"] + self.assertEqual((f["sqlfn"], f["publicSqlName"]), ("adjacent_bbox", "adjacent")) + + def test_no_function_names_a_family_member_its_signatures_do_not(self): + """The condition #state_deployed_sqlfn of parser/sqlfn.py reads, asked of the + whole catalog: signatures all carrying one name other than sqlfn, a backing + tag aside.""" + other = [f["name"] for f in self.fns.values() + if f.get("sqlSignatures") and not f.get("sqlfnBackingOnly") + and len(names := {s.get("sqlName", f["sqlfn"]) + for s in f["sqlSignatures"]}) == 1 + and names != {f["sqlfn"]}] + self.assertEqual(other, []) + + +if __name__ == "__main__": + unittest.main()