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:
Tom Lane 2017-11-16 16:22:57 -05:00
parent 575cead991
commit 687f096ea9
17 changed files with 1412 additions and 963 deletions

View File

@ -68,12 +68,30 @@ AS $$
val = [{'a': 1, 'b': 'boo', 'c': None}, {'d': 2}]
return val
$$;
SELECT test2arr();
SELECT test2arr();
test2arr
--------------------------------------------------------------
{"\"a\"=>\"1\", \"b\"=>\"boo\", \"c\"=>NULL","\"d\"=>\"2\""}
(1 row)
-- test python -> domain over hstore
CREATE DOMAIN hstore_foo AS hstore CHECK(VALUE ? 'foo');
CREATE FUNCTION test2dom(fn text) RETURNS hstore_foo
LANGUAGE plpythonu
TRANSFORM FOR TYPE hstore
AS $$
return {'a': 1, fn: 'boo', 'c': None}
$$;
SELECT test2dom('foo');
test2dom
-----------------------------------
"a"=>"1", "c"=>NULL, "foo"=>"boo"
(1 row)
SELECT test2dom('bar'); -- fail
ERROR: value for domain hstore_foo violates check constraint "hstore_foo_check"
CONTEXT: while creating return value
PL/Python function "test2dom"
-- test as part of prepare/execute
CREATE FUNCTION test3() RETURNS void
LANGUAGE plpythonu

View File

@ -60,7 +60,21 @@ val = [{'a': 1, 'b': 'boo', 'c': None}, {'d': 2}]
return val
$$;
SELECT test2arr();
SELECT test2arr();
-- test python -> domain over hstore
CREATE DOMAIN hstore_foo AS hstore CHECK(VALUE ? 'foo');
CREATE FUNCTION test2dom(fn text) RETURNS hstore_foo
LANGUAGE plpythonu
TRANSFORM FOR TYPE hstore
AS $$
return {'a': 1, fn: 'boo', 'c': None}
$$;
SELECT test2dom('foo');
SELECT test2dom('bar'); -- fail
-- test as part of prepare/execute

View File

@ -377,6 +377,7 @@ lookup_type_cache(Oid type_id, int flags)
typentry->typstorage = typtup->typstorage;
typentry->typtype = typtup->typtype;
typentry->typrelid = typtup->typrelid;
typentry->typelem = typtup->typelem;
/* If it's a domain, immediately thread it into the domain cache list */
if (typentry->typtype == TYPTYPE_DOMAIN)
@ -791,6 +792,12 @@ load_typcache_tupdesc(TypeCacheEntry *typentry)
Assert(typentry->tupDesc->tdrefcount > 0);
typentry->tupDesc->tdrefcount++;
/*
* In future, we could take some pains to not increment the seqno if the
* tupdesc didn't really change; but for now it's not worth it.
*/
typentry->tupDescSeqNo++;
relation_close(rel, AccessShareLock);
}

View File

