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
40 changes: 38 additions & 2 deletions parser/compositions.py
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,29 @@ 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+)?)$")
# A loop filling a local array from the arguments past the first k, `values[i] =
# PG_GETARG_FLOAT8(i + 1)` for `i < count`, and the count of those arguments,
# `count = PG_NARGS() - 1`
_GATHER_LOOP = re.compile(
r"for\s*\(\s*int\s+(\w+)\s*=\s*0\s*;\s*\1\s*<\s*(\w+)\s*;\s*\1\s*\+\+\s*\)\s*"
r"(\w+)\s*\[\s*\1\s*\]\s*=\s*PG_GETARG_\w+\s*\(\s*\1\s*\+\s*(\d+)\s*\)\s*;")
_NARGS_LESS = r"(?<![\w.>]){}\s*=\s*PG_NARGS\s*\(\s*\)\s*-\s*(\d+)\s*;"


def _gathers(body, nargs):
"""{array local: (the SQL arguments it gathers, its count local)} of a wrapper ``body``
called with ``nargs`` arguments: ``Pcpoint_make`` gathers arguments 1 and 2 of
pcpoint(pcid, x, y) into ``values`` and counts 2 in ``count``. #_caller_index reads the
one argument a local carries and #_nargs_choices of parser/boundargs.py a value the
number of arguments chooses; no reader there follows a loop over the arguments, which
this one does for the loop counting them from ``PG_NARGS()``."""
out = {}
for m in _GATHER_LOOP.finditer(body):
_, count, array, k = m.groups()
c = re.search(_NARGS_LESS.format(re.escape(count)), body)
if c and c.group(1) == k and nargs > int(k):
out[array] = (list(range(int(k), nargs)), count)
return out


