From b70e01355ce76b92dd059fca689eebf83db2bd5f Mon Sep 17 00:00:00 2001 From: Esteban Zimanyi Date: Fri, 9 Oct 2026 04:03:45 +0200 Subject: [PATCH] State a literal a C wrapper passes as a value operand of its composition A C wrapper that casts an argument before its call and passes a C literal for a parameter the SQL signature does not carry states that parameter as a value operand: eDwithin(tpose, geometry, float), whose wrapper Edwithin_tpose_geo casts with tpoint = tpose_to_tpoint(temp) and calls edwithin_tgeo_geo(tpoint, gs, dist, true), is the composition of operand 0 through tpose_to_tpoint, operands 1 and 2 as they are, and the value true for spheroid, the operand form A7 states for a value a SQL body binds. A literal is true, false or a number. Witness: on MobilityDB master 2334c439ab edwithin_tgeo_geo, edwithin_geo_tgeo and edwithin_tgeo_tgeo take a spheroid flag the SQL eDwithin over tpose does not carry, so a tpose wrapper calling them passes true; master's attach_wrapper_compositions finds no SQL argument for the literal, leaves the three signatures eDwithin(geometry, tpose, float), eDwithin(tpose, geometry, float) and eDwithin(tpose, tpose, float) among the sqlSignatures of those functions, and a binding passes them a tpose they refuse. Measured: over a MobilityDB tree on master 2334c439ab whose six tpose wrappers call the public tgeo functions, run.py states 6 tpose compositions, the three eDwithin with the value true for spheroid, and no tpose signature on the tgeo functions; master's parser over the same tree states 3 and leaves the three eDwithin signatures on the tgeo functions. Over MobilityDB master 2334c439ab every key of the catalog equals that of master's parser. The suite passes the new test tests/test_compositions.py test_a_literal_argument_is_a_value_operand, which fails with master's parser, with 484 passed and 25 skipped against 483 and 25 with master's parser, the same three struct and family tests failing in both over a catalog generated from the source headers. Why: a binding reaches a relationship of a derived type through the cast and the function of its cast target with every parameter that function reads bound, as PostgreSQL's wrapper binds it. --- parser/compositions.py | 11 ++++++--- tests/test_compositions.py | 46 +++++++++++++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/parser/compositions.py b/parser/compositions.py index 9135b83..22831af 100644 --- a/parser/compositions.py +++ b/parser/compositions.py @@ -497,6 +497,7 @@ def attach_compositions(idl, sql_src): _CAST_CALL = re.compile(r"^\s*(\w+)\s*\(\s*(\w+)\s*\)\s*$") +_C_LITERAL = re.compile(r"^(?:true|false|-?\d+(?:\.\d+)?)$") def attach_wrapper_compositions(idl, mdb_src, sql_src, meos_src): @@ -514,7 +515,9 @@ def attach_wrapper_compositions(idl, mdb_src, sql_src, meos_src): body cannot reach. Each signature is traced to the wrapper whose CREATE FUNCTION states it (#_signature_wrapper of parser/boundargs.py) and each argument of the call to the SQL argument it carries (#_caller_index); a cast is a public catalog function of - one parameter. A signature whose wrapper casts nothing stays where it is.""" + one parameter. A C literal the wrapper passes, as ``true`` for the ``spheroid`` of + ``edwithin_tgeo_geo``, is a value operand. A signature whose wrapper casts nothing + stays where it is.""" from parser.boundargs import (_call_args, _caller_index, _signature_wrapper, extract_wrappers) from parser.sqlfn import _meos_to_mdb, _wrapper_sql_sigs @@ -545,13 +548,15 @@ def attach_wrapper_compositions(idl, mdb_src, sql_src, meos_src): r"(?])" + re.escape(a) + r"\s*=(?!=)\s*([^;]+);", body)] hit = _CAST_CALL.match(rhs[0]) if len(rhs) == 1 else None cast = hit.group(1) if hit else None - if cast in public and len(public[cast].get("params") or ()) == 1: + if _C_LITERAL.match(a): + operands.append({"value": a, "param": p}) + elif cast in public and len(public[cast].get("params") or ()) == 1: operands.append({"arg": _caller_index(body, hit.group(2)), "casts": [cast], "param": p}) cast_seen = True else: operands.append({"arg": _caller_index(body, a), "param": p}) - if not cast_seen or any(o["arg"] is None for o in operands): + if not cast_seen or any("arg" in o and o["arg"] is None for o in operands): operands = None if operands is None: keep.append(sig) diff --git a/tests/test_compositions.py b/tests/test_compositions.py index 8035f5f..7eb79b0 100644 --- a/tests/test_compositions.py +++ b/tests/test_compositions.py @@ -334,6 +334,18 @@ def test_a_cast_no_create_cast_declares_stops_the_catalog(self): int result = eintersects_tgeo_geo(temp, gs); PG_RETURN_BOOL(result ? true : false); } + +Datum +Edwithin_tpose_geo(PG_FUNCTION_ARGS) +{ + Temporal *temp = PG_GETARG_TEMPORAL_P(0); + GSERIALIZED *gs = PG_GETARG_GSERIALIZED_P(1); + double dist = PG_GETARG_FLOAT8(2); + Temporal *tpoint = tpose_to_tpoint(temp); + int result = edwithin_tgeo_geo(tpoint, gs, dist, true); + pfree(tpoint); + PG_RETURN_BOOL(result ? true : false); +} ''' CAST_SQL = ''' @@ -347,6 +359,11 @@ def test_a_cast_no_create_cast_declares_stops_the_catalog(self): AS 'MODULE_PATHNAME', 'Eintersects_tgeo_geo' SUPPORT tspatial_supportfn LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; +CREATE FUNCTION eDwithin(tpose, geometry, float) + RETURNS boolean + AS 'MODULE_PATHNAME', 'Edwithin_tpose_geo' + SUPPORT tspatial_supportfn + LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; ''' CAST_MEOS = ''' @@ -359,6 +376,17 @@ def test_a_cast_no_create_cast_declares_stops_the_catalog(self): { return 0; } + +/** + * @brief Return 1 if a temporal geo is ever within a distance of a geometry + * @csqlfn #Edwithin_tpose_geo() + */ +int +edwithin_tgeo_geo(const Temporal *temp, const GSERIALIZED *gs, double dist, + bool spheroid) +{ + return 0; +} ''' @@ -386,20 +414,32 @@ def _attach(self): "sqlSignatures": [{"args": ["tgeompoint", "geometry"], "ret": "boolean"}, {"args": ["tpose", "geometry"], "ret": "boolean"}]}, {"name": "tpose_to_tpoint", "api": "public", "params": [{"name": "temp"}], - "sqlSignatures": []}], + "sqlSignatures": []}, + {"name": "edwithin_tgeo_geo", "api": "public", "sqlfn": "eDwithin", + "params": [{"name": "temp"}, {"name": "gs"}, {"name": "dist"}, + {"name": "spheroid"}], + "sqlSignatures": [{"args": ["tpose", "geometry", "float"], "ret": "boolean"}]}], "compositions": []} return attach_wrapper_compositions(idl, root / "src", root / "sql", root / "meos") def test_the_cast_wrapper_states_a_composition(self): idl, n = self._attach() - self.assertEqual(n, 1) - self.assertEqual(idl["compositions"], [{ + self.assertEqual(n, 2) + self.assertEqual(idl["compositions"][:1], [{ "sqlName": "eIntersects", "args": ["tpose", "geometry"], "required": 2, "argDefaults": [None, None], "ret": "boolean", "operands": [{"arg": 0, "casts": ["tpose_to_tpoint"], "param": "temp"}, {"arg": 1, "param": "gs"}], "call": "eintersects_tgeo_geo"}]) + def test_a_literal_argument_is_a_value_operand(self): + idl, _ = self._attach() + self.assertEqual(idl["compositions"][1]["operands"], [ + {"arg": 0, "casts": ["tpose_to_tpoint"], "param": "temp"}, + {"arg": 1, "param": "gs"}, {"arg": 2, "param": "dist"}, + {"value": "true", "param": "spheroid"}]) + self.assertEqual(idl["functions"][2]["sqlSignatures"], []) + def test_the_wrapper_casting_nothing_keeps_its_signature(self): idl, _ = self._attach() self.assertEqual([s["args"] for s in idl["functions"][0]["sqlSignatures"]],