diff --git a/parser/sqlfn.py b/parser/sqlfn.py index 35f68ad..27692a4 100644 --- a/parser/sqlfn.py +++ b/parser/sqlfn.py @@ -215,6 +215,11 @@ def attach_sqlfn_map(idl, meos_src, mdb_src, sql_src=None): d2s = _mdb_to_sql(mdb_src) w2sig = _wrapper_sql_sigs(sql_src) if sql_src else {} n = 0 + # Transient map: MEOS function name -> every SQL name it resolves to, for the + # functions that fan out (a shared wrapper / ever-always pair). This is NOT + # catalog output — every binding reads only the primary `sqlfn` — it is working + # data handed to the case-collision lint, which must see every spelling. + multi = {} for f in idl["functions"]: wrappers = m2d.get(f["name"]) if not wrappers: @@ -269,13 +274,14 @@ def attach_sqlfn_map(idl, meos_src, mdb_src, sql_src=None): f["sqlSignatures"] = own if pairs[0][1]: f["sqlop"] = pairs[0][1] - # Shared wrapper OR ever/always pair exposing >1 SQL name: record them all. + # A shared wrapper / ever-always pair exposes >1 SQL name for this one MEOS + # function. That fan-out is transient lint input, not catalog output (every + # binding reads only the primary `sqlfn`), so collect it here and never write + # it to the catalog — the singular `sqlfn` is the one canonical name per entry. if len(pairs) > 1: - f["sqlfnAll"] = [s for s, _ in pairs] - if len(wrappers) > 1: - f["mdbCAll"] = wrappers + multi[f["name"]] = [s for s, _ in pairs] n += 1 - return idl, n + return idl, n, multi # MEOS-C ever/always spatial-relationship functions are named _...; their @@ -331,7 +337,7 @@ def lint_positional_sqlfn(idl): return bad -def lint_sqlfn_case_collisions(idl): +def lint_sqlfn_case_collisions(idl, multi=None): """Return [(lower, [spelling, ...])] for @sqlfn names that collide case-insensitively but differ in case (e.g. tDistance vs tdistance). @@ -340,10 +346,15 @@ def lint_sqlfn_case_collisions(idl): binding name is taken case-SENSITIVELY, and case-insensitive engines (Spark SQL, …) register every spelling under one UDF — so one silently shadows the other. A canonical binding name must have exactly ONE spelling; surface a - casing straggler here before it reaches a binding.""" + casing straggler here before it reaches a binding. + + `multi` (from attach_sqlfn_map) maps a fan-out function to every SQL name it + resolves to, so a straggler that appears only as a secondary name is still + caught even though the catalog now stores only the primary `sqlfn`.""" + multi = multi or {} by_lower = {} for f in idl["functions"]: - for sf in [f.get("sqlfn"), *f.get("sqlfnAll", [])]: + for sf in [f.get("sqlfn"), *multi.get(f["name"], [])]: 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) diff --git a/run.py b/run.py index 022adbb..43d711c 100644 --- a/run.py +++ b/run.py @@ -121,7 +121,7 @@ def main(): MDB_SRC = SRC_ROOT / "mobilitydb" / "src" SQL_SRC = SRC_ROOT / "mobilitydb" / "sql" if MEOS_SRC.exists() and MDB_SRC.exists(): - idl, nsql = attach_sqlfn_map(idl, MEOS_SRC, MDB_SRC, SQL_SRC) + idl, nsql, sqlfn_multi = attach_sqlfn_map(idl, MEOS_SRC, MDB_SRC, SQL_SRC) print(f"[4/4] Attached {nsql} @sqlfn SQL names", file=sys.stderr) # Guard: a copy-paste @csqlfn in meos/src can point an ever/always function at # the opposite-prefix wrapper (eintersects_* tagged #Aintersects_*), flipping its @@ -138,7 +138,7 @@ def main(): # binding names — a case-insensitive engine (Spark SQL) registers both under # one UDF, so one silently shadows the other. Invisible in SQL; surface the # casing straggler here, to be fixed at the MEOS-C @sqlfn source. - case_bad = lint_sqlfn_case_collisions(idl) + case_bad = lint_sqlfn_case_collisions(idl, sqlfn_multi) if case_bad: print(f" ⚠ {len(case_bad)} @sqlfn case-collision(s) (pick ONE canonical " f"spelling at the MEOS-C source — binding-breaking otherwise):", file=sys.stderr)