def attach_wrapper_compositions(idl, mdb_src, sql_src, meos_src):
Expand All @@ -551,7 +574,11 @@ def attach_wrapper_compositions(idl, mdb_src, sql_src, meos_src):
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 C literal the wrapper passes, as ``true`` for the ``spheroid`` of
``edwithin_tgeo_geo``, is a value operand. A signature whose wrapper casts nothing
``edwithin_tgeo_geo``, is a value operand. The arguments a wrapper gathers into a C
array (#_gathers) are a gather operand and their count a value operand:
``pcpoint(integer, double precision, double precision)`` is ``{"operands": [{"arg": 0,
"param": "pcid"}, {"gather": [1, 2], "param": "values"}, {"value": "2", "param":
"count"}], "call": "pcpoint_make"}``. A signature whose wrapper casts or gathers nothing
stays where it is."""
from parser.boundargs import (_call_args, _caller_index, _delegated, _signature_wrapper,
extract_helpers, extract_wrappers, specialize_argtypes)
Expand Down Expand Up @@ -588,14 +615,23 @@ def attach_wrapper_compositions(idl, mdb_src, sql_src, meos_src):
operands = None
if args is not None and len(args) == len(params):
operands, cast_seen = [], False
gathers = _gathers(body, len(sig.get("args") or ()))
counts = {c: str(len(ks)) for ks, c in gathers.values()}
for a, p in zip(args, params):
a = a.strip()
rhs = [m.group(1) for m in re.finditer(
r"(?<![\w.>])" + re.escape(a) + r"\s*=(?!=)\s*([^;]+);", body)
if m.group(1).strip() != "NULL"]
hit = _CAST_CALL.match(rhs[0]) if len(rhs) == 1 else None
cast = hit.group(1) if hit else None
if _C_LITERAL.match(a):
if a in gathers:
# The arguments the wrapper gathers into a C array, which a binding
# passes as it passes a SQL array of them
operands.append({"gather": gathers[a][0], "param": p})
cast_seen = True
elif a in counts:
operands.append({"value": counts[a], "param": p})
elif _C_LITERAL.match(a):
operands.append({"value": a, "param": p})
elif p in bound and not hit:
# A local the wrapper leaves at the literal it starts from
Expand Down
108 changes: 108 additions & 0 deletions tests/test_compositions.py
Original file line number Diff line number Diff line change
Expand Up @@ -676,5 +676,113 @@ def test_the_span_signatures_keep_their_place(self):
["tstzspan", "tstzspan"])



GATHER_WRAPPERS = """
Datum
Pcpoint_make(PG_FUNCTION_ARGS)
{
uint32_t pcid = (uint32_t) PG_GETARG_INT32(0);
int count = PG_NARGS() - 1;
assert(count > 0 && count <= PCPOINT_MAX_COORDS);
double values[PCPOINT_MAX_COORDS];
for (int i = 0; i < count; i++)
values[i] = PG_GETARG_FLOAT8(i + 1);
PG_RETURN_PCPOINT_P(pcpoint_make(pcid, values, count));
}

Datum
Pcpoint_make_coords(PG_FUNCTION_ARGS)
{
uint32_t pcid = (uint32_t) PG_GETARG_INT32(0);
ArrayType *array = PG_GETARG_ARRAYTYPE_P(1);
int count;
const double *values = float8arr_extract(array, &count);
Pcpoint *result = pcpoint_make(pcid, values, count);
PG_FREE_IF_COPY(array, 1);
PG_RETURN_PCPOINT_P(result);
}
"""

GATHER_SQL = """
-- The vocabulary of multi-word types comes from a RETURNS clause, as cellArea(quadbin) states
-- double precision in the SQL of MobilityDB
CREATE FUNCTION cellArea(quadbin)
RETURNS double precision
AS 'MODULE_PATHNAME', 'Quadbin_cell_area'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION pcpoint(pcid integer, x double precision, y double precision)
RETURNS pcpoint
AS 'MODULE_PATHNAME', 'Pcpoint_make'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION pcpoint(pcid integer, x double precision, y double precision,
z double precision)
RETURNS pcpoint
AS 'MODULE_PATHNAME', 'Pcpoint_make'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION pcpoint(pcid integer, coordinates double precision[])
RETURNS pcpoint
AS 'MODULE_PATHNAME', 'Pcpoint_make_coords'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
"""

GATHER_MEOS = """
/**
* @brief Return a pcpoint from a point cloud identifier and coordinates
* @csqlfn #Pcpoint_make(), #Pcpoint_make_coords()
*/
Pcpoint *
pcpoint_make(uint32_t pcid, const double *values, int count)
{
return NULL;
}
"""


class GatherCompositionTests(unittest.TestCase):
"""#attach_wrapper_compositions over a wrapper gathering its trailing arguments into a C
array (#_gathers): pcpoint(pcid, x, y) and pcpoint(pcid, x, y, z) pass the coordinates
as the values of pcpoint_make and their number as its count, while pcpoint(pcid,
coordinates) passes its SQL array as it is."""

def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
root = Path(self.tmp.name)
for sub, name, text in (("src", "pcset.c", GATHER_WRAPPERS),
("sql", "pcpoint.in.sql", GATHER_SQL),
("meos", "pcpoint.c", GATHER_MEOS)):
(root / sub).mkdir()
(root / sub / name).write_text(text)

def tearDown(self):
self.tmp.cleanup()

def _attach(self):
root = Path(self.tmp.name)
double = "double precision"
idl = {"functions": [
{"name": "pcpoint_make", "api": "public", "mdbC": "Pcpoint_make", "sqlfn": "pcpoint",
"params": [{"name": "pcid"}, {"name": "values"}, {"name": "count"}],
"sqlSignatures": [{"args": ["integer", double, double], "ret": "pcpoint"},
{"args": ["integer", double, double, double], "ret": "pcpoint"},
{"args": ["integer", double + "[]"], "ret": "pcpoint"}]}],
"compositions": []}
return attach_wrapper_compositions(idl, root / "src", root / "sql", root / "meos")

def test_the_coordinates_are_gathered_into_the_values(self):
idl, n = self._attach()
self.assertEqual(n, 2)
self.assertEqual([c["operands"] for c in idl["compositions"]], [
[{"arg": 0, "param": "pcid"}, {"gather": [1, 2], "param": "values"},
{"value": "2", "param": "count"}],
[{"arg": 0, "param": "pcid"}, {"gather": [1, 2, 3], "param": "values"},
{"value": "3", "param": "count"}]])
self.assertEqual({c["call"] for c in idl["compositions"]}, {"pcpoint_make"})

def test_the_array_signature_keeps_its_place(self):
idl, _ = self._attach()
self.assertEqual([s["args"] for s in idl["functions"][0]["sqlSignatures"]],
[["integer", "double precision[]"]])


if __name__ == "__main__":
unittest.main()
Loading