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
2 changes: 1 addition & 1 deletion .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 29 additions & 0 deletions parser/sqlfn.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 5 additions & 1 deletion run.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
96 changes: 96 additions & 0 deletions tests/test_sqlfn_deployed.py
Original file line number Diff line number Diff line change
@@ -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()
Loading