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
82 changes: 82 additions & 0 deletions parser/boundargs.py
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,88 @@ def own(w, sig=None):
return idl, n, list(dict.fromkeys(drift))


# A local a wrapper sets from the class of a temporal type: `interp =
# temptype_supports_linear(temptype) ? LINEAR : STEP`.
_TYPE_CHOICE = r"(?<![\w.>]){var}\s*=(?!=)\s*temptype_supports_linear\s*\(\s*(\w+)\s*\)" \
r"\s*\?\s*(\w+)\s*:\s*(\w+)\s*;"
# The temporal type of the call, read from its return or from argument i:
# `temptype = oid_meostype(get_fn_expr_rettype(fcinfo->flinfo))`.
_CALL_TYPE = r"(?<![\w.>]){var}\s*=(?!=)\s*oid_meostype\s*\(\s*get_fn_expr_(rettype|argtype)" \
r"\s*\(\s*fcinfo\s*->\s*flinfo\s*(?:,\s*(\d+)\s*)?\)\s*\)"


def _type_choice(body: str, var: str) -> tuple[int | None, str, int | None, str, str] | None:
"""``(k, source, i, linear, other)`` when local ``var`` of wrapper ``body`` is set to
``temptype_supports_linear(t) ? linear : other`` and ``t`` is the temporal type of the
call's return (``source`` "ret") or of its argument ``i`` (``source`` "arg"); ``k`` is
the SQL argument the wrapper otherwise reads ``var`` from (#_caller_index), None when it
reads none."""
m = re.search(_TYPE_CHOICE.format(var=re.escape(var)), body)
if not m:
return None
t = re.search(_CALL_TYPE.format(var=re.escape(m.group(1))), body)
if not t:
return None
k = _caller_index(body, var)
return (k, "ret" if t.group(1) == "rettype" else "arg",
int(t.group(2)) if t.group(2) else None, m.group(2), m.group(3))


def attach_type_derived_args(idl: dict, mdb_src: str | Path,
sql_src: str | Path | None = None,
meos_src: str | Path | None = None) -> tuple[dict, int]:
"""(idl, number of arguments bound) once each SQL signature carries in ``boundArgs`` the
value a wrapper derives from the temporal type of the signature.

A wrapper that reads no interpolation from the call takes the one the type supports,
``interp = temptype_supports_linear(temptype) ? LINEAR : STEP`` with ``temptype`` the
type the function returns (``Tsequenceset_constructor_gaps``) or the type of one of
its arguments (``Temporal_append_tinstant``), and reads argument ``k`` instead only
when the call carries it. A signature stating at most ``k`` arguments therefore passes
LINEAR or STEP as fixed as a literal, the class #attach_temporal_types states for its
temporal type (``linear``, read from ``temptype_supports_linear``), so it is bound on
the signature as #merge_boundargs binds a literal the wrapper starts a local from. Runs
once the temporal types are attached; the wrappers are traced as #merge_boundargs
traces them."""
from parser.sqlfn import _meos_to_mdb, _wrapper_sql_sigs
wrappers = extract_wrappers(mdb_src)
m2d = _meos_to_mdb(meos_src) if meos_src else {}
w2sig = _wrapper_sql_sigs(sql_src) if sql_src else {}
linear = {t: rec.get("linear") for t, rec in (idl.get("temporalTypes") or {}).items()}
n = 0
for func in idl["functions"]:
primary = func.get("mdbC")
if not primary:
continue
ws = [primary] + [w for w in m2d.get(func["name"]) or () if w != primary]
params = [p.get("name") for p in func.get("params", [])]
for sig in func.get("sqlSignatures") or []:
w = _signature_wrapper(func, sig, ws, w2sig) or ws[0]
body = wrappers.get(w)
if not body:
continue
args = _call_args(body, func["name"]) or _call_args(body, "pg_" + func["name"])
for pos, a in enumerate(args or ()):
if pos >= len(params) or not _IDENT.match(a):
continue
choice = _type_choice(body, a)
if choice is None:
continue
k, source, i, lin, other = choice
sargs = sig.get("args") or []
if k is not None and len(sargs) > k:
continue
t = sig.get("ret") if source == "ret" else (
sargs[i] if i is not None and i < len(sargs) else None)
if t not in linear:
continue
bound = sig.setdefault("boundArgs", {})
if params[pos] not in bound:
bound[params[pos]] = lin if linear[t] else other
n += 1
return idl, n


