From 9c65c52e8d0f32ac0d59cc8e1de87aaebe01d042 Mon Sep 17 00:00:00 2001 From: Esteban Zimanyi Date: Sun, 14 Jun 2026 13:01:11 +0200 Subject: [PATCH] Attach the SQL-facing arity from the CREATE FUNCTION signatures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The @sqlfn map gives each catalog function its MobilityDB SQL name, but the binding-facing ARITY is the SQL CREATE FUNCTION signature, not the wider C one. A SQL arg with a DEFAULT clause is optional, and a trailing C parameter absent from the SQL form is a C-only out-param (e.g. the size_t * of the *_as_hexwkb family). Without this a generator emits the raw C arity — trajectory(temporal, bool) instead of SQL trajectory(temporal), or asHexWKB(temporal, byte, ) instead of asHexWKB(temporal) — forcing every binding to hand-special-case the flag and out-params. Parse mobilitydb/sql/**/*.in.sql for `CREATE FUNCTION name(args) ... AS 'MODULE_PATHNAME',''`, key by the wrapper (the @csqlfn target), and attach per function: sqlArity -- required SQL args (no DEFAULT) sqlArityMax -- total SQL args (including DEFAULT) so a generator derives: C params [0, sqlArity) required, [sqlArity, sqlArityMax) SQL-optional, [sqlArityMax, C-arity) out-params. 1977 wrappers parsed, 185 with optional args; trajectory -> (1, 2), temporal_as_hexwkb -> (1, 2) over a 3-param C signature (the size_out is the lone out-param). --- parser/sqlfn.py | 60 ++++++++++++++++++++++++++++++++++++++++++++++++- run.py | 3 ++- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/parser/sqlfn.py b/parser/sqlfn.py index 0c0fcf1..b515840 100644 --- a/parser/sqlfn.py +++ b/parser/sqlfn.py @@ -31,6 +31,57 @@ _SQLFN = re.compile(r"@sqlfn\s+(\w+)\s*\(\)") _SQLOP = re.compile(r"@sqlop\s+@p\s+(\S+)") _DATUM = re.compile(r"Datum\s+(\w+)\s*\(\s*PG_FUNCTION_ARGS") +# `CREATE [OR REPLACE] FUNCTION name(` — the SQL-facing signature; the wrapper it +# binds is in the trailing `AS 'MODULE_PATHNAME', ''`. +_CREATE_FN = re.compile(r"CREATE\s+(?:OR\s+REPLACE\s+)?FUNCTION\s+(\w+)\s*\(", re.I) +_AS_WRAPPER = re.compile(r"AS\s+'[^']*'\s*,\s*'(\w+)'", re.I) + + +def _split_top_commas(s): + out, depth, cur = [], 0, "" + for ch in s: + if ch in "([": + depth += 1 + elif ch in ")]": + depth -= 1 + if ch == "," and depth == 0: + out.append(cur) + cur = "" + else: + cur += ch + if cur.strip(): + out.append(cur) + return out + + +def _wrapper_sql_sigs(sql_src): + """MobilityDB-C wrapper name -> (sqlArity, sqlArityMax) from the CREATE FUNCTION + definitions. The SQL signature is the binding-facing arity, NOT the C one: an arg + with a DEFAULT clause is optional, and any trailing C param absent from the SQL form + is a C-only out-param (e.g. the `size_t *` of the *_as_hexwkb family). So a generator + binding the @sqlfn name must expose sqlArity..sqlArityMax args, NOT the full C list. + Across overloads of one wrapper, take min-required / max-total.""" + out = {} + sql_src = Path(sql_src) + if not sql_src.exists(): + return out + for sf in sorted(sql_src.rglob("*.sql")): + text = sf.read_text(errors="ignore") + for m in _CREATE_FN.finditer(text): + i, depth, start = m.end(), 1, m.end() + while i < len(text) and depth: + depth += (text[i] == "(") - (text[i] == ")") + i += 1 + wm = _AS_WRAPPER.search(text, i, i + 400) + if not wm: + continue # SQL-language ($$...$$) wrapper has no C symbol — skip + wrapper = wm.group(1) + args = [a for a in _split_top_commas(text[start:i - 1]) if a.strip()] + required = sum(1 for a in args if not re.search(r"\bDEFAULT\b", a, re.I)) + prev = out.get(wrapper) + out[wrapper] = ((min(prev[0], required), max(prev[1], len(args))) + if prev else (required, len(args))) + return out def _meos_to_mdb(meos_src): @@ -85,9 +136,10 @@ def _mdb_to_sql(mdb_src): return out -def attach_sqlfn_map(idl, meos_src, mdb_src): +def attach_sqlfn_map(idl, meos_src, mdb_src, sql_src=None): m2d = _meos_to_mdb(meos_src) d2s = _mdb_to_sql(mdb_src) + w2sig = _wrapper_sql_sigs(sql_src) if sql_src else {} n = 0 for f in idl["functions"]: wrappers = m2d.get(f["name"]) @@ -105,6 +157,12 @@ def attach_sqlfn_map(idl, meos_src, mdb_src): continue f["mdbC"] = wrappers[0] f["sqlfn"] = pairs[0][0] + # The SQL-facing arity (required..total). Lets a generator expose the SQL + # signature instead of the wider C one: args beyond sqlArity are SQL-optional + # (DEFAULT), and C params beyond sqlArityMax are C-only out-params. + sig = w2sig.get(wrappers[0]) + if sig: + f["sqlArity"], f["sqlArityMax"] = sig if pairs[0][1]: f["sqlop"] = pairs[0][1] # Shared wrapper OR ever/always pair exposing >1 SQL name: record them all. diff --git a/run.py b/run.py index e016eb8..5b55452 100644 --- a/run.py +++ b/run.py @@ -84,8 +84,9 @@ def main(): SRC_ROOT = Path(os.environ.get("MDB_SRC_ROOT", "./_mobilitydb")) MEOS_SRC = SRC_ROOT / "meos" / "src" 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) + idl, nsql = 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