@ -40,6 +40,7 @@ typedef struct TypeCacheEntry
char typstorage;
char typtype;
Oid typrelid;
Oid typelem;
/*
* Information obtained from opfamily entries
@ -75,9 +76,11 @@ typedef struct TypeCacheEntry
/*
* Tuple descriptor if it's a composite type (row type). NULL if not
* composite or information hasn't yet been requested. (NOTE: this is a
* reference-counted tupledesc.)
* reference-counted tupledesc.) To simplify caching dependent info,
* tupDescSeqNo is incremented each time tupDesc is rebuilt in a session.
*/
TupleDesc tupDesc;
int64 tupDescSeqNo;
/*
* Fields computed when TYPECACHE_RANGE_INFO is requested. Zeroes if not

View File

@ -765,6 +765,76 @@ SELECT * FROM test_type_conversion_array_domain_check_violation();
ERROR: value for domain ordered_pair_domain violates check constraint "ordered_pair_domain_check"
CONTEXT: while creating return value
PL/Python function "test_type_conversion_array_domain_check_violation"
--
-- Arrays of domains
--
CREATE FUNCTION test_read_uint2_array(x uint2[]) RETURNS uint2 AS $$
plpy.info(x, type(x))
return x[0]
$$ LANGUAGE plpythonu;
select test_read_uint2_array(array[1::uint2]);
INFO: ([1], <type 'list'>)
test_read_uint2_array
-----------------------
1
(1 row)
CREATE FUNCTION test_build_uint2_array(x int2) RETURNS uint2[] AS $$
return [x, x]
$$ LANGUAGE plpythonu;
select test_build_uint2_array(1::int2);
test_build_uint2_array
------------------------
{1,1}
(1 row)
select test_build_uint2_array(-1::int2); -- fail
ERROR: value for domain uint2 violates check constraint "uint2_check"
CONTEXT: while creating return value
PL/Python function "test_build_uint2_array"
--
-- ideally this would work, but for now it doesn't, because the return value
-- is [[2,4], [2,4]] which our conversion code thinks should become a 2-D
-- integer array, not an array of arrays.
--
CREATE FUNCTION test_type_conversion_domain_array(x integer[])
RETURNS ordered_pair_domain[] AS $$
return [x, x]
$$ LANGUAGE plpythonu;
select test_type_conversion_domain_array(array[2,4]);
ERROR: return value of function with array return type is not a Python sequence
CONTEXT: while creating return value
PL/Python function "test_type_conversion_domain_array"
select test_type_conversion_domain_array(array[4,2]); -- fail
ERROR: return value of function with array return type is not a Python sequence
CONTEXT: while creating return value
PL/Python function "test_type_conversion_domain_array"
CREATE FUNCTION test_type_conversion_domain_array2(x ordered_pair_domain)
RETURNS integer AS $$
plpy.info(x, type(x))
return x[1]
$$ LANGUAGE plpythonu;
select test_type_conversion_domain_array2(array[2,4]);
INFO: ([2, 4], <type 'list'>)
test_type_conversion_domain_array2
------------------------------------
4
(1 row)
select test_type_conversion_domain_array2(array[4,2]); -- fail
ERROR: value for domain ordered_pair_domain violates check constraint "ordered_pair_domain_check"
CREATE FUNCTION test_type_conversion_array_domain_array(x ordered_pair_domain[])
RETURNS ordered_pair_domain AS $$
plpy.info(x, type(x))
return x[0]
$$ LANGUAGE plpythonu;
select test_type_conversion_array_domain_array(array[array[2,4]::ordered_pair_domain]);
INFO: ([[2, 4]], <type 'list'>)
test_type_conversion_array_domain_array
-----------------------------------------
{2,4}
(1 row)
---
--- Composite types
---
@ -820,6 +890,64 @@ SELECT test_composite_type_input(row(1, 2));
3
(1 row)
--
-- Domains within composite
--
CREATE TYPE nnint_container AS (f1 int, f2 nnint);
CREATE FUNCTION nnint_test(x int, y int) RETURNS nnint_container AS $$
return {'f1': x, 'f2': y}
$$ LANGUAGE plpythonu;
SELECT nnint_test(null, 3);
nnint_test
------------
(,3)
(1 row)
SELECT nnint_test(3, null); -- fail
ERROR: value for domain nnint violates check constraint "nnint_check"
CONTEXT: while creating return value
PL/Python function "nnint_test"
--
-- Domains of composite
--
CREATE DOMAIN ordered_named_pair AS named_pair_2 CHECK((VALUE).i <= (VALUE).j);
CREATE FUNCTION read_ordered_named_pair(p ordered_named_pair) RETURNS integer AS $$
return p['i'] + p['j']
$$ LANGUAGE plpythonu;
SELECT read_ordered_named_pair(row(1, 2));
read_ordered_named_pair
-------------------------
3
(1 row)
SELECT read_ordered_named_pair(row(2, 1)); -- fail
ERROR: value for domain ordered_named_pair violates check constraint "ordered_named_pair_check"
CREATE FUNCTION build_ordered_named_pair(i int, j int) RETURNS ordered_named_pair AS $$
return {'i': i, 'j': j}
$$ LANGUAGE plpythonu;
SELECT build_ordered_named_pair(1,2);
build_ordered_named_pair
--------------------------
(1,2)
(1 row)
SELECT build_ordered_named_pair(2,1); -- fail
ERROR: value for domain ordered_named_pair violates check constraint "ordered_named_pair_check"
CONTEXT: while creating return value
PL/Python function "build_ordered_named_pair"
CREATE FUNCTION build_ordered_named_pairs(i int, j int) RETURNS ordered_named_pair[] AS $$
return [{'i': i, 'j': j}, {'i': i, 'j': j+1}]
$$ LANGUAGE plpythonu;
SELECT build_ordered_named_pairs(1,2);
build_ordered_named_pairs
---------------------------
{"(1,2)","(1,3)"}
(1 row)
SELECT build_ordered_named_pairs(2,1); -- fail
ERROR: value for domain ordered_named_pair violates check constraint "ordered_named_pair_check"
CONTEXT: while creating return value
PL/Python function "build_ordered_named_pairs"
--
-- Prepared statements
--

View File

@ -765,6 +765,76 @@ SELECT * FROM test_type_conversion_array_domain_check_violation();
ERROR: value for domain ordered_pair_domain violates check constraint "ordered_pair_domain_check"
CONTEXT: while creating return value
PL/Python function "test_type_conversion_array_domain_check_violation"
--
-- Arrays of domains
--
CREATE FUNCTION test_read_uint2_array(x uint2[]) RETURNS uint2 AS $$
plpy.info(x, type(x))
return x[0]
$$ LANGUAGE plpythonu;
select test_read_uint2_array(array[1::uint2]);
INFO: ([1], <class 'list'>)
test_read_uint2_array
-----------------------
1
(1 row)
CREATE FUNCTION test_build_uint2_array(x int2) RETURNS uint2[] AS $$
return [x, x]
$$ LANGUAGE plpythonu;
select test_build_uint2_array(1::int2);
test_build_uint2_array
------------------------
{1,1}
(1 row)
select test_build_uint2_array(-1::int2); -- fail
ERROR: value for domain uint2 violates check constraint "uint2_check"
CONTEXT: while creating return value
PL/Python function "test_build_uint2_array"
--
-- ideally this would work, but for now it doesn't, because the return value
-- is [[2,4], [2,4]] which our conversion code thinks should become a 2-D
-- integer array, not an array of arrays.
--
CREATE FUNCTION test_type_conversion_domain_array(x integer[])
RETURNS ordered_pair_domain[] AS $$
return [x, x]
$$ LANGUAGE plpythonu;
select test_type_conversion_domain_array(array[2,4]);
ERROR: return value of function with array return type is not a Python sequence
CONTEXT: while creating return value
PL/Python function "test_type_conversion_domain_array"
select test_type_conversion_domain_array(array[4,2]); -- fail
ERROR: return value of function with array return type is not a Python sequence
CONTEXT: while creating return value
PL/Python function "test_type_conversion_domain_array"
CREATE FUNCTION test_type_conversion_domain_array2(x ordered_pair_domain)
RETURNS integer AS $$
plpy.info(x, type(x))
return x[1]
$$ LANGUAGE plpythonu;
select test_type_conversion_domain_array2(array[2,4]);
INFO: ([2, 4], <class 'list'>)
test_type_conversion_domain_array2
------------------------------------
4
(1 row)
select test_type_conversion_domain_array2(array[4,2]); -- fail
ERROR: value for domain ordered_pair_domain violates check constraint "ordered_pair_domain_check"
CREATE FUNCTION test_type_conversion_array_domain_array(x ordered_pair_domain[])
RETURNS ordered_pair_domain AS $$
plpy.info(x, type(x))
return x[0]
$$ LANGUAGE plpythonu;
select test_type_conversion_array_domain_array(array[array[2,4]::ordered_pair_domain]);
INFO: ([[2, 4]], <class 'list'>)
test_type_conversion_array_domain_array
-----------------------------------------
{2,4}
(1 row)
---
--- Composite types
---
@ -820,6 +890,64 @@ SELECT test_composite_type_input(row(1, 2));
3
(1 row)
--
-- Domains within composite
--
CREATE TYPE nnint_container AS (f1 int, f2 nnint);
CREATE FUNCTION nnint_test(x int, y int) RETURNS nnint_container AS $$
return {'f1': x, 'f2': y}
$$ LANGUAGE plpythonu;
SELECT nnint_test(null, 3);
nnint_test
------------
(,3)
(1 row)
SELECT nnint_test(3, null); -- fail
ERROR: value for domain nnint violates check constraint "nnint_check"
CONTEXT: while creating return value
PL/Python function "nnint_test"
--
-- Domains of composite
--
CREATE DOMAIN ordered_named_pair AS named_pair_2 CHECK((VALUE).i <= (VALUE).j);
CREATE FUNCTION read_ordered_named_pair(p ordered_named_pair) RETURNS integer AS $$
return p['i'] + p['j']
$$ LANGUAGE plpythonu;
SELECT read_ordered_named_pair(row(1, 2));
read_ordered_named_pair
-------------------------
3
(1 row)
SELECT read_ordered_named_pair(row(2, 1)); -- fail
ERROR: value for domain ordered_named_pair violates check constraint "ordered_named_pair_check"
CREATE FUNCTION build_ordered_named_pair(i int, j int) RETURNS ordered_named_pair AS $$
return {'i': i, 'j': j}
$$ LANGUAGE plpythonu;
SELECT build_ordered_named_pair(1,2);
build_ordered_named_pair
--------------------------
(1,2)
(1 row)
SELECT build_ordered_named_pair(2,1); -- fail
ERROR: value for domain ordered_named_pair violates check constraint "ordered_named_pair_check"
CONTEXT: while creating return value
PL/Python function "build_ordered_named_pair"
CREATE FUNCTION build_ordered_named_pairs(i int, j int) RETURNS ordered_named_pair[] AS $$
return [{'i': i, 'j': j}, {'i': i, 'j': j+1}]
$$ LANGUAGE plpythonu;
SELECT build_ordered_named_pairs(1,2);
build_ordered_named_pairs
---------------------------
{"(1,2)","(1,3)"}
(1 row)
SELECT build_ordered_named_pairs(2,1); -- fail
ERROR: value for domain ordered_named_pair violates check constraint "ordered_named_pair_check"
CONTEXT: while creating return value
PL/Python function "build_ordered_named_pairs"
--
-- Prepared statements
--

View File

@ -9,6 +9,7 @@
#include <limits.h>
#include "access/xact.h"
#include "catalog/pg_type.h"
#include "mb/pg_wchar.h"
#include "utils/memutils.h"
@ -106,6 +107,7 @@ static PyObject *
PLy_cursor_query(const char *query)
{
PLyCursorObject *cursor;
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
volatile MemoryContext oldcontext;
volatile ResourceOwner oldowner;
@ -116,7 +118,11 @@ PLy_cursor_query(const char *query)
cursor->mcxt = AllocSetContextCreate(TopMemoryContext,
"PL/Python cursor context",
ALLOCSET_DEFAULT_SIZES);
PLy_typeinfo_init(&cursor->result, cursor->mcxt);
/* Initialize for converting result tuples to Python */
PLy_input_setup_func(&cursor->result, cursor->mcxt,
RECORDOID, -1,
exec_ctx->curr_proc);
oldcontext = CurrentMemoryContext;
oldowner = CurrentResourceOwner;
@ -125,7 +131,6 @@ PLy_cursor_query(const char *query)
PG_TRY();
{
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
SPIPlanPtr plan;
Portal portal;
@ -166,6 +171,7 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
volatile int nargs;
int i;
PLyPlanObject *plan;
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
volatile MemoryContext oldcontext;
volatile ResourceOwner oldowner;
@ -208,7 +214,11 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
cursor->mcxt = AllocSetContextCreate(TopMemoryContext,
"PL/Python cursor context",
ALLOCSET_DEFAULT_SIZES);
PLy_typeinfo_init(&cursor->result, cursor->mcxt);
/* Initialize for converting result tuples to Python */
PLy_input_setup_func(&cursor->result, cursor->mcxt,
RECORDOID, -1,
exec_ctx->curr_proc);
oldcontext = CurrentMemoryContext;
oldowner = CurrentResourceOwner;
@ -217,7 +227,6 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
PG_TRY();
{
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
Portal portal;
char *volatile nulls;
volatile int j;
@ -229,39 +238,24 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
for (j = 0; j < nargs; j++)
{
PLyObToDatum *arg = &plan->args[j];
PyObject *elem;
elem = PySequence_GetItem(args, j);
if (elem != Py_None)
PG_TRY();
{
PG_TRY();
{
plan->values[j] =
plan->args[j].out.d.func(&(plan->args[j].out.d),
-1,
elem,
false);
}
PG_CATCH();
{
Py_DECREF(elem);
PG_RE_THROW();
}
PG_END_TRY();
bool isnull;
Py_DECREF(elem);
nulls[j] = ' ';
plan->values[j] = PLy_output_convert(arg, elem, &isnull);
nulls[j] = isnull ? 'n' : ' ';
}
else
PG_CATCH();
{
Py_DECREF(elem);
plan->values[j] =
InputFunctionCall(&(plan->args[j].out.d.typfunc),
NULL,
plan->args[j].out.d.typioparam,
-1);
nulls[j] = 'n';
PG_RE_THROW();
}
PG_END_TRY();
Py_DECREF(elem);
}
portal = SPI_cursor_open(NULL, plan->plan, plan->values, nulls,
@ -281,7 +275,7 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
/* cleanup plan->values array */
for (k = 0; k < nargs; k++)
{
if (!plan->args[k].out.d.typbyval &&
if (!plan->args[k].typbyval &&
(plan->values[k] != PointerGetDatum(NULL)))
{
pfree(DatumGetPointer(plan->values[k]));
@ -298,7 +292,7 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
for (i = 0; i < nargs; i++)
{
if (!plan->args[i].out.d.typbyval &&
if (!plan->args[i].typbyval &&
(plan->values[i] != PointerGetDatum(NULL)))
{
pfree(DatumGetPointer(plan->values[i]));
@ -339,6 +333,7 @@ PLy_cursor_iternext(PyObject *self)
{
PLyCursorObject *cursor;
PyObject *ret;
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
volatile MemoryContext oldcontext;
volatile ResourceOwner oldowner;
Portal portal;
@ -374,11 +369,11 @@ PLy_cursor_iternext(PyObject *self)
}
else
{
if (cursor->result.is_rowtype != 1)
PLy_input_tuple_funcs(&cursor->result, SPI_tuptable->tupdesc);
PLy_input_setup_tuple(&cursor->result, SPI_tuptable->tupdesc,
exec_ctx->curr_proc);
ret = PLyDict_FromTuple(&cursor->result, SPI_tuptable->vals[0],
SPI_tuptable->tupdesc);
ret = PLy_input_from_tuple(&cursor->result, SPI_tuptable->vals[0],
SPI_tuptable->tupdesc);
}
SPI_freetuptable(SPI_tuptable);
@ -401,6 +396,7 @@ PLy_cursor_fetch(PyObject *self, PyObject *args)
PLyCursorObject *cursor;
int count;
PLyResultObject *ret;
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
volatile MemoryContext oldcontext;
volatile ResourceOwner oldowner;
Portal portal;
@ -437,9 +433,6 @@ PLy_cursor_fetch(PyObject *self, PyObject *args)
{
SPI_cursor_fetch(portal, true, count);
if (cursor->result.is_rowtype != 1)
PLy_input_tuple_funcs(&cursor->result, SPI_tuptable->tupdesc);
Py_DECREF(ret->status);
ret->status = PyInt_FromLong(SPI_OK_FETCH);
@ -465,11 +458,14 @@ PLy_cursor_fetch(PyObject *self, PyObject *args)
Py_DECREF(ret->rows);
ret->rows = PyList_New(SPI_processed);
PLy_input_setup_tuple(&cursor->result, SPI_tuptable->tupdesc,
exec_ctx->curr_proc);
for (i = 0; i < SPI_processed; i++)
{
PyObject *row = PLyDict_FromTuple(&cursor->result,
SPI_tuptable->vals[i],
SPI_tuptable->tupdesc);
PyObject *row = PLy_input_from_tuple(&cursor->result,
SPI_tuptable->vals[i],
SPI_tuptable->tupdesc);
PyList_SetItem(ret->rows, i, row);
}

View File

@ -12,7 +12,7 @@ typedef struct PLyCursorObject
{
PyObject_HEAD
char *portalname;
PLyTypeInfo result;
PLyDatumToOb result;
bool closed;
MemoryContext mcxt;
} PLyCursorObject;

View File

@ -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);

View File

@ -318,7 +318,12 @@ plpython_inline_handler(PG_FUNCTION_ARGS)
ALLOCSET_DEFAULT_SIZES);
proc.pyname = MemoryContextStrdup(proc.mcxt, "__plpython_inline_block");
proc.langid = codeblock->langOid;
proc.result.out.d.typoid = VOIDOID;
/*
* This is currently sufficient to get PLy_exec_function to work, but
* someday we might need to be honest and use PLy_output_setup_func.
*/
proc.result.typoid = VOIDOID;
/*
* Push execution context onto stack. It is important that this get

View File

@ -16,7 +16,7 @@ typedef struct PLyPlanObject
int nargs;
Oid *types;
Datum *values;
PLyTypeInfo *args;
PLyObToDatum *args;
MemoryContext mcxt;
} PLyPlanObject;

View File

@ -15,6 +15,7 @@
#include "utils/builtins.h"
#include "utils/hsearch.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/syscache.h"
@ -29,7 +30,6 @@
static HTAB *PLy_procedure_cache = NULL;
static PLyProcedure *PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger);
static bool PLy_procedure_argument_valid(PLyTypeInfo *arg);
static bool PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup);
static char *PLy_procedure_munge_source(const char *name, const char *src);
@ -165,6 +165,7 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
*ptr = '_';
}
/* Create long-lived context that all procedure info will live in */
cxt = AllocSetContextCreate(TopMemoryContext,
procName,
ALLOCSET_DEFAULT_SIZES);
@ -188,11 +189,9 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
proc->fn_tid = procTup->t_self;
proc->fn_readonly = (procStruct->provolatile != PROVOLATILE_VOLATILE);
proc->is_setof = procStruct->proretset;
PLy_typeinfo_init(&proc->result, proc->mcxt);
proc->src = NULL;
proc->argnames = NULL;
for (i = 0; i < FUNC_MAX_ARGS; i++)
PLy_typeinfo_init(&proc->args[i], proc->mcxt);
proc->args = NULL;
proc->nargs = 0;
proc->langid = procStruct->prolang;
protrftypes_datum = SysCacheGetAttr(PROCOID, procTup,
@ -211,50 +210,48 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
*/
if (!is_trigger)
{
Oid rettype = procStruct->prorettype;
HeapTuple rvTypeTup;
Form_pg_type rvTypeStruct;
rvTypeTup = SearchSysCache1(TYPEOID,
ObjectIdGetDatum(procStruct->prorettype));
rvTypeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(rettype));
if (!HeapTupleIsValid(rvTypeTup))
elog(ERROR, "cache lookup failed for type %u",
procStruct->prorettype);
elog(ERROR, "cache lookup failed for type %u", rettype);
rvTypeStruct = (Form_pg_type) GETSTRUCT(rvTypeTup);
/* Disallow pseudotype result, except for void or record */
if (rvTypeStruct->typtype == TYPTYPE_PSEUDO)
{
if (procStruct->prorettype == TRIGGEROID)
if (rettype == VOIDOID ||
rettype == RECORDOID)
/* okay */ ;
else if (rettype == TRIGGEROID || rettype == EVTTRIGGEROID)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("trigger functions can only be called as triggers")));
else if (procStruct->prorettype != VOIDOID &&
procStruct->prorettype != RECORDOID)
else
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("PL/Python functions cannot return type %s",
format_type_be(procStruct->prorettype))));
format_type_be(rettype))));
}
if (rvTypeStruct->typtype == TYPTYPE_COMPOSITE ||
procStruct->prorettype == RECORDOID)
{
/*
* Tuple: set up later, during first call to
* PLy_function_handler
*/
proc->result.out.d.typoid = procStruct->prorettype;
proc->result.out.d.typmod = -1;
proc->result.is_rowtype = 2;
}
else
{
/* do the real work */
PLy_output_datum_func(&proc->result, rvTypeTup, proc->langid, proc->trftypes);
}
/* set up output function for procedure result */
PLy_output_setup_func(&proc->result, proc->mcxt,
rettype, -1, proc);
ReleaseSysCache(rvTypeTup);
}
else
{
/*
* In a trigger function, we use proc->result and proc->result_in
* for converting tuples, but we don't yet have enough info to set
* them up. PLy_exec_trigger will deal with it.
*/
proc->result.typoid = InvalidOid;
proc->result_in.typoid = InvalidOid;
}
/*
* Now get information required for input conversion of the
@ -287,7 +284,10 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
}
}
/* Allocate arrays for per-input-argument data */
proc->argnames = (char **) palloc0(sizeof(char *) * proc->nargs);
proc->args = (PLyDatumToOb *) palloc0(sizeof(PLyDatumToOb) * proc->nargs);
for (i = pos = 0; i < total; i++)
{
HeapTuple argTypeTup;
@ -306,28 +306,17 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
elog(ERROR, "cache lookup failed for type %u", types[i]);
argTypeStruct = (Form_pg_type) GETSTRUCT(argTypeTup);
/* check argument type is OK, set up I/O function info */
switch (argTypeStruct->typtype)
{
case TYPTYPE_PSEUDO:
/* Disallow pseudotype argument */
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("PL/Python functions cannot accept type %s",
format_type_be(types[i]))));
break;
case TYPTYPE_COMPOSITE:
/* we'll set IO funcs at first call */
proc->args[pos].is_rowtype = 2;
break;
default:
PLy_input_datum_func(&(proc->args[pos]),
types[i],
argTypeTup,
proc->langid,
proc->trftypes);
break;
}
/* disallow pseudotype arguments */
if (argTypeStruct->typtype == TYPTYPE_PSEUDO)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("PL/Python functions cannot accept type %s",
format_type_be(types[i]))));
/* set up I/O function info */
PLy_input_setup_func(&proc->args[pos], proc->mcxt,
types[i], -1, /* typmod not known */
proc);
/* get argument name */
proc->argnames[pos] = names ? pstrdup(names[i]) : NULL;
@ -424,54 +413,12 @@ PLy_procedure_delete(PLyProcedure *proc)
MemoryContextDelete(proc->mcxt);
}
/*
* Check if our cached information about a datatype is still valid
*/
static bool
PLy_procedure_argument_valid(PLyTypeInfo *arg)
{
HeapTuple relTup;
bool valid;
/* Nothing to cache unless type is composite */
if (arg->is_rowtype != 1)
return true;
/*
* Zero typ_relid means that we got called on an output argument of a
* function returning an unnamed record type; the info for it can't
* change.
*/
if (!OidIsValid(arg->typ_relid))
return true;
/* Else we should have some cached data */
Assert(TransactionIdIsValid(arg->typrel_xmin));
Assert(ItemPointerIsValid(&arg->typrel_tid));
/* Get the pg_class tuple for the data type */
relTup = SearchSysCache1(RELOID, ObjectIdGetDatum(arg->typ_relid));
if (!HeapTupleIsValid(relTup))
elog(ERROR, "cache lookup failed for relation %u", arg->typ_relid);
/* If it has changed, the cached data is not valid */
valid = (arg->typrel_xmin == HeapTupleHeaderGetRawXmin(relTup->t_data) &&
ItemPointerEquals(&arg->typrel_tid, &relTup->t_self));
ReleaseSysCache(relTup);
return valid;
}
/*
* Decide whether a cached PLyProcedure struct is still valid
*/
static bool
PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup)
{
int i;
bool valid;
if (proc == NULL)
return false;
@ -480,22 +427,7 @@ PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup)
ItemPointerEquals(&proc->fn_tid, &procTup->t_self)))
return false;
/* Else check the input argument datatypes */
valid = true;
for (i = 0; i < proc->nargs; i++)
{
valid = PLy_procedure_argument_valid(&proc->args[i]);
/* Short-circuit on first changed argument */
if (!valid)
break;
}
/* if the output type is composite, it might have changed */
if (valid)
valid = PLy_procedure_argument_valid(&proc->result);
return valid;
return true;
}
static char *

