mirror of
https://github.com/postgres/postgres.git
synced 2025-11-26 23:43:30 +03:00
Make PL/Python handle domain-type conversions correctly.
Fix PL/Python so that it can handle domains over composite, and so that
it enforces domain constraints correctly in other cases that were not
always done properly before. Notably, it didn't do arrays of domains
right (oversight in commit c12d570fa), and it failed to enforce domain
constraints when returning a composite type containing a domain field,
and if a transform function is being used for a domain's base type then
it failed to enforce domain constraints on the result. Also, in many
places it missed checking domain constraints on null values, because
the plpy_typeio code simply wasn't called for Py_None.
Rather than try to band-aid these problems, I made a significant
refactoring of the plpy_typeio logic. The existing design of recursing
for array and composite members is extended to also treat domains as
containers requiring recursion, and the APIs for the module are cleaned
up and simplified.
The patch also modifies plpy_typeio to rely on the typcache more than
it did before (which was pretty much not at all). This reduces the
need for repetitive lookups, and lets us get rid of an ad-hoc scheme
for detecting changes in composite types. I added a couple of small
features to typcache to help with that.
Although some of this is fixing bugs that long predate v11, I don't
think we should risk a back-patch: it's a significant amount of code
churn, and there've been no complaints from the field about the bugs.
Tom Lane, reviewed by Anthony Bykov
Discussion: https://postgr.es/m/24449.1509393613@sss.pgh.pa.us
This commit is contained in:
@@ -202,7 +202,7 @@ PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc)
|
||||
* return value as a special "void datum" rather than NULL (as is the
|
||||
* case for non-void-returning functions).
|
||||
*/
|
||||
if (proc->result.out.d.typoid == VOIDOID)
|
||||
if (proc->result.typoid == VOIDOID)
|
||||
{
|
||||
if (plrv != Py_None)
|
||||
ereport(ERROR,
|
||||
@@ -212,48 +212,22 @@ PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc)
|
||||
fcinfo->isnull = false;
|
||||
rv = (Datum) 0;
|
||||
}
|
||||
else if (plrv == Py_None)
|
||||
else if (plrv == Py_None &&
|
||||
srfstate && srfstate->iter == NULL)
|
||||
{
|
||||
fcinfo->isnull = true;
|
||||
|
||||
/*
|
||||
* In a SETOF function, the iteration-ending null isn't a real
|
||||
* value; don't pass it through the input function, which might
|
||||
* complain.
|
||||
*/
|
||||
if (srfstate && srfstate->iter == NULL)
|
||||
rv = (Datum) 0;
|
||||
else if (proc->result.is_rowtype < 1)
|
||||
rv = InputFunctionCall(&proc->result.out.d.typfunc,
|
||||
NULL,
|
||||
proc->result.out.d.typioparam,
|
||||
-1);
|
||||
else
|
||||
/* Tuple as None */
|
||||
rv = (Datum) NULL;
|
||||
}
|
||||
else if (proc->result.is_rowtype >= 1)
|
||||
{
|
||||
TupleDesc desc;
|
||||
|
||||
/* make sure it's not an unnamed record */
|
||||
Assert((proc->result.out.d.typoid == RECORDOID &&
|
||||
proc->result.out.d.typmod != -1) ||
|
||||
(proc->result.out.d.typoid != RECORDOID &&
|
||||
proc->result.out.d.typmod == -1));
|
||||
|
||||
desc = lookup_rowtype_tupdesc(proc->result.out.d.typoid,
|
||||
proc->result.out.d.typmod);
|
||||
|
||||
rv = PLyObject_ToCompositeDatum(&proc->result, desc, plrv, false);
|
||||
fcinfo->isnull = (rv == (Datum) NULL);
|
||||
|
||||
ReleaseTupleDesc(desc);
|
||||
fcinfo->isnull = true;
|
||||
rv = (Datum) 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
fcinfo->isnull = false;
|
||||
rv = (proc->result.out.d.func) (&proc->result.out.d, -1, plrv, false);
|
||||
/* Normal conversion of result */
|
||||
rv = PLy_output_convert(&proc->result, plrv,
|
||||
&fcinfo->isnull);
|
||||
}
|
||||
}
|
||||
PG_CATCH();
|
||||
@@ -328,20 +302,32 @@ PLy_exec_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc)
|
||||
PyObject *volatile plargs = NULL;
|
||||
PyObject *volatile plrv = NULL;
|
||||
TriggerData *tdata;
|
||||
TupleDesc rel_descr;
|
||||
|
||||
Assert(CALLED_AS_TRIGGER(fcinfo));
|
||||
|
||||
/*
|
||||
* Input/output conversion for trigger tuples. Use the result TypeInfo
|
||||
* variable to store the tuple conversion info. We do this over again on
|
||||
* each call to cover the possibility that the relation's tupdesc changed
|
||||
* since the trigger was last called. PLy_input_tuple_funcs and
|
||||
* PLy_output_tuple_funcs are responsible for not doing repetitive work.
|
||||
*/
|
||||
tdata = (TriggerData *) fcinfo->context;
|
||||
|
||||
PLy_input_tuple_funcs(&(proc->result), tdata->tg_relation->rd_att);
|
||||
PLy_output_tuple_funcs(&(proc->result), tdata->tg_relation->rd_att);
|
||||
/*
|
||||
* Input/output conversion for trigger tuples. We use the result and
|
||||
* result_in fields to store the tuple conversion info. We do this over
|
||||
* again on each call to cover the possibility that the relation's tupdesc
|
||||
* changed since the trigger was last called. The PLy_xxx_setup_func
|
||||
* calls should only happen once, but PLy_input_setup_tuple and
|
||||
* PLy_output_setup_tuple are responsible for not doing repetitive work.
|
||||
*/
|
||||
rel_descr = RelationGetDescr(tdata->tg_relation);
|
||||
if (proc->result.typoid != rel_descr->tdtypeid)
|
||||
PLy_output_setup_func(&proc->result, proc->mcxt,
|
||||
rel_descr->tdtypeid,
|
||||
rel_descr->tdtypmod,
|
||||
proc);
|
||||
if (proc->result_in.typoid != rel_descr->tdtypeid)
|
||||
PLy_input_setup_func(&proc->result_in, proc->mcxt,
|
||||
rel_descr->tdtypeid,
|
||||
rel_descr->tdtypmod,
|
||||
proc);
|
||||
PLy_output_setup_tuple(&proc->result, rel_descr, proc);
|
||||
PLy_input_setup_tuple(&proc->result_in, rel_descr, proc);
|
||||
|
||||
PG_TRY();
|
||||
{
|
||||
@@ -436,46 +422,12 @@ PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc)
|
||||
args = PyList_New(proc->nargs);
|
||||
for (i = 0; i < proc->nargs; i++)
|
||||
{
|
||||
if (proc->args[i].is_rowtype > 0)
|
||||
{
|
||||
if (fcinfo->argnull[i])
|
||||
arg = NULL;
|
||||
else
|
||||
{
|
||||
HeapTupleHeader td;
|
||||
Oid tupType;
|
||||
int32 tupTypmod;
|
||||
TupleDesc tupdesc;
|
||||
HeapTupleData tmptup;
|
||||
PLyDatumToOb *arginfo = &proc->args[i];
|
||||
|
||||
td = DatumGetHeapTupleHeader(fcinfo->arg[i]);
|
||||
/* Extract rowtype info and find a tupdesc */
|
||||
tupType = HeapTupleHeaderGetTypeId(td);
|
||||
tupTypmod = HeapTupleHeaderGetTypMod(td);
|
||||
tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
|
||||
|
||||
/* Set up I/O funcs if not done yet */
|
||||
if (proc->args[i].is_rowtype != 1)
|
||||
PLy_input_tuple_funcs(&(proc->args[i]), tupdesc);
|
||||
|
||||
/* Build a temporary HeapTuple control structure */
|
||||
tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
|
||||
tmptup.t_data = td;
|
||||
|
||||
arg = PLyDict_FromTuple(&(proc->args[i]), &tmptup, tupdesc);
|
||||
ReleaseTupleDesc(tupdesc);
|
||||
}
|
||||
}
|
||||
if (fcinfo->argnull[i])
|
||||
arg = NULL;
|
||||
else
|
||||
{
|
||||
if (fcinfo->argnull[i])
|
||||
arg = NULL;
|
||||
else
|
||||
{
|
||||
arg = (proc->args[i].in.d.func) (&(proc->args[i].in.d),
|
||||
fcinfo->arg[i]);
|
||||
}
|
||||
}
|
||||
arg = PLy_input_convert(arginfo, fcinfo->arg[i]);
|
||||
|
||||
if (arg == NULL)
|
||||
{
|
||||
@@ -493,7 +445,7 @@ PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc)
|
||||
}
|
||||
|
||||
/* Set up output conversion for functions returning RECORD */
|
||||
if (proc->result.out.d.typoid == RECORDOID)
|
||||
if (proc->result.typoid == RECORDOID)
|
||||
{
|
||||
TupleDesc desc;
|
||||
|
||||
@@ -504,7 +456,7 @@ PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc)
|
||||
"that cannot accept type record")));
|
||||
|
||||
/* cache the output conversion functions */
|
||||
PLy_output_record_funcs(&(proc->result), desc);
|
||||
PLy_output_setup_record(&proc->result, desc, proc);
|
||||
}
|
||||
}
|
||||
PG_CATCH();
|
||||
@@ -723,6 +675,7 @@ static PyObject *
|
||||
PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *rv)
|
||||
{
|
||||
TriggerData *tdata = (TriggerData *) fcinfo->context;
|
||||
TupleDesc rel_descr = RelationGetDescr(tdata->tg_relation);
|
||||
PyObject *pltname,
|
||||
*pltevent,
|
||||
*pltwhen,
|
||||
@@ -790,8 +743,9 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r
|
||||
pltevent = PyString_FromString("INSERT");
|
||||
|
||||
PyDict_SetItemString(pltdata, "old", Py_None);
|
||||
pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple,
|
||||
tdata->tg_relation->rd_att);
|
||||
pytnew = PLy_input_from_tuple(&proc->result_in,
|
||||
tdata->tg_trigtuple,
|
||||
rel_descr);
|
||||
PyDict_SetItemString(pltdata, "new", pytnew);
|
||||
Py_DECREF(pytnew);
|
||||
*rv = tdata->tg_trigtuple;
|
||||
@@ -801,8 +755,9 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r
|
||||
pltevent = PyString_FromString("DELETE");
|
||||
|
||||
PyDict_SetItemString(pltdata, "new", Py_None);
|
||||
pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple,
|
||||
tdata->tg_relation->rd_att);
|
||||
pytold = PLy_input_from_tuple(&proc->result_in,
|
||||
tdata->tg_trigtuple,
|
||||
rel_descr);
|
||||
PyDict_SetItemString(pltdata, "old", pytold);
|
||||
Py_DECREF(pytold);
|
||||
*rv = tdata->tg_trigtuple;
|
||||
@@ -811,12 +766,14 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r
|
||||
{
|
||||
pltevent = PyString_FromString("UPDATE");
|
||||
|
||||
pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_newtuple,
|
||||
tdata->tg_relation->rd_att);
|
||||
pytnew = PLy_input_from_tuple(&proc->result_in,
|
||||
tdata->tg_newtuple,
|
||||
rel_descr);
|
||||
PyDict_SetItemString(pltdata, "new", pytnew);
|
||||
Py_DECREF(pytnew);
|
||||
pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple,
|
||||
tdata->tg_relation->rd_att);
|
||||
pytold = PLy_input_from_tuple(&proc->result_in,
|
||||
tdata->tg_trigtuple,
|
||||
rel_descr);
|
||||
PyDict_SetItemString(pltdata, "old", pytold);
|
||||
Py_DECREF(pytold);
|
||||
*rv = tdata->tg_newtuple;
|
||||
@@ -897,6 +854,9 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r
|
||||
return pltdata;
|
||||
}
|
||||
|
||||
/*
|
||||
* Apply changes requested by a MODIFY return from a trigger function.
|
||||
*/
|
||||
static HeapTuple
|
||||
PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata,
|
||||
HeapTuple otup)
|
||||
@@ -938,7 +898,7 @@ PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata,
|
||||
plkeys = PyDict_Keys(plntup);
|
||||
nkeys = PyList_Size(plkeys);
|
||||
|
||||
tupdesc = tdata->tg_relation->rd_att;
|
||||
tupdesc = RelationGetDescr(tdata->tg_relation);
|
||||
|
||||
modvalues = (Datum *) palloc0(tupdesc->natts * sizeof(Datum));
|
||||
modnulls = (bool *) palloc0(tupdesc->natts * sizeof(bool));
|
||||
@@ -950,7 +910,6 @@ PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata,
|
||||
char *plattstr;
|
||||
int attn;
|
||||
PLyObToDatum *att;
|
||||
Form_pg_attribute attr;
|
||||
|
||||
platt = PyList_GetItem(plkeys, i);
|
||||
if (PyString_Check(platt))
|
||||
@@ -975,7 +934,6 @@ PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot set system attribute \"%s\"",
|
||||
plattstr)));
|
||||
att = &proc->result.out.r.atts[attn - 1];
|
||||
|
||||
plval = PyDict_GetItem(plntup, platt);
|
||||
if (plval == NULL)
|
||||
@@ -983,25 +941,12 @@ PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata,
|
||||
|
||||
Py_INCREF(plval);
|
||||
|
||||
attr = TupleDescAttr(tupdesc, attn - 1);
|
||||
if (plval != Py_None)
|
||||
{
|
||||
modvalues[attn - 1] =
|
||||
(att->func) (att,
|
||||
attr->atttypmod,
|
||||
plval,
|
||||
false);
|
||||
modnulls[attn - 1] = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
modvalues[attn - 1] =
|
||||
InputFunctionCall(&att->typfunc,
|
||||
NULL,
|
||||
att->typioparam,
|
||||
attr->atttypmod);
|
||||
modnulls[attn - 1] = true;
|
||||
}
|
||||
/* We assume proc->result is set up to convert tuples properly */
|
||||
att = &proc->result.u.tuple.atts[attn - 1];
|
||||
|
||||
modvalues[attn - 1] = PLy_output_convert(att,
|
||||
plval,
|
||||
&modnulls[attn - 1]);
|
||||
modrepls[attn - 1] = true;
|
||||
|
||||
Py_DECREF(plval);
|
||||
|
||||
Reference in New Issue
Block a user