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
26 changes: 25 additions & 1 deletion parser/boundargs.py
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,24 @@ def _kernel_args(body: str, func: dict, meos_body: str | None) -> list[str | Non
return None


def _expr_caller_index(body: str, expr: str) -> int | None:
"""The SQL argument an expression handed to the kernel carries, the one argument the
locals it reads carry (#_caller_index): ``Raster_make`` passes
``(const uint8_t *) VARDATA_ANY(pixels)``, which carries argument 0 through
``pixels = PG_GETARG_BYTEA_PP(0)``. None for a bare local, which #_caller_index reads,
and where the locals carry no argument or several."""
expr = _CAST.sub("", expr.strip())
if _IDENT.match(expr):
return None
found = set()
for ident in set(re.findall(r"\b[a-z_]\w*\b", expr)):
if re.search(r"(?<![\w.>])" + re.escape(ident) + r"\s*=(?!=)", body):
k = _caller_index(body, ident)
if k is not None:
found.add(k)
return found.pop() if len(found) == 1 else None


def _sql_arg_params(body: str, func: dict, meos_body: str | None = None) -> list[str] | None:
"""The C parameters of ``func`` in the order of the SQL arguments the wrapper ``body``
reads for them, or None when that is their C order or cannot be read: the call
Expand All @@ -920,11 +938,17 @@ def _sql_arg_params(body: str, func: dict, meos_body: str | None = None) -> list
if not args:
return None
params = func.get("params", [])
# The length of an input array travels with it, so it carries no argument of its own
lengths = {(a.get("lengthFrom") or {}).get("name")
for a in (func.get("shape") or {}).get("inputArrays") or []}
by_k: dict[int, str] = {}
for a, p in zip(args, params):
if a is None or a.strip().startswith("&") or _literal(a.strip()) is not None:
if a is None or a.strip().startswith("&") or _literal(a.strip()) is not None \
or p["name"] in lengths:
continue
k = _caller_index(body, a)
if k is None:
k = _expr_caller_index(body, a)
if k is not None:
if k in by_k:
return None
Expand Down
24 changes: 24 additions & 0 deletions tests/test_boundargs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1093,6 +1093,18 @@ def test_the_literal_is_attached_then_stripped(self):
return (int) nad_tnumber_tbox(temp, box);
"""
NAD_TINT_TBOX = {"name": "nad_tint_tbox", "params": [{"name": "temp"}, {"name": "box"}]}
# A wrapper reading a bytea first and passing it last, as an expression over its local.
BYTES_LAST = """
bytea *pixels = PG_GETARG_BYTEA_PP(0);
int width = PG_GETARG_INT32(1);
char *pixeltype = text_to_cstring(PG_GETARG_TEXT_PP(3));
Raster *result = raster_make(width, PG_GETARG_INT32(2), pixeltype,
(const uint8_t *) VARDATA_ANY(pixels), (size_t) VARSIZE_ANY_EXHDR(pixels));
"""
RASTER_MAKE = {"name": "raster_make", "params": [
{"name": n} for n in ("width", "height", "pixeltype", "pixels", "size")],
"shape": {"inputArrays": [{"param": "pixels",
"lengthFrom": {"kind": "param", "name": "size"}}]}}


class SqlArgParamsTests(unittest.TestCase):
Expand Down Expand Up @@ -1131,6 +1143,18 @@ def test_without_the_public_body_nothing_is_stated(self):
"""#test_a_shared_kernel_states_the_order without the body of the public function."""
self.assertIsNone(_sql_arg_params(KERNEL_WRAPPER, DISTANCE_SET_INT))

def test_an_expression_argument_carries_what_its_local_carries(self):
"""A bytea read first and passed last, as an expression over its local, with its
length beside it: the length travels with its array and carries no argument."""
self.assertEqual(_sql_arg_params(BYTES_LAST, RASTER_MAKE),
["pixels", "width", "height", "pixeltype"])

def test_an_expression_reading_two_arguments_carries_none(self):
"""#test_an_expression_argument_carries_what_its_local_carries where the expression
reads two locals carrying different arguments, so the order cannot be read."""
body = BYTES_LAST.replace("VARDATA_ANY(pixels)", "VARDATA_ANY(pixels) + width")
self.assertIsNone(_sql_arg_params(body, RASTER_MAKE))


IDL = Path(__file__).resolve().parent.parent / "output" / "meos-idl.json"

Expand Down
Loading