View File

@ -31,12 +31,12 @@ typedef struct PLyProcedure
ItemPointerData fn_tid;
bool fn_readonly;
bool is_setof; /* true, if procedure returns result set */
PLyTypeInfo result; /* also used to store info for trigger tuple
* type */
PLyObToDatum result; /* Function result output conversion info */
PLyDatumToOb result_in; /* For converting input tuples in a trigger */
char *src; /* textual procedure code, after mangling */
char **argnames; /* Argument names */
PLyTypeInfo args[FUNC_MAX_ARGS];
int nargs;
PLyDatumToOb *args; /* Argument input conversion info */
int nargs; /* Number of elements in above arrays */
Oid langid; /* OID of plpython pg_language entry */
List *trftypes; /* OID list of transform types */
PyObject *code; /* compiled procedure code */

View File

@ -46,6 +46,7 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
PyObject *list = NULL;
PyObject *volatile optr = NULL;
char *query;
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
volatile MemoryContext oldcontext;
volatile ResourceOwner oldowner;
volatile int nargs;
@ -71,9 +72,9 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
nargs = list ? PySequence_Length(list) : 0;
plan->nargs = nargs;
plan->types = nargs ? palloc(sizeof(Oid) * nargs) : NULL;
plan->values = nargs ? palloc(sizeof(Datum) * nargs) : NULL;
plan->args = nargs ? palloc(sizeof(PLyTypeInfo) * nargs) : NULL;
plan->types = nargs ? palloc0(sizeof(Oid) * nargs) : NULL;
plan->values = nargs ? palloc0(sizeof(Datum) * nargs) : NULL;
plan->args = nargs ? palloc0(sizeof(PLyObToDatum) * nargs) : NULL;
MemoryContextSwitchTo(oldcontext);
@ -85,22 +86,10 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
PG_TRY();
{
int i;
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
/*
* the other loop might throw an exception, if PLyTypeInfo member
* isn't properly initialized the Py_DECREF(plan) will go boom
*/
for (i = 0; i < nargs; i++)
{
PLy_typeinfo_init(&plan->args[i], plan->mcxt);
plan->values[i] = PointerGetDatum(NULL);
}
for (i = 0; i < nargs; i++)
{
char *sptr;
HeapTuple typeTup;
Oid typeId;
int32 typmod;
@ -124,11 +113,6 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
parseTypeString(sptr, &typeId, &typmod, false);
typeTup = SearchSysCache1(TYPEOID,
ObjectIdGetDatum(typeId));
if (!HeapTupleIsValid(typeTup))
elog(ERROR, "cache lookup failed for type %u", typeId);
Py_DECREF(optr);
/*
@ -138,8 +122,9 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
optr = NULL;
plan->types[i] = typeId;
PLy_output_datum_func(&plan->args[i], typeTup, exec_ctx->curr_proc->langid, exec_ctx->curr_proc->trftypes);
ReleaseSysCache(typeTup);
PLy_output_setup_func(&plan->args[i], plan->mcxt,
typeId, typmod,
exec_ctx->curr_proc);
}
pg_verifymbstr(query, strlen(query), false);
@ -253,39 +238,24 @@ PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
for (j = 0; j < nargs; j++)
{
PLyObToDatum *arg = &plan->args[j];
PyObject *elem;
elem = PySequence_GetItem(list, j);
if (elem != Py_None)
PG_TRY();
{
PG_TRY();
{
plan->values[j] =
plan->args[j].out.d.func(&(plan->args[j].out.d),
-1,
elem,
false);
}
PG_CATCH();
{
Py_DECREF(elem);
PG_RE_THROW();
}
PG_END_TRY();
bool isnull;
Py_DECREF(elem);
nulls[j] = ' ';
plan->values[j] = PLy_output_convert(arg, elem, &isnull);
nulls[j] = isnull ? 'n' : ' ';
}
else
PG_CATCH();
{
Py_DECREF(elem);
plan->values[j] =
InputFunctionCall(&(plan->args[j].out.d.typfunc),
NULL,
plan->args[j].out.d.typioparam,
-1);
nulls[j] = 'n';
PG_RE_THROW();
}
PG_END_TRY();
Py_DECREF(elem);
}
rv = SPI_execute_plan(plan->plan, plan->values, nulls,
@ -306,7 +276,7 @@ PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
*/
for (k = 0; k < nargs; k++)
{
if (!plan->args[k].out.d.typbyval &&
if (!plan->args[k].typbyval &&
(plan->values[k] != PointerGetDatum(NULL)))
{
pfree(DatumGetPointer(plan->values[k]));
@ -321,7 +291,7 @@ PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
for (i = 0; i < nargs; i++)
{
if (!plan->args[i].out.d.typbyval &&
if (!plan->args[i].typbyval &&
(plan->values[i] != PointerGetDatum(NULL)))
{
pfree(DatumGetPointer(plan->values[i]));
@ -386,6 +356,7 @@ static PyObject *
PLy_spi_execute_fetch_result(SPITupleTable *tuptable, uint64 rows, int status)
{
PLyResultObject *result;
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
volatile MemoryContext oldcontext;
result = (PLyResultObject *) PLy_result_new();
@ -401,7 +372,7 @@ PLy_spi_execute_fetch_result(SPITupleTable *tuptable, uint64 rows, int status)
}
else if (status > 0 && tuptable != NULL)
{
PLyTypeInfo args;
PLyDatumToOb ininfo;
MemoryContext cxt;
Py_DECREF(result->nrows);
@ -412,7 +383,10 @@ PLy_spi_execute_fetch_result(SPITupleTable *tuptable, uint64 rows, int status)
cxt = AllocSetContextCreate(CurrentMemoryContext,
"PL/Python temp context",
ALLOCSET_DEFAULT_SIZES);
PLy_typeinfo_init(&args, cxt);
/* Initialize for converting result tuples to Python */
PLy_input_setup_func(&ininfo, cxt, RECORDOID, -1,
exec_ctx->curr_proc);
oldcontext = CurrentMemoryContext;
PG_TRY();
@ -436,12 +410,14 @@ PLy_spi_execute_fetch_result(SPITupleTable *tuptable, uint64 rows, int status)
Py_DECREF(result->rows);
result->rows = PyList_New(rows);
PLy_input_tuple_funcs(&args, tuptable->tupdesc);
PLy_input_setup_tuple(&ininfo, tuptable->tupdesc,
exec_ctx->curr_proc);
for (i = 0; i < rows; i++)
{
PyObject *row = PLyDict_FromTuple(&args,
tuptable->vals[i],
tuptable->tupdesc);
PyObject *row = PLy_input_from_tuple(&ininfo,
tuptable->vals[i],
tuptable->tupdesc);
PyList_SetItem(result->rows, i, row);
}

File diff suppressed because it is too large Load Diff

View File

@ -6,117 +6,169 @@
#define PLPY_TYPEIO_H
#include "access/htup.h"
#include "access/tupdesc.h"
#include "fmgr.h"
#include "storage/itemptr.h"
#include "utils/typcache.h"
struct PLyProcedure; /* avoid requiring plpy_procedure.h here */
/*
* Conversion from PostgreSQL Datum to a Python object.
* "Input" conversion from PostgreSQL Datum to a Python object.
*
* arg is the previously-set-up conversion data, val is the value to convert.
* val mustn't be NULL.
*
* Note: the conversion data structs should be regarded as private to
* plpy_typeio.c. We declare them here only so that other modules can
* define structs containing them.
*/
struct PLyDatumToOb;
typedef PyObject *(*PLyDatumToObFunc) (struct PLyDatumToOb *arg, Datum val);
typedef struct PLyDatumToOb PLyDatumToOb; /* forward reference */
typedef struct PLyDatumToOb
typedef PyObject *(*PLyDatumToObFunc) (PLyDatumToOb *arg, Datum val);
typedef struct PLyScalarToOb
{
PLyDatumToObFunc func;
FmgrInfo typfunc; /* The type's output function */
FmgrInfo typtransform; /* from-SQL transform */
Oid typoid; /* The OID of the type */
int32 typmod; /* The typmod of the type */
Oid typioparam;
bool typbyval;
int16 typlen;
char typalign;
struct PLyDatumToOb *elm;
} PLyDatumToOb;
FmgrInfo typfunc; /* lookup info for type's output function */
} PLyScalarToOb;
typedef struct PLyArrayToOb
{
PLyDatumToOb *elm; /* conversion info for array's element type */
} PLyArrayToOb;
typedef struct PLyTupleToOb
{
PLyDatumToOb *atts;
int natts;
/* If we're dealing with a RECORD type, actual descriptor is here: */
TupleDesc recdesc;
/* If we're dealing with a named composite type, these fields are set: */
TypeCacheEntry *typentry; /* typcache entry for type */
int64 tupdescseq; /* last tupdesc seqno seen in typcache */
/* These fields are NULL/0 if not yet set: */
PLyDatumToOb *atts; /* array of per-column conversion info */
int natts; /* length of array */
} PLyTupleToOb;
typedef union PLyTypeInput
typedef struct PLyTransformToOb
{
PLyDatumToOb d;
PLyTupleToOb r;
} PLyTypeInput;
FmgrInfo typtransform; /* lookup info for from-SQL transform func */
} PLyTransformToOb;
/*
* Conversion from Python object to a PostgreSQL Datum.
*
* The 'inarray' argument to the conversion function is true, if the
* converted value was in an array (Python list). It is used to give a
* better error message in some cases.
*/
struct PLyObToDatum;
typedef Datum (*PLyObToDatumFunc) (struct PLyObToDatum *arg, int32 typmod, PyObject *val, bool inarray);
typedef struct PLyObToDatum
struct PLyDatumToOb
{
PLyObToDatumFunc func;
FmgrInfo typfunc; /* The type's input function */
FmgrInfo typtransform; /* to-SQL transform */
Oid typoid; /* The OID of the type */
int32 typmod; /* The typmod of the type */
Oid typioparam;
bool typbyval;
PLyDatumToObFunc func; /* conversion control function */
Oid typoid; /* OID of the source type */
int32 typmod; /* typmod of the source type */
bool typbyval; /* its physical representation details */
int16 typlen;
char typalign;
struct PLyObToDatum *elm;
} PLyObToDatum;
MemoryContext mcxt; /* context this info is stored in */
union /* conversion-type-specific data */
{
PLyScalarToOb scalar;
PLyArrayToOb array;
PLyTupleToOb tuple;
PLyTransformToOb transform;
} u;
};
/*
* "Output" conversion from Python object to a PostgreSQL Datum.
*
* arg is the previously-set-up conversion data, val is the value to convert.
*
* *isnull is set to true if val is Py_None, false otherwise.
* (The conversion function *must* be called even for Py_None,
* so that domain constraints can be checked.)
*
* inarray is true if the converted value was in an array (Python list).
* It is used to give a better error message in some cases.
*/
typedef struct PLyObToDatum PLyObToDatum; /* forward reference */
typedef Datum (*PLyObToDatumFunc) (PLyObToDatum *arg, PyObject *val,
bool *isnull,
bool inarray);
typedef struct PLyObToScalar
{
FmgrInfo typfunc; /* lookup info for type's input function */
Oid typioparam; /* argument to pass to it */
} PLyObToScalar;
typedef struct PLyObToArray
{
PLyObToDatum *elm; /* conversion info for array's element type */
Oid elmbasetype; /* element base type */
} PLyObToArray;
typedef struct PLyObToTuple
{
PLyObToDatum *atts;
int natts;
/* If we're dealing with a RECORD type, actual descriptor is here: */
TupleDesc recdesc;
/* If we're dealing with a named composite type, these fields are set: */
TypeCacheEntry *typentry; /* typcache entry for type */
int64 tupdescseq; /* last tupdesc seqno seen in typcache */
/* These fields are NULL/0 if not yet set: */
PLyObToDatum *atts; /* array of per-column conversion info */
int natts; /* length of array */
/* We might need to convert using record_in(); if so, cache info here */
FmgrInfo recinfunc; /* lookup info for record_in */
} PLyObToTuple;
typedef union PLyTypeOutput
typedef struct PLyObToDomain
{
PLyObToDatum d;
PLyObToTuple r;
} PLyTypeOutput;
PLyObToDatum *base; /* conversion info for domain's base type */
void *domain_info; /* cache space for domain_check() */
} PLyObToDomain;
/* all we need to move PostgreSQL data to Python objects,
* and vice versa
*/
typedef struct PLyTypeInfo
typedef struct PLyObToTransform
{
PLyTypeInput in;
PLyTypeOutput out;
FmgrInfo typtransform; /* lookup info for to-SQL transform function */
} PLyObToTransform;
/*
* is_rowtype can be: -1 = not known yet (initial state); 0 = scalar
* datatype; 1 = rowtype; 2 = rowtype, but I/O functions not set up yet
*/
int is_rowtype;
/* used to check if the type has been modified */
Oid typ_relid;
TransactionId typrel_xmin;
ItemPointerData typrel_tid;
struct PLyObToDatum
{
PLyObToDatumFunc func; /* conversion control function */
Oid typoid; /* OID of the target type */
int32 typmod; /* typmod of the target type */
bool typbyval; /* its physical representation details */
int16 typlen;
char typalign;
MemoryContext mcxt; /* context this info is stored in */
union /* conversion-type-specific data */
{
PLyObToScalar scalar;
PLyObToArray array;
PLyObToTuple tuple;
PLyObToDomain domain;
PLyObToTransform transform;
} u;
};
/* context for subsidiary data (doesn't belong to this struct though) */
MemoryContext mcxt;
} PLyTypeInfo;
extern void PLy_typeinfo_init(PLyTypeInfo *arg, MemoryContext mcxt);
extern PyObject *PLy_input_convert(PLyDatumToOb *arg, Datum val);
extern Datum PLy_output_convert(PLyObToDatum *arg, PyObject *val,
bool *isnull);
extern void PLy_input_datum_func(PLyTypeInfo *arg, Oid typeOid, HeapTuple typeTup, Oid langid, List *trftypes);
extern void PLy_output_datum_func(PLyTypeInfo *arg, HeapTuple typeTup, Oid langid, List *trftypes);
extern PyObject *PLy_input_from_tuple(PLyDatumToOb *arg, HeapTuple tuple,
TupleDesc desc);
extern void PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc);
extern void PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc);
extern void PLy_input_setup_func(PLyDatumToOb *arg, MemoryContext arg_mcxt,
Oid typeOid, int32 typmod,
struct PLyProcedure *proc);
extern void PLy_output_setup_func(PLyObToDatum *arg, MemoryContext arg_mcxt,
Oid typeOid, int32 typmod,
struct PLyProcedure *proc);
extern void PLy_output_record_funcs(PLyTypeInfo *arg, TupleDesc desc);
extern void PLy_input_setup_tuple(PLyDatumToOb *arg, TupleDesc desc,
struct PLyProcedure *proc);
extern void PLy_output_setup_tuple(PLyObToDatum *arg, TupleDesc desc,
struct PLyProcedure *proc);
/* conversion from Python objects to composite Datums */
extern Datum PLyObject_ToCompositeDatum(PLyTypeInfo *info, TupleDesc desc, PyObject *plrv, bool isarray);
extern void PLy_output_setup_record(PLyObToDatum *arg, TupleDesc desc,
struct PLyProcedure *proc);
/* conversion from heap tuples to Python dictionaries */
extern PyObject *PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc);
/* conversion from Python objects to C strings */
/* conversion from Python objects to C strings --- exported for transforms */
extern char *PLyObject_AsString(PyObject *plrv);
#endif /* PLPY_TYPEIO_H */

View File

@ -387,6 +387,55 @@ $$ LANGUAGE plpythonu;
SELECT * FROM test_type_conversion_array_domain_check_violation();
--
-- Arrays of domains
--
CREATE FUNCTION test_read_uint2_array(x uint2[]) RETURNS uint2 AS $$
plpy.info(x, type(x))
return x[0]
$$ LANGUAGE plpythonu;
select test_read_uint2_array(array[1::uint2]);
CREATE FUNCTION test_build_uint2_array(x int2) RETURNS uint2[] AS $$
return [x, x]
$$ LANGUAGE plpythonu;
select test_build_uint2_array(1::int2);
select test_build_uint2_array(-1::int2); -- fail
--
-- ideally this would work, but for now it doesn't, because the return value
-- is [[2,4], [2,4]] which our conversion code thinks should become a 2-D
-- integer array, not an array of arrays.
--
CREATE FUNCTION test_type_conversion_domain_array(x integer[])
RETURNS ordered_pair_domain[] AS $$
return [x, x]
$$ LANGUAGE plpythonu;
select test_type_conversion_domain_array(array[2,4]);
select test_type_conversion_domain_array(array[4,2]); -- fail
CREATE FUNCTION test_type_conversion_domain_array2(x ordered_pair_domain)
RETURNS integer AS $$
plpy.info(x, type(x))
return x[1]
$$ LANGUAGE plpythonu;
select test_type_conversion_domain_array2(array[2,4]);
select test_type_conversion_domain_array2(array[4,2]); -- fail
CREATE FUNCTION test_type_conversion_array_domain_array(x ordered_pair_domain[])
RETURNS ordered_pair_domain AS $$
plpy.info(x, type(x))
return x[0]
$$ LANGUAGE plpythonu;
select test_type_conversion_array_domain_array(array[array[2,4]::ordered_pair_domain]);
---
--- Composite types
---
@ -430,6 +479,48 @@ ALTER TYPE named_pair RENAME TO named_pair_2;
SELECT test_composite_type_input(row(1, 2));
--
-- Domains within composite
--
CREATE TYPE nnint_container AS (f1 int, f2 nnint);
CREATE FUNCTION nnint_test(x int, y int) RETURNS nnint_container AS $$
return {'f1': x, 'f2': y}
$$ LANGUAGE plpythonu;
SELECT nnint_test(null, 3);
SELECT nnint_test(3, null); -- fail
--
-- Domains of composite
--
CREATE DOMAIN ordered_named_pair AS named_pair_2 CHECK((VALUE).i <= (VALUE).j);
CREATE FUNCTION read_ordered_named_pair(p ordered_named_pair) RETURNS integer AS $$
return p['i'] + p['j']
$$ LANGUAGE plpythonu;
SELECT read_ordered_named_pair(row(1, 2));
SELECT read_ordered_named_pair(row(2, 1)); -- fail
CREATE FUNCTION build_ordered_named_pair(i int, j int) RETURNS ordered_named_pair AS $$
return {'i': i, 'j': j}
$$ LANGUAGE plpythonu;
SELECT build_ordered_named_pair(1,2);
SELECT build_ordered_named_pair(2,1); -- fail
CREATE FUNCTION build_ordered_named_pairs(i int, j int) RETURNS ordered_named_pair[] AS $$
return [{'i': i, 'j': j}, {'i': i, 'j': j+1}]
$$ LANGUAGE plpythonu;
SELECT build_ordered_named_pairs(1,2);
SELECT build_ordered_named_pairs(2,1); -- fail
--
-- Prepared statements
--