diff --git a/parser/sqlfn.py b/parser/sqlfn.py index 190b311..7bb3b5f 100644 --- a/parser/sqlfn.py +++ b/parser/sqlfn.py @@ -46,7 +46,11 @@ # operators lost their SQL name that way). _FNDEF = re.compile(r"\*/\s*\n(?:[^\n(){};=]+\n)?(?:[\w\s*]+?\s)?(\w+)\s*\(") _SQLFN = re.compile(r"@sqlfn\s+(\w+)\s*\(\)") -_SQLOP = re.compile(r"@sqlop\s+@p\s+(\S+)") +# The operator stops at a comma, mirroring `_SQLFN`'s `(\w+)\s*\(\)`: a block naming several +# SQL functions lists their operators the same comma-separated way +# (`@sqlop @p ->, @p ->>` beside `@sqlfn a(), b()`), and a PostgreSQL operator name never +# contains a comma. `(\S+)` ran past it and published the comma as part of the operator. +_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', ''`. diff --git a/tests/test_sqlfn_direct.py b/tests/test_sqlfn_direct.py index 4b366a5..263632a 100644 --- a/tests/test_sqlfn_direct.py +++ b/tests/test_sqlfn_direct.py @@ -69,6 +69,30 @@ def _trees(self, d): (mdb / "y.c").write_text(MDB_C) return str(meos), str(mdb) + def test_multi_op_tag_keeps_the_operator_without_its_comma(self): + """A block naming several SQL functions lists their operators the same + comma-separated way `@sqlfn` lists the names, so the operator ends at the + comma. Reading to the next whitespace published `->,` as the operator.""" + src = """ +/** + * @ingroup meos_json_json + * @brief Extract a field from a temporal JSONB value + * @sqlfn tjsonbObjectField(), tjsonbObjectFieldText() + * @sqlop @p ->, @p ->> + */ +Temporal * +tjsonb_object_field(const Temporal *temp, const text *key) +{ +} +""" + with tempfile.TemporaryDirectory() as d: + meos = Path(d) / "meos" + meos.mkdir() + (meos / "j.c").write_text(src) + direct = _meos_direct_sql(str(meos)) + self.assertEqual(direct.get("tjsonb_object_field"), + ("tjsonbObjectField", "->")) + def test_direct_map_needs_sqlfn_and_skips_csqlfn_blocks(self): with tempfile.TemporaryDirectory() as d: meos, _ = self._trees(d)