# The SQL argument a wrapper reads: `PG_GETARG_<T>(k)`, or a helper handed the call info
# with the index, as `input_interp_string(fcinfo, 1)` reads argument 1.
_GETARG = re.compile(r"PG_GETARG_\w+\s*\(\s*(\d+)\s*\)|\bfcinfo\s*,\s*(\d+)\s*\)")
Expand Down
12 changes: 11 additions & 1 deletion run.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
from parser.nullable import merge_nullable, extract_param_docs
from parser.nullresult import attach_null_result
from parser.outparam import extract_param_names, merge_outparams
from parser.boundargs import (attach_call_literals, merge_boundargs, merge_sql_arg_params,
from parser.boundargs import (attach_call_literals, attach_type_derived_args, merge_boundargs,
merge_sql_arg_params,
resolve_bound_names, strip_call_literals)
from parser.aggregates import attach_aggregates
from parser.altsqlfn import attach_alt_sql_names
Expand Down Expand Up @@ -380,6 +381,15 @@ def main():
# moment a family is added.
idl = attach_temporal_types(idl, MOBILITYDB_SRC)

# State on each SQL signature the value a wrapper derives from its temporal type where
# the call carries none, the interpolation `temptype_supports_linear(temptype) ? LINEAR :
# STEP` of appendInstant and the sequence set constructors: it reads the classes the
# temporal types above state.
if MDB_SRC.exists():
idl, ntd = attach_type_derived_args(idl, MDB_SRC, sql_src=SQL_SRC, meos_src=MEOS_SRC)
print(f" arguments a wrapper derives from the signature's temporal type: {ntd}",
file=sys.stderr)

# Keep a SQL signature two public functions claim on the one whose C parameters it
# fits, matched by type once the object model and the type relations state the C type
# of each SQL type.
Expand Down
105 changes: 104 additions & 1 deletion tests/test_boundargs.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
import unittest
from pathlib import Path

from parser.boundargs import (_sql_arg_params, attach_call_literals, extract_call_literals,
from parser.boundargs import (_sql_arg_params, attach_call_literals, attach_type_derived_args,
extract_call_literals,
extract_wrappers, merge_boundargs, resolve_bound_names,
strip_call_literals)

Expand Down Expand Up @@ -1132,5 +1133,107 @@ def test_the_number_first_reads_through_the_shared_kernel(self):
self.assertEqual([s.get("sqlArgParams") for s in sigs], [None, ["d", "temp"]])


# The interpolation two wrappers take from the class of a temporal type, of the call's
# argument 1 and of its return, where the call carries none (Temporal_append_tinstant,
# Tsequenceset_constructor_gaps).
TYPE_DERIVED = '''
PGDLLEXPORT Datum Temporal_append_tinstant(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(Temporal_append_tinstant);
Datum
Temporal_append_tinstant(PG_FUNCTION_ARGS)
{
Temporal *temp = PG_GETARG_TEMPORAL_P(0);
TInstant *inst = PG_GETARG_TINSTANT_P(1);
interpType interp;
if (PG_NARGS() == 2 || PG_ARGISNULL(2))
{
MeosType temptype = oid_meostype(get_fn_expr_argtype(fcinfo->flinfo, 1));
interp = temptype_supports_linear(temptype) ? LINEAR : STEP;
}
else
interp = input_interp_string(fcinfo, 2);
Temporal *result = temporal_append_tinstant(temp, inst, interp, 0.0, NULL, false);
PG_RETURN_TEMPORAL_P(result);
}

PGDLLEXPORT Datum Tsequenceset_constructor_gaps(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(Tsequenceset_constructor_gaps);
Datum
Tsequenceset_constructor_gaps(PG_FUNCTION_ARGS)
{
ArrayType *array = PG_GETARG_ARRAYTYPE_P(0);
double maxdist = -1.0;
Interval *maxt = NULL;
MeosType temptype = oid_meostype(get_fn_expr_rettype(fcinfo->flinfo));
interpType interp = temptype_supports_linear(temptype) ? LINEAR : STEP;
if (PG_NARGS() > 1 && ! PG_ARGISNULL(1))
maxt = PG_GETARG_INTERVAL_P(1);
if (PG_NARGS() > 2 && ! PG_ARGISNULL(2))
maxdist = PG_GETARG_FLOAT8(2);
if (PG_NARGS() > 3 && ! PG_ARGISNULL(3))
interp = input_interp_string(fcinfo, 3);
int count;
TInstant **instants = (TInstant **) temparr_extract(array, &count);
TSequenceSet *result = tsequenceset_make_gaps(instants, count, interp, maxt, maxdist);
PG_RETURN_TSEQUENCESET_P(result);
}
'''


class TypeDerivedArgTests(unittest.TestCase):
"""#attach_type_derived_args binds the interpolation a wrapper derives from the
temporal type of a signature, as #DelegatingWrapperTests reads a literal behind a
helper, only on the signatures that do not carry it."""

def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
src = Path(self.tmp.name) / "src"
src.mkdir()
(src / "temporal.c").write_text(TYPE_DERIVED)

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

@staticmethod
def _idl():
def sig(name, args, ret):
return {"sqlName": name, "args": args, "ret": ret}
return {
"temporalTypes": {"tfloat": {"linear": True}, "tint": {"linear": False}},
"functions": [
{"name": "temporal_append_tinstant", "mdbC": "Temporal_append_tinstant",
"params": [{"name": n} for n in
("temp", "inst", "interp", "maxdist", "maxt", "expand")],
"sqlSignatures": [sig("appendInstant", ["tfloat", "tfloat"], "tfloat"),
sig("appendInstant", ["tint", "tint"], "tint"),
sig("appendInstant", ["tfloat", "tfloat", "text"],
"tfloat")]},
{"name": "tsequenceset_make_gaps", "mdbC": "Tsequenceset_constructor_gaps",
"params": [{"name": n} for n in
("instants", "count", "interp", "maxt", "maxdist")],
"sqlSignatures": [sig("tintSeqSetGaps", ["tint[]", "interval", "float"],
"tint"),
sig("tfloatSeqSetGaps",
["tfloat[]", "interval", "float", "text"],
"tfloat")]}]}

def test_the_class_of_the_argument_type_is_bound(self):
idl, n = attach_type_derived_args(self._idl(), self.tmp.name)
sigs = idl["functions"][0]["sqlSignatures"]
self.assertEqual(sigs[0]["boundArgs"], {"interp": "LINEAR"})
self.assertEqual(sigs[1]["boundArgs"], {"interp": "STEP"})

def test_the_class_of_the_return_type_is_bound(self):
idl, _ = attach_type_derived_args(self._idl(), self.tmp.name)
self.assertEqual(idl["functions"][1]["sqlSignatures"][0]["boundArgs"],
{"interp": "STEP"})

def test_a_signature_carrying_the_interpolation_binds_none(self):
idl, n = attach_type_derived_args(self._idl(), self.tmp.name)
self.assertNotIn("boundArgs", idl["functions"][0]["sqlSignatures"][2])
self.assertNotIn("boundArgs", idl["functions"][1]["sqlSignatures"][1])
self.assertEqual(n, 3)


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