Commit 687f096e authored by Tom Lane's avatar Tom Lane

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 c12d570f), 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
parent 575cead9
...@@ -68,12 +68,30 @@ AS $$ ...@@ -68,12 +68,30 @@ AS $$
val = [{'a': 1, 'b': 'boo', 'c': None}, {'d': 2}] val = [{'a': 1, 'b': 'boo', 'c': None}, {'d': 2}]
return val return val
$$; $$;
SELECT test2arr(); SELECT test2arr();
test2arr test2arr
-------------------------------------------------------------- --------------------------------------------------------------
{"\"a\"=>\"1\", \"b\"=>\"boo\", \"c\"=>NULL","\"d\"=>\"2\""} {"\"a\"=>\"1\", \"b\"=>\"boo\", \"c\"=>NULL","\"d\"=>\"2\""}
(1 row) (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 -- test as part of prepare/execute
CREATE FUNCTION test3() RETURNS void CREATE FUNCTION test3() RETURNS void
LANGUAGE plpythonu LANGUAGE plpythonu
......
...@@ -60,7 +60,21 @@ val = [{'a': 1, 'b': 'boo', 'c': None}, {'d': 2}] ...@@ -60,7 +60,21 @@ val = [{'a': 1, 'b': 'boo', 'c': None}, {'d': 2}]
return val 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 -- test as part of prepare/execute
......
...@@ -377,6 +377,7 @@ lookup_type_cache(Oid type_id, int flags) ...@@ -377,6 +377,7 @@ lookup_type_cache(Oid type_id, int flags)
typentry->typstorage = typtup->typstorage; typentry->typstorage = typtup->typstorage;
typentry->typtype = typtup->typtype; typentry->typtype = typtup->typtype;
typentry->typrelid = typtup->typrelid; typentry->typrelid = typtup->typrelid;
typentry->typelem = typtup->typelem;
/* If it's a domain, immediately thread it into the domain cache list */ /* If it's a domain, immediately thread it into the domain cache list */
if (typentry->typtype == TYPTYPE_DOMAIN) if (typentry->typtype == TYPTYPE_DOMAIN)
...@@ -791,6 +792,12 @@ load_typcache_tupdesc(TypeCacheEntry *typentry) ...@@ -791,6 +792,12 @@ load_typcache_tupdesc(TypeCacheEntry *typentry)
Assert(typentry->tupDesc->tdrefcount > 0); Assert(typentry->tupDesc->tdrefcount > 0);
typentry->tupDesc->tdrefcount++; 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); relation_close(rel, AccessShareLock);
} }
......
...@@ -40,6 +40,7 @@ typedef struct TypeCacheEntry ...@@ -40,6 +40,7 @@ typedef struct TypeCacheEntry
char typstorage; char typstorage;
char typtype; char typtype;
Oid typrelid; Oid typrelid;
Oid typelem;
/* /*
* Information obtained from opfamily entries * Information obtained from opfamily entries
...@@ -75,9 +76,11 @@ typedef struct TypeCacheEntry ...@@ -75,9 +76,11 @@ typedef struct TypeCacheEntry
/* /*
* Tuple descriptor if it's a composite type (row type). NULL if not * 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 * 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; TupleDesc tupDesc;
int64 tupDescSeqNo;
/* /*
* Fields computed when TYPECACHE_RANGE_INFO is requested. Zeroes if not * Fields computed when TYPECACHE_RANGE_INFO is requested. Zeroes if not
......
...@@ -765,6 +765,76 @@ SELECT * FROM test_type_conversion_array_domain_check_violation(); ...@@ -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" ERROR: value for domain ordered_pair_domain violates check constraint "ordered_pair_domain_check"
CONTEXT: while creating return value CONTEXT: while creating return value
PL/Python function "test_type_conversion_array_domain_check_violation" 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 --- Composite types
--- ---
...@@ -820,6 +890,64 @@ SELECT test_composite_type_input(row(1, 2)); ...@@ -820,6 +890,64 @@ SELECT test_composite_type_input(row(1, 2));
3 3
(1 row) (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 -- Prepared statements
-- --
......
...@@ -765,6 +765,76 @@ SELECT * FROM test_type_conversion_array_domain_check_violation(); ...@@ -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" ERROR: value for domain ordered_pair_domain violates check constraint "ordered_pair_domain_check"
CONTEXT: while creating return value CONTEXT: while creating return value
PL/Python function "test_type_conversion_array_domain_check_violation" 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 --- Composite types
--- ---
...@@ -820,6 +890,64 @@ SELECT test_composite_type_input(row(1, 2)); ...@@ -820,6 +890,64 @@ SELECT test_composite_type_input(row(1, 2));
3 3
(1 row) (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 -- Prepared statements
-- --
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include <limits.h> #include <limits.h>
#include "access/xact.h" #include "access/xact.h"
#include "catalog/pg_type.h"
#include "mb/pg_wchar.h" #include "mb/pg_wchar.h"
#include "utils/memutils.h" #include "utils/memutils.h"
...@@ -106,6 +107,7 @@ static PyObject * ...@@ -106,6 +107,7 @@ static PyObject *
PLy_cursor_query(const char *query) PLy_cursor_query(const char *query)
{ {
PLyCursorObject *cursor; PLyCursorObject *cursor;
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
volatile MemoryContext oldcontext; volatile MemoryContext oldcontext;
volatile ResourceOwner oldowner; volatile ResourceOwner oldowner;
...@@ -116,7 +118,11 @@ PLy_cursor_query(const char *query) ...@@ -116,7 +118,11 @@ PLy_cursor_query(const char *query)
cursor->mcxt = AllocSetContextCreate(TopMemoryContext, cursor->mcxt = AllocSetContextCreate(TopMemoryContext,
"PL/Python cursor context", "PL/Python cursor context",
ALLOCSET_DEFAULT_SIZES); 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; oldcontext = CurrentMemoryContext;
oldowner = CurrentResourceOwner; oldowner = CurrentResourceOwner;
...@@ -125,7 +131,6 @@ PLy_cursor_query(const char *query) ...@@ -125,7 +131,6 @@ PLy_cursor_query(const char *query)
PG_TRY(); PG_TRY();
{ {
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
SPIPlanPtr plan; SPIPlanPtr plan;
Portal portal; Portal portal;
...@@ -166,6 +171,7 @@ PLy_cursor_plan(PyObject *ob, PyObject *args) ...@@ -166,6 +171,7 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
volatile int nargs; volatile int nargs;
int i; int i;
PLyPlanObject *plan; PLyPlanObject *plan;
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
volatile MemoryContext oldcontext; volatile MemoryContext oldcontext;
volatile ResourceOwner oldowner; volatile ResourceOwner oldowner;
...@@ -208,7 +214,11 @@ PLy_cursor_plan(PyObject *ob, PyObject *args) ...@@ -208,7 +214,11 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
cursor->mcxt = AllocSetContextCreate(TopMemoryContext, cursor->mcxt = AllocSetContextCreate(TopMemoryContext,
"PL/Python cursor context", "PL/Python cursor context",
ALLOCSET_DEFAULT_SIZES); 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; oldcontext = CurrentMemoryContext;
oldowner = CurrentResourceOwner; oldowner = CurrentResourceOwner;
...@@ -217,7 +227,6 @@ PLy_cursor_plan(PyObject *ob, PyObject *args) ...@@ -217,7 +227,6 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
PG_TRY(); PG_TRY();
{ {
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
Portal portal; Portal portal;
char *volatile nulls; char *volatile nulls;
volatile int j; volatile int j;
...@@ -229,39 +238,24 @@ PLy_cursor_plan(PyObject *ob, PyObject *args) ...@@ -229,39 +238,24 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
for (j = 0; j < nargs; j++) for (j = 0; j < nargs; j++)
{ {
PLyObToDatum *arg = &plan->args[j];
PyObject *elem; PyObject *elem;
elem = PySequence_GetItem(args, j); elem = PySequence_GetItem(args, j);
if (elem != Py_None) PG_TRY();
{ {
PG_TRY(); bool isnull;
{
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();
Py_DECREF(elem); plan->values[j] = PLy_output_convert(arg, elem, &isnull);
nulls[j] = ' '; nulls[j] = isnull ? 'n' : ' ';
} }
else PG_CATCH();
{ {
Py_DECREF(elem); Py_DECREF(elem);
plan->values[j] = PG_RE_THROW();
InputFunctionCall(&(plan->args[j].out.d.typfunc),
NULL,
plan->args[j].out.d.typioparam,
-1);
nulls[j] = 'n';
} }
PG_END_TRY();
Py_DECREF(elem);
} }
portal = SPI_cursor_open(NULL, plan->plan, plan->values, nulls, portal = SPI_cursor_open(NULL, plan->plan, plan->values, nulls,
...@@ -281,7 +275,7 @@ PLy_cursor_plan(PyObject *ob, PyObject *args) ...@@ -281,7 +275,7 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
/* cleanup plan->values array */ /* cleanup plan->values array */
for (k = 0; k < nargs; k++) for (k = 0; k < nargs; k++)
{ {
if (!plan->args[k].out.d.typbyval && if (!plan->args[k].typbyval &&
(plan->values[k] != PointerGetDatum(NULL))) (plan->values[k] != PointerGetDatum(NULL)))
{ {
pfree(DatumGetPointer(plan->values[k])); pfree(DatumGetPointer(plan->values[k]));
...@@ -298,7 +292,7 @@ PLy_cursor_plan(PyObject *ob, PyObject *args) ...@@ -298,7 +292,7 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
for (i = 0; i < nargs; i++) for (i = 0; i < nargs; i++)
{ {
if (!plan->args[i].out.d.typbyval && if (!plan->args[i].typbyval &&
(plan->values[i] != PointerGetDatum(NULL))) (plan->values[i] != PointerGetDatum(NULL)))
{ {
pfree(DatumGetPointer(plan->values[i])); pfree(DatumGetPointer(plan->values[i]));
...@@ -339,6 +333,7 @@ PLy_cursor_iternext(PyObject *self) ...@@ -339,6 +333,7 @@ PLy_cursor_iternext(PyObject *self)
{ {
PLyCursorObject *cursor; PLyCursorObject *cursor;
PyObject *ret; PyObject *ret;
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
volatile MemoryContext oldcontext; volatile MemoryContext oldcontext;
volatile ResourceOwner oldowner; volatile ResourceOwner oldowner;
Portal portal; Portal portal;
...@@ -374,11 +369,11 @@ PLy_cursor_iternext(PyObject *self) ...@@ -374,11 +369,11 @@ PLy_cursor_iternext(PyObject *self)
} }
else else
{ {
if (cursor->result.is_rowtype != 1) PLy_input_setup_tuple(&cursor->result, SPI_tuptable->tupdesc,
PLy_input_tuple_funcs(&cursor->result, SPI_tuptable->tupdesc); exec_ctx->curr_proc);
ret = PLyDict_FromTuple(&cursor->result, SPI_tuptable->vals[0], ret = PLy_input_from_tuple(&cursor->result, SPI_tuptable->vals[0],
SPI_tuptable->tupdesc); SPI_tuptable->tupdesc);
} }
SPI_freetuptable(SPI_tuptable); SPI_freetuptable(SPI_tuptable);
...@@ -401,6 +396,7 @@ PLy_cursor_fetch(PyObject *self, PyObject *args) ...@@ -401,6 +396,7 @@ PLy_cursor_fetch(PyObject *self, PyObject *args)
PLyCursorObject *cursor; PLyCursorObject *cursor;
int count; int count;
PLyResultObject *ret; PLyResultObject *ret;
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
volatile MemoryContext oldcontext; volatile MemoryContext oldcontext;
volatile ResourceOwner oldowner; volatile ResourceOwner oldowner;
Portal portal; Portal portal;
...@@ -437,9 +433,6 @@ PLy_cursor_fetch(PyObject *self, PyObject *args) ...@@ -437,9 +433,6 @@ PLy_cursor_fetch(PyObject *self, PyObject *args)
{ {
SPI_cursor_fetch(portal, true, count); 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); Py_DECREF(ret->status);
ret->status = PyInt_FromLong(SPI_OK_FETCH); ret->status = PyInt_FromLong(SPI_OK_FETCH);
...@@ -465,11 +458,14 @@ PLy_cursor_fetch(PyObject *self, PyObject *args) ...@@ -465,11 +458,14 @@ PLy_cursor_fetch(PyObject *self, PyObject *args)
Py_DECREF(ret->rows); Py_DECREF(ret->rows);
ret->rows = PyList_New(SPI_processed); 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++) for (i = 0; i < SPI_processed; i++)
{ {
PyObject *row = PLyDict_FromTuple(&cursor->result, PyObject *row = PLy_input_from_tuple(&cursor->result,
SPI_tuptable->vals[i], SPI_tuptable->vals[i],
SPI_tuptable->tupdesc); SPI_tuptable->tupdesc);
PyList_SetItem(ret->rows, i, row); PyList_SetItem(ret->rows, i, row);
} }
......
...@@ -12,7 +12,7 @@ typedef struct PLyCursorObject ...@@ -12,7 +12,7 @@ typedef struct PLyCursorObject
{ {
PyObject_HEAD PyObject_HEAD
char *portalname; char *portalname;
PLyTypeInfo result; PLyDatumToOb result;
bool closed; bool closed;
MemoryContext mcxt; MemoryContext mcxt;
} PLyCursorObject; } PLyCursorObject;
......
...@@ -202,7 +202,7 @@ PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc) ...@@ -202,7 +202,7 @@ PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc)
* return value as a special "void datum" rather than NULL (as is the * return value as a special "void datum" rather than NULL (as is the
* case for non-void-returning functions). * case for non-void-returning functions).
*/ */
if (proc->result.out.d.typoid == VOIDOID) if (proc->result.typoid == VOIDOID)
{ {
if (plrv != Py_None) if (plrv != Py_None)
ereport(ERROR, ereport(ERROR,
...@@ -212,48 +212,22 @@ PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc) ...@@ -212,48 +212,22 @@ PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc)
fcinfo->isnull = false; fcinfo->isnull = false;
rv = (Datum) 0; 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 * In a SETOF function, the iteration-ending null isn't a real
* value; don't pass it through the input function, which might * value; don't pass it through the input function, which might
* complain. * complain.
*/ */
if (srfstate && srfstate->iter == NULL) fcinfo->isnull = true;
rv = (Datum) 0; 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);
} }
else else
{ {
fcinfo->isnull = false; /* Normal conversion of result */
rv = (proc->result.out.d.func) (&proc->result.out.d, -1, plrv, false); rv = PLy_output_convert(&proc->result, plrv,
&fcinfo->isnull);
} }
} }
PG_CATCH(); PG_CATCH();
...@@ -328,20 +302,32 @@ PLy_exec_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc) ...@@ -328,20 +302,32 @@ PLy_exec_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc)
PyObject *volatile plargs = NULL; PyObject *volatile plargs = NULL;
PyObject *volatile plrv = NULL; PyObject *volatile plrv = NULL;
TriggerData *tdata; TriggerData *tdata;
TupleDesc rel_descr;
Assert(CALLED_AS_TRIGGER(fcinfo)); Assert(CALLED_AS_TRIGGER(fcinfo));
tdata = (TriggerData *) fcinfo->context;
/* /*
* Input/output conversion for trigger tuples. Use the result TypeInfo * Input/output conversion for trigger tuples. We use the result and
* variable to store the tuple conversion info. We do this over again on * result_in fields to store the tuple conversion info. We do this over
* each call to cover the possibility that the relation's tupdesc changed * again on each call to cover the possibility that the relation's tupdesc
* since the trigger was last called. PLy_input_tuple_funcs and * changed since the trigger was last called. The PLy_xxx_setup_func
* PLy_output_tuple_funcs are responsible for not doing repetitive work. * calls should only happen once, but PLy_input_setup_tuple and
* PLy_output_setup_tuple are responsible for not doing repetitive work.
*/ */
tdata = (TriggerData *) fcinfo->context; rel_descr = RelationGetDescr(tdata->tg_relation);
if (proc->result.typoid != rel_descr->tdtypeid)
PLy_input_tuple_funcs(&(proc->result), tdata->tg_relation->rd_att); PLy_output_setup_func(&proc->result, proc->mcxt,
PLy_output_tuple_funcs(&(proc->result), tdata->tg_relation->rd_att); 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(); PG_TRY();
{ {
...@@ -436,46 +422,12 @@ PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc) ...@@ -436,46 +422,12 @@ PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc)
args = PyList_New(proc->nargs); args = PyList_New(proc->nargs);
for (i = 0; i < proc->nargs; i++) for (i = 0; i < proc->nargs; i++)
{ {
if (proc->args[i].is_rowtype > 0) PLyDatumToOb *arginfo = &proc->args[i];
{
if (fcinfo->argnull[i]) if (fcinfo->argnull[i])
arg = NULL; arg = NULL;
else
{
HeapTupleHeader td;
Oid tupType;
int32 tupTypmod;
TupleDesc tupdesc;
HeapTupleData tmptup;
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);
}
}
else else
{ arg = PLy_input_convert(arginfo, fcinfo->arg[i]);
if (fcinfo->argnull[i])
arg = NULL;
else
{
arg = (proc->args[i].in.d.func) (&(proc->args[i].in.d),
fcinfo->arg[i]);
}
}
if (arg == NULL) if (arg == NULL)
{ {
...@@ -493,7 +445,7 @@ PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc) ...@@ -493,7 +445,7 @@ PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc)
} }
/* Set up output conversion for functions returning RECORD */ /* Set up output conversion for functions returning RECORD */
if (proc->result.out.d.typoid == RECORDOID) if (proc->result.typoid == RECORDOID)
{ {
TupleDesc desc; TupleDesc desc;
...@@ -504,7 +456,7 @@ PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc) ...@@ -504,7 +456,7 @@ PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc)
"that cannot accept type record"))); "that cannot accept type record")));
/* cache the output conversion functions */ /* cache the output conversion functions */
PLy_output_record_funcs(&(proc->result), desc); PLy_output_setup_record(&proc->result, desc, proc);
} }
} }
PG_CATCH(); PG_CATCH();
...@@ -723,6 +675,7 @@ static PyObject * ...@@ -723,6 +675,7 @@ static PyObject *
PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *rv) PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *rv)
{ {
TriggerData *tdata = (TriggerData *) fcinfo->context; TriggerData *tdata = (TriggerData *) fcinfo->context;
TupleDesc rel_descr = RelationGetDescr(tdata->tg_relation);
PyObject *pltname, PyObject *pltname,
*pltevent, *pltevent,
*pltwhen, *pltwhen,
...@@ -790,8 +743,9 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r ...@@ -790,8 +743,9 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r
pltevent = PyString_FromString("INSERT"); pltevent = PyString_FromString("INSERT");
PyDict_SetItemString(pltdata, "old", Py_None); PyDict_SetItemString(pltdata, "old", Py_None);
pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple, pytnew = PLy_input_from_tuple(&proc->result_in,
tdata->tg_relation->rd_att); tdata->tg_trigtuple,
rel_descr);
PyDict_SetItemString(pltdata, "new", pytnew); PyDict_SetItemString(pltdata, "new", pytnew);
Py_DECREF(pytnew); Py_DECREF(pytnew);
*rv = tdata->tg_trigtuple; *rv = tdata->tg_trigtuple;
...@@ -801,8 +755,9 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r ...@@ -801,8 +755,9 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r
pltevent = PyString_FromString("DELETE"); pltevent = PyString_FromString("DELETE");
PyDict_SetItemString(pltdata, "new", Py_None); PyDict_SetItemString(pltdata, "new", Py_None);
pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple, pytold = PLy_input_from_tuple(&proc->result_in,
tdata->tg_relation->rd_att); tdata->tg_trigtuple,
rel_descr);
PyDict_SetItemString(pltdata, "old", pytold); PyDict_SetItemString(pltdata, "old", pytold);
Py_DECREF(pytold); Py_DECREF(pytold);
*rv = tdata->tg_trigtuple; *rv = tdata->tg_trigtuple;
...@@ -811,12 +766,14 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r ...@@ -811,12 +766,14 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r
{ {
pltevent = PyString_FromString("UPDATE"); pltevent = PyString_FromString("UPDATE");
pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_newtuple, pytnew = PLy_input_from_tuple(&proc->result_in,
tdata->tg_relation->rd_att); tdata->tg_newtuple,
rel_descr);
PyDict_SetItemString(pltdata, "new", pytnew); PyDict_SetItemString(pltdata, "new", pytnew);
Py_DECREF(pytnew); Py_DECREF(pytnew);
pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple, pytold = PLy_input_from_tuple(&proc->result_in,
tdata->tg_relation->rd_att); tdata->tg_trigtuple,
rel_descr);
PyDict_SetItemString(pltdata, "old", pytold); PyDict_SetItemString(pltdata, "old", pytold);
Py_DECREF(pytold); Py_DECREF(pytold);
*rv = tdata->tg_newtuple; *rv = tdata->tg_newtuple;
...@@ -897,6 +854,9 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r ...@@ -897,6 +854,9 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r
return pltdata; return pltdata;
} }
/*
* Apply changes requested by a MODIFY return from a trigger function.
*/
static HeapTuple static HeapTuple
PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata, PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata,
HeapTuple otup) HeapTuple otup)
...@@ -938,7 +898,7 @@ PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata, ...@@ -938,7 +898,7 @@ PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata,
plkeys = PyDict_Keys(plntup); plkeys = PyDict_Keys(plntup);
nkeys = PyList_Size(plkeys); nkeys = PyList_Size(plkeys);
tupdesc = tdata->tg_relation->rd_att; tupdesc = RelationGetDescr(tdata->tg_relation);
modvalues = (Datum *) palloc0(tupdesc->natts * sizeof(Datum)); modvalues = (Datum *) palloc0(tupdesc->natts * sizeof(Datum));
modnulls = (bool *) palloc0(tupdesc->natts * sizeof(bool)); modnulls = (bool *) palloc0(tupdesc->natts * sizeof(bool));
...@@ -950,7 +910,6 @@ PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata, ...@@ -950,7 +910,6 @@ PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata,
char *plattstr; char *plattstr;
int attn; int attn;
PLyObToDatum *att; PLyObToDatum *att;
Form_pg_attribute attr;
platt = PyList_GetItem(plkeys, i); platt = PyList_GetItem(plkeys, i);
if (PyString_Check(platt)) if (PyString_Check(platt))
...@@ -975,7 +934,6 @@ PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata, ...@@ -975,7 +934,6 @@ PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot set system attribute \"%s\"", errmsg("cannot set system attribute \"%s\"",
plattstr))); plattstr)));
att = &proc->result.out.r.atts[attn - 1];
plval = PyDict_GetItem(plntup, platt); plval = PyDict_GetItem(plntup, platt);
if (plval == NULL) if (plval == NULL)
...@@ -983,25 +941,12 @@ PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata, ...@@ -983,25 +941,12 @@ PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata,
Py_INCREF(plval); Py_INCREF(plval);
attr = TupleDescAttr(tupdesc, attn - 1); /* We assume proc->result is set up to convert tuples properly */
if (plval != Py_None) att = &proc->result.u.tuple.atts[attn - 1];
{
modvalues[attn - 1] = modvalues[attn - 1] = PLy_output_convert(att,
(att->func) (att, plval,
attr->atttypmod, &modnulls[attn - 1]);
plval,
false);
modnulls[attn - 1] = false;
}
else
{
modvalues[attn - 1] =
InputFunctionCall(&att->typfunc,
NULL,
att->typioparam,
attr->atttypmod);
modnulls[attn - 1] = true;
}
modrepls[attn - 1] = true; modrepls[attn - 1] = true;
Py_DECREF(plval); Py_DECREF(plval);
......
...@@ -318,7 +318,12 @@ plpython_inline_handler(PG_FUNCTION_ARGS) ...@@ -318,7 +318,12 @@ plpython_inline_handler(PG_FUNCTION_ARGS)
ALLOCSET_DEFAULT_SIZES); ALLOCSET_DEFAULT_SIZES);
proc.pyname = MemoryContextStrdup(proc.mcxt, "__plpython_inline_block"); proc.pyname = MemoryContextStrdup(proc.mcxt, "__plpython_inline_block");
proc.langid = codeblock->langOid; 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 * Push execution context onto stack. It is important that this get
......
...@@ -16,7 +16,7 @@ typedef struct PLyPlanObject ...@@ -16,7 +16,7 @@ typedef struct PLyPlanObject
int nargs; int nargs;
Oid *types; Oid *types;
Datum *values; Datum *values;
PLyTypeInfo *args; PLyObToDatum *args;
MemoryContext mcxt; MemoryContext mcxt;
} PLyPlanObject; } PLyPlanObject;
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "utils/builtins.h" #include "utils/builtins.h"
#include "utils/hsearch.h" #include "utils/hsearch.h"
#include "utils/inval.h" #include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h" #include "utils/memutils.h"
#include "utils/syscache.h" #include "utils/syscache.h"
...@@ -29,7 +30,6 @@ ...@@ -29,7 +30,6 @@
static HTAB *PLy_procedure_cache = NULL; static HTAB *PLy_procedure_cache = NULL;
static PLyProcedure *PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger); 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 bool PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup);
static char *PLy_procedure_munge_source(const char *name, const char *src); 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) ...@@ -165,6 +165,7 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
*ptr = '_'; *ptr = '_';
} }
/* Create long-lived context that all procedure info will live in */
cxt = AllocSetContextCreate(TopMemoryContext, cxt = AllocSetContextCreate(TopMemoryContext,
procName, procName,
ALLOCSET_DEFAULT_SIZES); ALLOCSET_DEFAULT_SIZES);
...@@ -188,11 +189,9 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger) ...@@ -188,11 +189,9 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
proc->fn_tid = procTup->t_self; proc->fn_tid = procTup->t_self;
proc->fn_readonly = (procStruct->provolatile != PROVOLATILE_VOLATILE); proc->fn_readonly = (procStruct->provolatile != PROVOLATILE_VOLATILE);
proc->is_setof = procStruct->proretset; proc->is_setof = procStruct->proretset;
PLy_typeinfo_init(&proc->result, proc->mcxt);
proc->src = NULL; proc->src = NULL;
proc->argnames = NULL; proc->argnames = NULL;
for (i = 0; i < FUNC_MAX_ARGS; i++) proc->args = NULL;
PLy_typeinfo_init(&proc->args[i], proc->mcxt);
proc->nargs = 0; proc->nargs = 0;
proc->langid = procStruct->prolang; proc->langid = procStruct->prolang;
protrftypes_datum = SysCacheGetAttr(PROCOID, procTup, protrftypes_datum = SysCacheGetAttr(PROCOID, procTup,
...@@ -211,50 +210,48 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger) ...@@ -211,50 +210,48 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
*/ */
if (!is_trigger) if (!is_trigger)
{ {
Oid rettype = procStruct->prorettype;
HeapTuple rvTypeTup; HeapTuple rvTypeTup;
Form_pg_type rvTypeStruct; Form_pg_type rvTypeStruct;
rvTypeTup = SearchSysCache1(TYPEOID, rvTypeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(rettype));
ObjectIdGetDatum(procStruct->prorettype));
if (!HeapTupleIsValid(rvTypeTup)) if (!HeapTupleIsValid(rvTypeTup))
elog(ERROR, "cache lookup failed for type %u", elog(ERROR, "cache lookup failed for type %u", rettype);
procStruct->prorettype);
rvTypeStruct = (Form_pg_type) GETSTRUCT(rvTypeTup); rvTypeStruct = (Form_pg_type) GETSTRUCT(rvTypeTup);
/* Disallow pseudotype result, except for void or record */ /* Disallow pseudotype result, except for void or record */
if (rvTypeStruct->typtype == TYPTYPE_PSEUDO) if (rvTypeStruct->typtype == TYPTYPE_PSEUDO)
{ {
if (procStruct->prorettype == TRIGGEROID) if (rettype == VOIDOID ||
rettype == RECORDOID)
/* okay */ ;
else if (rettype == TRIGGEROID || rettype == EVTTRIGGEROID)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("trigger functions can only be called as triggers"))); errmsg("trigger functions can only be called as triggers")));
else if (procStruct->prorettype != VOIDOID && else
procStruct->prorettype != RECORDOID)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("PL/Python functions cannot return type %s", errmsg("PL/Python functions cannot return type %s",
format_type_be(procStruct->prorettype)))); format_type_be(rettype))));
} }
if (rvTypeStruct->typtype == TYPTYPE_COMPOSITE || /* set up output function for procedure result */
procStruct->prorettype == RECORDOID) PLy_output_setup_func(&proc->result, proc->mcxt,
{ rettype, -1, proc);
/*
* 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);
}
ReleaseSysCache(rvTypeTup); 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 * Now get information required for input conversion of the
...@@ -287,7 +284,10 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger) ...@@ -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->argnames = (char **) palloc0(sizeof(char *) * proc->nargs);
proc->args = (PLyDatumToOb *) palloc0(sizeof(PLyDatumToOb) * proc->nargs);
for (i = pos = 0; i < total; i++) for (i = pos = 0; i < total; i++)
{ {
HeapTuple argTypeTup; HeapTuple argTypeTup;
...@@ -306,28 +306,17 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger) ...@@ -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]); elog(ERROR, "cache lookup failed for type %u", types[i]);
argTypeStruct = (Form_pg_type) GETSTRUCT(argTypeTup); argTypeStruct = (Form_pg_type) GETSTRUCT(argTypeTup);
/* check argument type is OK, set up I/O function info */ /* disallow pseudotype arguments */
switch (argTypeStruct->typtype) if (argTypeStruct->typtype == TYPTYPE_PSEUDO)
{ ereport(ERROR,
case TYPTYPE_PSEUDO: (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
/* Disallow pseudotype argument */ errmsg("PL/Python functions cannot accept type %s",
ereport(ERROR, format_type_be(types[i]))));
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("PL/Python functions cannot accept type %s", /* set up I/O function info */
format_type_be(types[i])))); PLy_input_setup_func(&proc->args[pos], proc->mcxt,
break; types[i], -1, /* typmod not known */
case TYPTYPE_COMPOSITE: proc);
/* 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;
}
/* get argument name */ /* get argument name */
proc->argnames[pos] = names ? pstrdup(names[i]) : NULL; proc->argnames[pos] = names ? pstrdup(names[i]) : NULL;
...@@ -424,54 +413,12 @@ PLy_procedure_delete(PLyProcedure *proc) ...@@ -424,54 +413,12 @@ PLy_procedure_delete(PLyProcedure *proc)
MemoryContextDelete(proc->mcxt); 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 * Decide whether a cached PLyProcedure struct is still valid
*/ */
static bool static bool
PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup) PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup)
{ {
int i;
bool valid;
if (proc == NULL) if (proc == NULL)
return false; return false;
...@@ -480,22 +427,7 @@ PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup) ...@@ -480,22 +427,7 @@ PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup)
ItemPointerEquals(&proc->fn_tid, &procTup->t_self))) ItemPointerEquals(&proc->fn_tid, &procTup->t_self)))
return false; return false;
/* Else check the input argument datatypes */ return true;
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;
} }
static char * static char *
......
...@@ -31,12 +31,12 @@ typedef struct PLyProcedure ...@@ -31,12 +31,12 @@ typedef struct PLyProcedure
ItemPointerData fn_tid; ItemPointerData fn_tid;
bool fn_readonly; bool fn_readonly;
bool is_setof; /* true, if procedure returns result set */ bool is_setof; /* true, if procedure returns result set */
PLyTypeInfo result; /* also used to store info for trigger tuple PLyObToDatum result; /* Function result output conversion info */
* type */ PLyDatumToOb result_in; /* For converting input tuples in a trigger */
char *src; /* textual procedure code, after mangling */ char *src; /* textual procedure code, after mangling */
char **argnames; /* Argument names */ char **argnames; /* Argument names */
PLyTypeInfo args[FUNC_MAX_ARGS]; PLyDatumToOb *args; /* Argument input conversion info */
int nargs; int nargs; /* Number of elements in above arrays */
Oid langid; /* OID of plpython pg_language entry */ Oid langid; /* OID of plpython pg_language entry */
List *trftypes; /* OID list of transform types */ List *trftypes; /* OID list of transform types */
PyObject *code; /* compiled procedure code */ PyObject *code; /* compiled procedure code */
......
...@@ -46,6 +46,7 @@ PLy_spi_prepare(PyObject *self, PyObject *args) ...@@ -46,6 +46,7 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
PyObject *list = NULL; PyObject *list = NULL;
PyObject *volatile optr = NULL; PyObject *volatile optr = NULL;
char *query; char *query;
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
volatile MemoryContext oldcontext; volatile MemoryContext oldcontext;
volatile ResourceOwner oldowner; volatile ResourceOwner oldowner;
volatile int nargs; volatile int nargs;
...@@ -71,9 +72,9 @@ PLy_spi_prepare(PyObject *self, PyObject *args) ...@@ -71,9 +72,9 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
nargs = list ? PySequence_Length(list) : 0; nargs = list ? PySequence_Length(list) : 0;
plan->nargs = nargs; plan->nargs = nargs;
plan->types = nargs ? palloc(sizeof(Oid) * nargs) : NULL; plan->types = nargs ? palloc0(sizeof(Oid) * nargs) : NULL;
plan->values = nargs ? palloc(sizeof(Datum) * nargs) : NULL; plan->values = nargs ? palloc0(sizeof(Datum) * nargs) : NULL;
plan->args = nargs ? palloc(sizeof(PLyTypeInfo) * nargs) : NULL; plan->args = nargs ? palloc0(sizeof(PLyObToDatum) * nargs) : NULL;
MemoryContextSwitchTo(oldcontext); MemoryContextSwitchTo(oldcontext);
...@@ -85,22 +86,10 @@ PLy_spi_prepare(PyObject *self, PyObject *args) ...@@ -85,22 +86,10 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
PG_TRY(); PG_TRY();
{ {
int i; 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++) for (i = 0; i < nargs; i++)
{ {
char *sptr; char *sptr;
HeapTuple typeTup;
Oid typeId; Oid typeId;
int32 typmod; int32 typmod;
...@@ -124,11 +113,6 @@ PLy_spi_prepare(PyObject *self, PyObject *args) ...@@ -124,11 +113,6 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
parseTypeString(sptr, &typeId, &typmod, false); 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); Py_DECREF(optr);
/* /*
...@@ -138,8 +122,9 @@ PLy_spi_prepare(PyObject *self, PyObject *args) ...@@ -138,8 +122,9 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
optr = NULL; optr = NULL;
plan->types[i] = typeId; plan->types[i] = typeId;
PLy_output_datum_func(&plan->args[i], typeTup, exec_ctx->curr_proc->langid, exec_ctx->curr_proc->trftypes); PLy_output_setup_func(&plan->args[i], plan->mcxt,
ReleaseSysCache(typeTup); typeId, typmod,
exec_ctx->curr_proc);
} }
pg_verifymbstr(query, strlen(query), false); pg_verifymbstr(query, strlen(query), false);
...@@ -253,39 +238,24 @@ PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit) ...@@ -253,39 +238,24 @@ PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
for (j = 0; j < nargs; j++) for (j = 0; j < nargs; j++)
{ {
PLyObToDatum *arg = &plan->args[j];
PyObject *elem; PyObject *elem;
elem = PySequence_GetItem(list, j); elem = PySequence_GetItem(list, j);
if (elem != Py_None) PG_TRY();
{ {
PG_TRY(); bool isnull;
{
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();
Py_DECREF(elem); plan->values[j] = PLy_output_convert(arg, elem, &isnull);
nulls[j] = ' '; nulls[j] = isnull ? 'n' : ' ';
} }
else PG_CATCH();
{ {
Py_DECREF(elem); Py_DECREF(elem);
plan->values[j] = PG_RE_THROW();
InputFunctionCall(&(plan->args[j].out.d.typfunc),
NULL,
plan->args[j].out.d.typioparam,
-1);
nulls[j] = 'n';
} }
PG_END_TRY();
Py_DECREF(elem);
} }
rv = SPI_execute_plan(plan->plan, plan->values, nulls, rv = SPI_execute_plan(plan->plan, plan->values, nulls,
...@@ -306,7 +276,7 @@ PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit) ...@@ -306,7 +276,7 @@ PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
*/ */
for (k = 0; k < nargs; k++) for (k = 0; k < nargs; k++)
{ {
if (!plan->args[k].out.d.typbyval && if (!plan->args[k].typbyval &&
(plan->values[k] != PointerGetDatum(NULL))) (plan->values[k] != PointerGetDatum(NULL)))
{ {
pfree(DatumGetPointer(plan->values[k])); pfree(DatumGetPointer(plan->values[k]));
...@@ -321,7 +291,7 @@ PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit) ...@@ -321,7 +291,7 @@ PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
for (i = 0; i < nargs; i++) for (i = 0; i < nargs; i++)
{ {
if (!plan->args[i].out.d.typbyval && if (!plan->args[i].typbyval &&
(plan->values[i] != PointerGetDatum(NULL))) (plan->values[i] != PointerGetDatum(NULL)))
{ {
pfree(DatumGetPointer(plan->values[i])); pfree(DatumGetPointer(plan->values[i]));
...@@ -386,6 +356,7 @@ static PyObject * ...@@ -386,6 +356,7 @@ static PyObject *
PLy_spi_execute_fetch_result(SPITupleTable *tuptable, uint64 rows, int status) PLy_spi_execute_fetch_result(SPITupleTable *tuptable, uint64 rows, int status)
{ {
PLyResultObject *result; PLyResultObject *result;
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
volatile MemoryContext oldcontext; volatile MemoryContext oldcontext;
result = (PLyResultObject *) PLy_result_new(); result = (PLyResultObject *) PLy_result_new();
...@@ -401,7 +372,7 @@ PLy_spi_execute_fetch_result(SPITupleTable *tuptable, uint64 rows, int status) ...@@ -401,7 +372,7 @@ PLy_spi_execute_fetch_result(SPITupleTable *tuptable, uint64 rows, int status)
} }
else if (status > 0 && tuptable != NULL) else if (status > 0 && tuptable != NULL)
{ {
PLyTypeInfo args; PLyDatumToOb ininfo;
MemoryContext cxt; MemoryContext cxt;
Py_DECREF(result->nrows); Py_DECREF(result->nrows);
...@@ -412,7 +383,10 @@ PLy_spi_execute_fetch_result(SPITupleTable *tuptable, uint64 rows, int status) ...@@ -412,7 +383,10 @@ PLy_spi_execute_fetch_result(SPITupleTable *tuptable, uint64 rows, int status)
cxt = AllocSetContextCreate(CurrentMemoryContext, cxt = AllocSetContextCreate(CurrentMemoryContext,
"PL/Python temp context", "PL/Python temp context",
ALLOCSET_DEFAULT_SIZES); 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; oldcontext = CurrentMemoryContext;
PG_TRY(); PG_TRY();
...@@ -436,12 +410,14 @@ PLy_spi_execute_fetch_result(SPITupleTable *tuptable, uint64 rows, int status) ...@@ -436,12 +410,14 @@ PLy_spi_execute_fetch_result(SPITupleTable *tuptable, uint64 rows, int status)
Py_DECREF(result->rows); Py_DECREF(result->rows);
result->rows = PyList_New(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++) for (i = 0; i < rows; i++)
{ {
PyObject *row = PLyDict_FromTuple(&args, PyObject *row = PLy_input_from_tuple(&ininfo,
tuptable->vals[i], tuptable->vals[i],
tuptable->tupdesc); tuptable->tupdesc);
PyList_SetItem(result->rows, i, row); PyList_SetItem(result->rows, i, row);
} }
......
...@@ -7,19 +7,15 @@ ...@@ -7,19 +7,15 @@
#include "postgres.h" #include "postgres.h"
#include "access/htup_details.h" #include "access/htup_details.h"
#include "access/transam.h"
#include "catalog/pg_type.h" #include "catalog/pg_type.h"
#include "funcapi.h" #include "funcapi.h"
#include "mb/pg_wchar.h" #include "mb/pg_wchar.h"
#include "parser/parse_type.h" #include "miscadmin.h"
#include "utils/array.h" #include "utils/array.h"
#include "utils/builtins.h" #include "utils/builtins.h"
#include "utils/fmgroids.h" #include "utils/fmgroids.h"
#include "utils/lsyscache.h" #include "utils/lsyscache.h"
#include "utils/memutils.h" #include "utils/memutils.h"
#include "utils/numeric.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
#include "plpython.h" #include "plpython.h"
...@@ -29,10 +25,6 @@ ...@@ -29,10 +25,6 @@
#include "plpy_main.h" #include "plpy_main.h"
/* I/O function caching */
static void PLy_input_datum_func2(PLyDatumToOb *arg, MemoryContext arg_mcxt, Oid typeOid, HeapTuple typeTup, Oid langid, List *trftypes);
static void PLy_output_datum_func2(PLyObToDatum *arg, MemoryContext arg_mcxt, HeapTuple typeTup, Oid langid, List *trftypes);
/* conversion from Datums to Python objects */ /* conversion from Datums to Python objects */
static PyObject *PLyBool_FromBool(PLyDatumToOb *arg, Datum d); static PyObject *PLyBool_FromBool(PLyDatumToOb *arg, Datum d);
static PyObject *PLyFloat_FromFloat4(PLyDatumToOb *arg, Datum d); static PyObject *PLyFloat_FromFloat4(PLyDatumToOb *arg, Datum d);
...@@ -43,361 +35,365 @@ static PyObject *PLyInt_FromInt32(PLyDatumToOb *arg, Datum d); ...@@ -43,361 +35,365 @@ static PyObject *PLyInt_FromInt32(PLyDatumToOb *arg, Datum d);
static PyObject *PLyLong_FromInt64(PLyDatumToOb *arg, Datum d); static PyObject *PLyLong_FromInt64(PLyDatumToOb *arg, Datum d);
static PyObject *PLyLong_FromOid(PLyDatumToOb *arg, Datum d); static PyObject *PLyLong_FromOid(PLyDatumToOb *arg, Datum d);
static PyObject *PLyBytes_FromBytea(PLyDatumToOb *arg, Datum d); static PyObject *PLyBytes_FromBytea(PLyDatumToOb *arg, Datum d);
static PyObject *PLyString_FromDatum(PLyDatumToOb *arg, Datum d); static PyObject *PLyString_FromScalar(PLyDatumToOb *arg, Datum d);
static PyObject *PLyObject_FromTransform(PLyDatumToOb *arg, Datum d); static PyObject *PLyObject_FromTransform(PLyDatumToOb *arg, Datum d);
static PyObject *PLyList_FromArray(PLyDatumToOb *arg, Datum d); static PyObject *PLyList_FromArray(PLyDatumToOb *arg, Datum d);
static PyObject *PLyList_FromArray_recurse(PLyDatumToOb *elm, int *dims, int ndim, int dim, static PyObject *PLyList_FromArray_recurse(PLyDatumToOb *elm, int *dims, int ndim, int dim,
char **dataptr_p, bits8 **bitmap_p, int *bitmask_p); char **dataptr_p, bits8 **bitmap_p, int *bitmask_p);
static PyObject *PLyDict_FromComposite(PLyDatumToOb *arg, Datum d);
static PyObject *PLyDict_FromTuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc);
/* conversion from Python objects to Datums */ /* conversion from Python objects to Datums */
static Datum PLyObject_ToBool(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray); static Datum PLyObject_ToBool(PLyObToDatum *arg, PyObject *plrv,
static Datum PLyObject_ToBytea(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray); bool *isnull, bool inarray);
static Datum PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray); static Datum PLyObject_ToBytea(PLyObToDatum *arg, PyObject *plrv,
static Datum PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray); bool *isnull, bool inarray);
static Datum PLyObject_ToTransform(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray); static Datum PLyObject_ToComposite(PLyObToDatum *arg, PyObject *plrv,
static Datum PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray); bool *isnull, bool inarray);
static Datum PLyObject_ToScalar(PLyObToDatum *arg, PyObject *plrv,
bool *isnull, bool inarray);
static Datum PLyObject_ToDomain(PLyObToDatum *arg, PyObject *plrv,
bool *isnull, bool inarray);
static Datum PLyObject_ToTransform(PLyObToDatum *arg, PyObject *plrv,
bool *isnull, bool inarray);
static Datum PLySequence_ToArray(PLyObToDatum *arg, PyObject *plrv,
bool *isnull, bool inarray);
static void PLySequence_ToArray_recurse(PLyObToDatum *elm, PyObject *list, static void PLySequence_ToArray_recurse(PLyObToDatum *elm, PyObject *list,
int *dims, int ndim, int dim, int *dims, int ndim, int dim,
Datum *elems, bool *nulls, int *currelem); Datum *elems, bool *nulls, int *currelem);
/* conversion from Python objects to composite Datums (used by triggers and SRFs) */ /* conversion from Python objects to composite Datums */
static Datum PLyString_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *string, bool inarray); static Datum PLyString_ToComposite(PLyObToDatum *arg, PyObject *string, bool inarray);
static Datum PLyMapping_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping); static Datum PLyMapping_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *mapping);
static Datum PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence); static Datum PLySequence_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *sequence);
static Datum PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object, bool inarray); static Datum PLyGenericObject_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *object, bool inarray);
void
PLy_typeinfo_init(PLyTypeInfo *arg, MemoryContext mcxt)
{
arg->is_rowtype = -1;
arg->in.r.natts = arg->out.r.natts = 0;
arg->in.r.atts = NULL;
arg->out.r.atts = NULL;
arg->typ_relid = InvalidOid;
arg->typrel_xmin = InvalidTransactionId;
ItemPointerSetInvalid(&arg->typrel_tid);
arg->mcxt = mcxt;
}
/* /*
* Conversion functions. Remember output from Python is input to * Conversion functions. Remember output from Python is input to
* PostgreSQL, and vice versa. * PostgreSQL, and vice versa.
*/ */
void
PLy_input_datum_func(PLyTypeInfo *arg, Oid typeOid, HeapTuple typeTup, Oid langid, List *trftypes) /*
* Perform input conversion, given correctly-set-up state information.
*
* This is the outer-level entry point for any input conversion. Internally,
* the conversion functions recurse directly to each other.
*/
PyObject *
PLy_input_convert(PLyDatumToOb *arg, Datum val)
{ {
if (arg->is_rowtype > 0) PyObject *result;
elog(ERROR, "PLyTypeInfo struct is initialized for Tuple"); PLyExecutionContext *exec_ctx = PLy_current_execution_context();
arg->is_rowtype = 0; MemoryContext scratch_context = PLy_get_scratch_context(exec_ctx);
PLy_input_datum_func2(&(arg->in.d), arg->mcxt, typeOid, typeTup, langid, trftypes); MemoryContext oldcontext;
/*
* Do the work in the scratch context to avoid leaking memory from the
* datatype output function calls. (The individual PLyDatumToObFunc
* functions can't reset the scratch context, because they recurse and an
* inner one might clobber data an outer one still needs. So we do it
* once at the outermost recursion level.)
*
* We reset the scratch context before, not after, each conversion cycle.
* This way we aren't on the hook to release a Python refcount on the
* result object in case MemoryContextReset throws an error.
*/
MemoryContextReset(scratch_context);
oldcontext = MemoryContextSwitchTo(scratch_context);
result = arg->func(arg, val);
MemoryContextSwitchTo(oldcontext);
return result;
} }
void /*
PLy_output_datum_func(PLyTypeInfo *arg, HeapTuple typeTup, Oid langid, List *trftypes) * Perform output conversion, given correctly-set-up state information.
*
* This is the outer-level entry point for any output conversion. Internally,
* the conversion functions recurse directly to each other.
*
* The result, as well as any cruft generated along the way, are in the
* current memory context. Caller is responsible for cleanup.
*/
Datum
PLy_output_convert(PLyObToDatum *arg, PyObject *val, bool *isnull)
{ {
if (arg->is_rowtype > 0) /* at outer level, we are not considering an array element */
elog(ERROR, "PLyTypeInfo struct is initialized for a Tuple"); return arg->func(arg, val, isnull, false);
arg->is_rowtype = 0;
PLy_output_datum_func2(&(arg->out.d), arg->mcxt, typeTup, langid, trftypes);
} }
void /*
PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc) * Transform a tuple into a Python dict object.
*
* Note: the tupdesc must match the one used to set up *arg. We could
* insist that this function lookup the tupdesc from what is in *arg,
* but in practice all callers have the right tupdesc available.
*/
PyObject *
PLy_input_from_tuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc)
{ {
int i; PyObject *dict;
PLyExecutionContext *exec_ctx = PLy_current_execution_context(); PLyExecutionContext *exec_ctx = PLy_current_execution_context();
MemoryContext oldcxt; MemoryContext scratch_context = PLy_get_scratch_context(exec_ctx);
MemoryContext oldcontext;
oldcxt = MemoryContextSwitchTo(arg->mcxt); /*
* As in PLy_input_convert, do the work in the scratch context.
*/
MemoryContextReset(scratch_context);
if (arg->is_rowtype == 0) oldcontext = MemoryContextSwitchTo(scratch_context);
elog(ERROR, "PLyTypeInfo struct is initialized for a Datum");
arg->is_rowtype = 1;
if (arg->in.r.natts != desc->natts) dict = PLyDict_FromTuple(arg, tuple, desc);
{
if (arg->in.r.atts)
pfree(arg->in.r.atts);
arg->in.r.natts = desc->natts;
arg->in.r.atts = palloc0(desc->natts * sizeof(PLyDatumToOb));
}
/* Can this be an unnamed tuple? If not, then an Assert would be enough */ MemoryContextSwitchTo(oldcontext);
if (desc->tdtypmod != -1)
elog(ERROR, "received unnamed record type as input");
Assert(OidIsValid(desc->tdtypeid)); return dict;
}
/* /*
* RECORDOID means we got called to create input functions for a tuple * Initialize, or re-initialize, per-column input info for a composite type.
* fetched by plpy.execute or for an anonymous record type *
*/ * This is separate from PLy_input_setup_func() because in cases involving
if (desc->tdtypeid != RECORDOID) * anonymous record types, we need to be passed the tupdesc explicitly.
{ * It's caller's responsibility that the tupdesc has adequate lifespan
HeapTuple relTup; * in such cases. If the tupdesc is for a named composite or registered
* record type, it does not need to be long-lived.
*/
void
PLy_input_setup_tuple(PLyDatumToOb *arg, TupleDesc desc, PLyProcedure *proc)
{
int i;
/* Get the pg_class tuple corresponding to the type of the input */ /* We should be working on a previously-set-up struct */
arg->typ_relid = typeidTypeRelid(desc->tdtypeid); Assert(arg->func == PLyDict_FromComposite);
relTup = SearchSysCache1(RELOID, ObjectIdGetDatum(arg->typ_relid));
if (!HeapTupleIsValid(relTup))
elog(ERROR, "cache lookup failed for relation %u", arg->typ_relid);
/* Remember XMIN and TID for later validation if cache is still OK */ /* Save pointer to tupdesc, but only if this is an anonymous record type */
arg->typrel_xmin = HeapTupleHeaderGetRawXmin(relTup->t_data); if (arg->typoid == RECORDOID && arg->typmod < 0)
arg->typrel_tid = relTup->t_self; arg->u.tuple.recdesc = desc;
ReleaseSysCache(relTup); /* (Re)allocate atts array as needed */
if (arg->u.tuple.natts != desc->natts)
{
if (arg->u.tuple.atts)
pfree(arg->u.tuple.atts);
arg->u.tuple.natts = desc->natts;
arg->u.tuple.atts = (PLyDatumToOb *)
MemoryContextAllocZero(arg->mcxt,
desc->natts * sizeof(PLyDatumToOb));
} }
/* Fill the atts entries, except for dropped columns */
for (i = 0; i < desc->natts; i++) for (i = 0; i < desc->natts; i++)
{ {
HeapTuple typeTup;
Form_pg_attribute attr = TupleDescAttr(desc, i); Form_pg_attribute attr = TupleDescAttr(desc, i);
PLyDatumToOb *att = &arg->u.tuple.atts[i];
if (attr->attisdropped) if (attr->attisdropped)
continue; continue;
if (arg->in.r.atts[i].typoid == attr->atttypid) if (att->typoid == attr->atttypid && att->typmod == attr->atttypmod)
continue; /* already set up this entry */ continue; /* already set up this entry */
typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(attr->atttypid)); PLy_input_setup_func(att, arg->mcxt,
if (!HeapTupleIsValid(typeTup)) attr->atttypid, attr->atttypmod,
elog(ERROR, "cache lookup failed for type %u", proc);
attr->atttypid);
PLy_input_datum_func2(&(arg->in.r.atts[i]), arg->mcxt,
attr->atttypid,
typeTup,
exec_ctx->curr_proc->langid,
exec_ctx->curr_proc->trftypes);
ReleaseSysCache(typeTup);
} }
MemoryContextSwitchTo(oldcxt);
} }
/*
* Initialize, or re-initialize, per-column output info for a composite type.
*
* This is separate from PLy_output_setup_func() because in cases involving
* anonymous record types, we need to be passed the tupdesc explicitly.
* It's caller's responsibility that the tupdesc has adequate lifespan
* in such cases. If the tupdesc is for a named composite or registered
* record type, it does not need to be long-lived.
*/
void void
PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc) PLy_output_setup_tuple(PLyObToDatum *arg, TupleDesc desc, PLyProcedure *proc)
{ {
int i; int i;
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
MemoryContext oldcxt;
oldcxt = MemoryContextSwitchTo(arg->mcxt);
if (arg->is_rowtype == 0)
elog(ERROR, "PLyTypeInfo struct is initialized for a Datum");
arg->is_rowtype = 1;
if (arg->out.r.natts != desc->natts) /* We should be working on a previously-set-up struct */
{ Assert(arg->func == PLyObject_ToComposite);
if (arg->out.r.atts)
pfree(arg->out.r.atts);
arg->out.r.natts = desc->natts;
arg->out.r.atts = palloc0(desc->natts * sizeof(PLyObToDatum));
}
Assert(OidIsValid(desc->tdtypeid)); /* Save pointer to tupdesc, but only if this is an anonymous record type */
if (arg->typoid == RECORDOID && arg->typmod < 0)
arg->u.tuple.recdesc = desc;
/* /* (Re)allocate atts array as needed */
* RECORDOID means we got called to create output functions for an if (arg->u.tuple.natts != desc->natts)
* anonymous record type
*/
if (desc->tdtypeid != RECORDOID)
{ {
HeapTuple relTup; if (arg->u.tuple.atts)
pfree(arg->u.tuple.atts);
/* Get the pg_class tuple corresponding to the type of the output */ arg->u.tuple.natts = desc->natts;
arg->typ_relid = typeidTypeRelid(desc->tdtypeid); arg->u.tuple.atts = (PLyObToDatum *)
relTup = SearchSysCache1(RELOID, ObjectIdGetDatum(arg->typ_relid)); MemoryContextAllocZero(arg->mcxt,
if (!HeapTupleIsValid(relTup)) desc->natts * sizeof(PLyObToDatum));
elog(ERROR, "cache lookup failed for relation %u", arg->typ_relid);
/* Remember XMIN and TID for later validation if cache is still OK */
arg->typrel_xmin = HeapTupleHeaderGetRawXmin(relTup->t_data);
arg->typrel_tid = relTup->t_self;
ReleaseSysCache(relTup);
} }
/* Fill the atts entries, except for dropped columns */
for (i = 0; i < desc->natts; i++) for (i = 0; i < desc->natts; i++)
{ {
HeapTuple typeTup;
Form_pg_attribute attr = TupleDescAttr(desc, i); Form_pg_attribute attr = TupleDescAttr(desc, i);
PLyObToDatum *att = &arg->u.tuple.atts[i];
if (attr->attisdropped) if (attr->attisdropped)
continue; continue;
if (arg->out.r.atts[i].typoid == attr->atttypid) if (att->typoid == attr->atttypid && att->typmod == attr->atttypmod)
continue; /* already set up this entry */ continue; /* already set up this entry */
typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(attr->atttypid)); PLy_output_setup_func(att, arg->mcxt,
if (!HeapTupleIsValid(typeTup)) attr->atttypid, attr->atttypmod,
elog(ERROR, "cache lookup failed for type %u", proc);
attr->atttypid);
PLy_output_datum_func2(&(arg->out.r.atts[i]), arg->mcxt, typeTup,
exec_ctx->curr_proc->langid,
exec_ctx->curr_proc->trftypes);
ReleaseSysCache(typeTup);
} }
MemoryContextSwitchTo(oldcxt);
} }
/*
* Set up output info for a PL/Python function returning record.
*
* Note: the given tupdesc is not necessarily long-lived.
*/
void void
PLy_output_record_funcs(PLyTypeInfo *arg, TupleDesc desc) PLy_output_setup_record(PLyObToDatum *arg, TupleDesc desc, PLyProcedure *proc)
{ {
/* Makes no sense unless RECORD */
Assert(arg->typoid == RECORDOID);
Assert(desc->tdtypeid == RECORDOID);
/* /*
* If the output record functions are already set, we just have to check * Bless the record type if not already done. We'd have to do this anyway
* if the record descriptor has not changed * to return a tuple, so we might as well force the issue so we can use
* the known-record-type code path.
*/ */
if ((arg->is_rowtype == 1) &&
(arg->out.d.typmod != -1) &&
(arg->out.d.typmod == desc->tdtypmod))
return;
/* bless the record to make it known to the typcache lookup code */
BlessTupleDesc(desc); BlessTupleDesc(desc);
/* save the freshly generated typmod */
arg->out.d.typmod = desc->tdtypmod;
/* proceed with normal I/O function caching */
PLy_output_tuple_funcs(arg, desc);
/* /*
* it should change is_rowtype to 1, so we won't go through this again * Update arg->typmod, and clear the recdesc link if it's changed. The
* unless the output record description changes * next call of PLyObject_ToComposite will look up a long-lived tupdesc
* for the record type.
*/ */
Assert(arg->is_rowtype == 1); arg->typmod = desc->tdtypmod;
if (arg->u.tuple.recdesc &&
arg->u.tuple.recdesc->tdtypmod != arg->typmod)
arg->u.tuple.recdesc = NULL;
/* Update derived data if necessary */
PLy_output_setup_tuple(arg, desc, proc);
} }
/* /*
* Transform a tuple into a Python dict object. * Recursively initialize the PLyObToDatum structure(s) needed to construct
* a SQL value of the specified typeOid/typmod from a Python value.
* (But note that at this point we may have RECORDOID/-1, ie, an indeterminate
* record type.)
* proc is used to look up transform functions.
*/ */
PyObject * void
PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc) PLy_output_setup_func(PLyObToDatum *arg, MemoryContext arg_mcxt,
Oid typeOid, int32 typmod,
PLyProcedure *proc)
{ {
PyObject *volatile dict; TypeCacheEntry *typentry;
PLyExecutionContext *exec_ctx = PLy_current_execution_context(); char typtype;
MemoryContext scratch_context = PLy_get_scratch_context(exec_ctx); Oid trfuncid;
MemoryContext oldcontext = CurrentMemoryContext; Oid typinput;
if (info->is_rowtype != 1) /* Since this is recursive, it could theoretically be driven to overflow */
elog(ERROR, "PLyTypeInfo structure describes a datum"); check_stack_depth();
dict = PyDict_New(); arg->typoid = typeOid;
if (dict == NULL) arg->typmod = typmod;
PLy_elog(ERROR, "could not create new dictionary"); arg->mcxt = arg_mcxt;
PG_TRY(); /*
* Fetch typcache entry for the target type, asking for whatever info
* we'll need later. RECORD is a special case: just treat it as composite
* without bothering with the typcache entry.
*/
if (typeOid != RECORDOID)
{ {
int i; typentry = lookup_type_cache(typeOid, TYPECACHE_DOMAIN_BASE_INFO);
typtype = typentry->typtype;
/* arg->typbyval = typentry->typbyval;
* Do the work in the scratch context to avoid leaking memory from the arg->typlen = typentry->typlen;
* datatype output function calls. arg->typalign = typentry->typalign;
*/
MemoryContextSwitchTo(scratch_context);
for (i = 0; i < info->in.r.natts; i++)
{
char *key;
Datum vattr;
bool is_null;
PyObject *value;
Form_pg_attribute attr = TupleDescAttr(desc, i);
if (attr->attisdropped)
continue;
key = NameStr(attr->attname);
vattr = heap_getattr(tuple, (i + 1), desc, &is_null);
if (is_null || info->in.r.atts[i].func == NULL)
PyDict_SetItemString(dict, key, Py_None);
else
{
value = (info->in.r.atts[i].func) (&info->in.r.atts[i], vattr);
PyDict_SetItemString(dict, key, value);
Py_DECREF(value);
}
}
MemoryContextSwitchTo(oldcontext);
MemoryContextReset(scratch_context);
} }
PG_CATCH(); else
{ {
MemoryContextSwitchTo(oldcontext); typentry = NULL;
Py_DECREF(dict); typtype = TYPTYPE_COMPOSITE;
PG_RE_THROW(); /* hard-wired knowledge about type RECORD: */
arg->typbyval = false;
arg->typlen = -1;
arg->typalign = 'd';
} }
PG_END_TRY();
return dict;
}
/*
* Convert a Python object to a composite Datum, using all supported
* conversion methods: composite as a string, as a sequence, as a mapping or
* as an object that has __getattr__ support.
*/
Datum
PLyObject_ToCompositeDatum(PLyTypeInfo *info, TupleDesc desc, PyObject *plrv, bool inarray)
{
Datum datum;
if (PyString_Check(plrv) || PyUnicode_Check(plrv))
datum = PLyString_ToComposite(info, desc, plrv, inarray);
else if (PySequence_Check(plrv))
/* composite type as sequence (tuple, list etc) */
datum = PLySequence_ToComposite(info, desc, plrv);
else if (PyMapping_Check(plrv))
/* composite type as mapping (currently only dict) */
datum = PLyMapping_ToComposite(info, desc, plrv);
else
/* returned as smth, must provide method __getattr__(name) */
datum = PLyGenericObject_ToComposite(info, desc, plrv, inarray);
return datum;
}
static void
PLy_output_datum_func2(PLyObToDatum *arg, MemoryContext arg_mcxt, HeapTuple typeTup, Oid langid, List *trftypes)
{
Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
Oid element_type;
Oid base_type;
Oid funcid;
MemoryContext oldcxt;
oldcxt = MemoryContextSwitchTo(arg_mcxt);
fmgr_info_cxt(typeStruct->typinput, &arg->typfunc, arg_mcxt);
arg->typoid = HeapTupleGetOid(typeTup);
arg->typmod = -1;
arg->typioparam = getTypeIOParam(typeTup);
arg->typbyval = typeStruct->typbyval;
element_type = get_base_element_type(arg->typoid);
base_type = getBaseType(element_type ? element_type : arg->typoid);
/* /*
* Select a conversion function to convert Python objects to PostgreSQL * Choose conversion method. Note that transform functions are checked
* datums. * for composite and scalar types, but not for arrays or domains. This is
* somewhat historical, but we'd have a problem allowing them on domains,
* since we drill down through all levels of a domain nest without looking
* at the intermediate levels at all.
*/ */
if (typtype == TYPTYPE_DOMAIN)
if ((funcid = get_transform_tosql(base_type, langid, trftypes))) {
/* Domain */
arg->func = PLyObject_ToDomain;
arg->u.domain.domain_info = NULL;
/* Recursively set up conversion info for the element type */
arg->u.domain.base = (PLyObToDatum *)
MemoryContextAllocZero(arg_mcxt, sizeof(PLyObToDatum));
PLy_output_setup_func(arg->u.domain.base, arg_mcxt,
typentry->domainBaseType,
typentry->domainBaseTypmod,
proc);
}
else if (typentry &&
OidIsValid(typentry->typelem) && typentry->typlen == -1)
{
/* Standard varlena array (cf. get_element_type) */
arg->func = PLySequence_ToArray;
/* Get base type OID to insert into constructed array */
/* (note this might not be the same as the immediate child type) */
arg->u.array.elmbasetype = getBaseType(typentry->typelem);
/* Recursively set up conversion info for the element type */
arg->u.array.elm = (PLyObToDatum *)
MemoryContextAllocZero(arg_mcxt, sizeof(PLyObToDatum));
PLy_output_setup_func(arg->u.array.elm, arg_mcxt,
typentry->typelem, typmod,
proc);
}
else if ((trfuncid = get_transform_tosql(typeOid,
proc->langid,
proc->trftypes)))
{ {
arg->func = PLyObject_ToTransform; arg->func = PLyObject_ToTransform;
fmgr_info_cxt(funcid, &arg->typtransform, arg_mcxt); fmgr_info_cxt(trfuncid, &arg->u.transform.typtransform, arg_mcxt);
} }
else if (typeStruct->typtype == TYPTYPE_COMPOSITE) else if (typtype == TYPTYPE_COMPOSITE)
{ {
/* Named composite type, or RECORD */
arg->func = PLyObject_ToComposite; arg->func = PLyObject_ToComposite;
/* We'll set up the per-field data later */
arg->u.tuple.recdesc = NULL;
arg->u.tuple.typentry = typentry;
arg->u.tuple.tupdescseq = typentry ? typentry->tupDescSeqNo - 1 : 0;
arg->u.tuple.atts = NULL;
arg->u.tuple.natts = 0;
/* Mark this invalid till needed, too */
arg->u.tuple.recinfunc.fn_oid = InvalidOid;
} }
else else
switch (base_type) {
/* Scalar type, but we have a couple of special cases */
switch (typeOid)
{ {
case BOOLOID: case BOOLOID:
arg->func = PLyObject_ToBool; arg->func = PLyObject_ToBool;
...@@ -406,66 +402,111 @@ PLy_output_datum_func2(PLyObToDatum *arg, MemoryContext arg_mcxt, HeapTuple type ...@@ -406,66 +402,111 @@ PLy_output_datum_func2(PLyObToDatum *arg, MemoryContext arg_mcxt, HeapTuple type
arg->func = PLyObject_ToBytea; arg->func = PLyObject_ToBytea;
break; break;
default: default:
arg->func = PLyObject_ToDatum; arg->func = PLyObject_ToScalar;
getTypeInputInfo(typeOid, &typinput, &arg->u.scalar.typioparam);
fmgr_info_cxt(typinput, &arg->u.scalar.typfunc, arg_mcxt);
break; break;
} }
if (element_type)
{
char dummy_delim;
Oid funcid;
if (type_is_rowtype(element_type))
arg->func = PLyObject_ToComposite;
arg->elm = palloc0(sizeof(*arg->elm));
arg->elm->func = arg->func;
arg->elm->typtransform = arg->typtransform;
arg->func = PLySequence_ToArray;
arg->elm->typoid = element_type;
arg->elm->typmod = -1;
get_type_io_data(element_type, IOFunc_input,
&arg->elm->typlen, &arg->elm->typbyval, &arg->elm->typalign, &dummy_delim,
&arg->elm->typioparam, &funcid);
fmgr_info_cxt(funcid, &arg->elm->typfunc, arg_mcxt);
} }
MemoryContextSwitchTo(oldcxt);
} }
static void /*
PLy_input_datum_func2(PLyDatumToOb *arg, MemoryContext arg_mcxt, Oid typeOid, HeapTuple typeTup, Oid langid, List *trftypes) * Recursively initialize the PLyDatumToOb structure(s) needed to construct
* a Python value from a SQL value of the specified typeOid/typmod.
* (But note that at this point we may have RECORDOID/-1, ie, an indeterminate
* record type.)
* proc is used to look up transform functions.
*/
void
PLy_input_setup_func(PLyDatumToOb *arg, MemoryContext arg_mcxt,
Oid typeOid, int32 typmod,
PLyProcedure *proc)
{ {
Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup); TypeCacheEntry *typentry;
Oid element_type; char typtype;
Oid base_type; Oid trfuncid;
Oid funcid; Oid typoutput;
MemoryContext oldcxt; bool typisvarlena;
oldcxt = MemoryContextSwitchTo(arg_mcxt);
/* Get the type's conversion information */ /* Since this is recursive, it could theoretically be driven to overflow */
fmgr_info_cxt(typeStruct->typoutput, &arg->typfunc, arg_mcxt); check_stack_depth();
arg->typoid = HeapTupleGetOid(typeTup);
arg->typmod = -1;
arg->typioparam = getTypeIOParam(typeTup);
arg->typbyval = typeStruct->typbyval;
arg->typlen = typeStruct->typlen;
arg->typalign = typeStruct->typalign;
/* Determine which kind of Python object we will convert to */ arg->typoid = typeOid;
arg->typmod = typmod;
arg->mcxt = arg_mcxt;
element_type = get_base_element_type(typeOid); /*
base_type = getBaseType(element_type ? element_type : typeOid); * Fetch typcache entry for the target type, asking for whatever info
* we'll need later. RECORD is a special case: just treat it as composite
* without bothering with the typcache entry.
*/
if (typeOid != RECORDOID)
{
typentry = lookup_type_cache(typeOid, TYPECACHE_DOMAIN_BASE_INFO);
typtype = typentry->typtype;
arg->typbyval = typentry->typbyval;
arg->typlen = typentry->typlen;
arg->typalign = typentry->typalign;
}
else
{
typentry = NULL;
typtype = TYPTYPE_COMPOSITE;
/* hard-wired knowledge about type RECORD: */
arg->typbyval = false;
arg->typlen = -1;
arg->typalign = 'd';
}
if ((funcid = get_transform_fromsql(base_type, langid, trftypes))) /*
* Choose conversion method. Note that transform functions are checked
* for composite and scalar types, but not for arrays or domains. This is
* somewhat historical, but we'd have a problem allowing them on domains,
* since we drill down through all levels of a domain nest without looking
* at the intermediate levels at all.
*/
if (typtype == TYPTYPE_DOMAIN)
{
/* Domain --- we don't care, just recurse down to the base type */
PLy_input_setup_func(arg, arg_mcxt,
typentry->domainBaseType,
typentry->domainBaseTypmod,
proc);
}
else if (typentry &&
OidIsValid(typentry->typelem) && typentry->typlen == -1)
{
/* Standard varlena array (cf. get_element_type) */
arg->func = PLyList_FromArray;
/* Recursively set up conversion info for the element type */
arg->u.array.elm = (PLyDatumToOb *)
MemoryContextAllocZero(arg_mcxt, sizeof(PLyDatumToOb));
PLy_input_setup_func(arg->u.array.elm, arg_mcxt,
typentry->typelem, typmod,
proc);
}
else if ((trfuncid = get_transform_fromsql(typeOid,
proc->langid,
proc->trftypes)))
{ {
arg->func = PLyObject_FromTransform; arg->func = PLyObject_FromTransform;
fmgr_info_cxt(funcid, &arg->typtransform, arg_mcxt); fmgr_info_cxt(trfuncid, &arg->u.transform.typtransform, arg_mcxt);
}
else if (typtype == TYPTYPE_COMPOSITE)
{
/* Named composite type, or RECORD */
arg->func = PLyDict_FromComposite;
/* We'll set up the per-field data later */
arg->u.tuple.recdesc = NULL;
arg->u.tuple.typentry = typentry;
arg->u.tuple.tupdescseq = typentry ? typentry->tupDescSeqNo - 1 : 0;
arg->u.tuple.atts = NULL;
arg->u.tuple.natts = 0;
} }
else else
switch (base_type) {
/* Scalar type, but we have a couple of special cases */
switch (typeOid)
{ {
case BOOLOID: case BOOLOID:
arg->func = PLyBool_FromBool; arg->func = PLyBool_FromBool;
...@@ -495,30 +536,19 @@ PLy_input_datum_func2(PLyDatumToOb *arg, MemoryContext arg_mcxt, Oid typeOid, He ...@@ -495,30 +536,19 @@ PLy_input_datum_func2(PLyDatumToOb *arg, MemoryContext arg_mcxt, Oid typeOid, He
arg->func = PLyBytes_FromBytea; arg->func = PLyBytes_FromBytea;
break; break;
default: default:
arg->func = PLyString_FromDatum; arg->func = PLyString_FromScalar;
getTypeOutputInfo(typeOid, &typoutput, &typisvarlena);
fmgr_info_cxt(typoutput, &arg->u.scalar.typfunc, arg_mcxt);
break; break;
} }
if (element_type)
{
char dummy_delim;
Oid funcid;
arg->elm = palloc0(sizeof(*arg->elm));
arg->elm->func = arg->func;
arg->elm->typtransform = arg->typtransform;
arg->func = PLyList_FromArray;
arg->elm->typoid = element_type;
arg->elm->typmod = -1;
get_type_io_data(element_type, IOFunc_output,
&arg->elm->typlen, &arg->elm->typbyval, &arg->elm->typalign, &dummy_delim,
&arg->elm->typioparam, &funcid);
fmgr_info_cxt(funcid, &arg->elm->typfunc, arg_mcxt);
} }
MemoryContextSwitchTo(oldcxt);
} }
/*
* Special-purpose input converters.
*/
static PyObject * static PyObject *
PLyBool_FromBool(PLyDatumToOb *arg, Datum d) PLyBool_FromBool(PLyDatumToOb *arg, Datum d)
{ {
...@@ -611,27 +641,40 @@ PLyBytes_FromBytea(PLyDatumToOb *arg, Datum d) ...@@ -611,27 +641,40 @@ PLyBytes_FromBytea(PLyDatumToOb *arg, Datum d)
return PyBytes_FromStringAndSize(str, size); return PyBytes_FromStringAndSize(str, size);
} }
/*
* Generic input conversion using a SQL type's output function.
*/
static PyObject * static PyObject *
PLyString_FromDatum(PLyDatumToOb *arg, Datum d) PLyString_FromScalar(PLyDatumToOb *arg, Datum d)
{ {
char *x = OutputFunctionCall(&arg->typfunc, d); char *x = OutputFunctionCall(&arg->u.scalar.typfunc, d);
PyObject *r = PyString_FromString(x); PyObject *r = PyString_FromString(x);
pfree(x); pfree(x);
return r; return r;
} }
/*
* Convert using a from-SQL transform function.
*/
static PyObject * static PyObject *
PLyObject_FromTransform(PLyDatumToOb *arg, Datum d) PLyObject_FromTransform(PLyDatumToOb *arg, Datum d)
{ {
return (PyObject *) DatumGetPointer(FunctionCall1(&arg->typtransform, d)); Datum t;
t = FunctionCall1(&arg->u.transform.typtransform, d);
return (PyObject *) DatumGetPointer(t);
} }
/*
* Convert a SQL array to a Python list.
*/
static PyObject * static PyObject *
PLyList_FromArray(PLyDatumToOb *arg, Datum d) PLyList_FromArray(PLyDatumToOb *arg, Datum d)
{ {
ArrayType *array = DatumGetArrayTypeP(d); ArrayType *array = DatumGetArrayTypeP(d);
PLyDatumToOb *elm = arg->elm; PLyDatumToOb *elm = arg->u.array.elm;
int ndim; int ndim;
int *dims; int *dims;
char *dataptr; char *dataptr;
...@@ -736,6 +779,94 @@ PLyList_FromArray_recurse(PLyDatumToOb *elm, int *dims, int ndim, int dim, ...@@ -736,6 +779,94 @@ PLyList_FromArray_recurse(PLyDatumToOb *elm, int *dims, int ndim, int dim,
return list; return list;
} }
/*
* Convert a composite SQL value to a Python dict.
*/
static PyObject *
PLyDict_FromComposite(PLyDatumToOb *arg, Datum d)
{
PyObject *dict;
HeapTupleHeader td;
Oid tupType;
int32 tupTypmod;
TupleDesc tupdesc;
HeapTupleData tmptup;
td = DatumGetHeapTupleHeader(d);
/* 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 */
PLy_input_setup_tuple(arg, tupdesc,
PLy_current_execution_context()->curr_proc);
/* Build a temporary HeapTuple control structure */
tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
tmptup.t_data = td;
dict = PLyDict_FromTuple(arg, &tmptup, tupdesc);
ReleaseTupleDesc(tupdesc);
return dict;
}
/*
* Transform a tuple into a Python dict object.
*/
static PyObject *
PLyDict_FromTuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc)
{
PyObject *volatile dict;
/* Simple sanity check that desc matches */
Assert(desc->natts == arg->u.tuple.natts);
dict = PyDict_New();
if (dict == NULL)
PLy_elog(ERROR, "could not create new dictionary");
PG_TRY();
{
int i;
for (i = 0; i < arg->u.tuple.natts; i++)
{
PLyDatumToOb *att = &arg->u.tuple.atts[i];
Form_pg_attribute attr = TupleDescAttr(desc, i);
char *key;
Datum vattr;
bool is_null;
PyObject *value;
if (attr->attisdropped)
continue;
key = NameStr(attr->attname);
vattr = heap_getattr(tuple, (i + 1), desc, &is_null);
if (is_null)
PyDict_SetItemString(dict, key, Py_None);
else
{
value = att->func(att, vattr);
PyDict_SetItemString(dict, key, value);
Py_DECREF(value);
}
}
}
PG_CATCH();
{
Py_DECREF(dict);
PG_RE_THROW();
}
PG_END_TRY();
return dict;
}
/* /*
* Convert a Python object to a PostgreSQL bool datum. This can't go * Convert a Python object to a PostgreSQL bool datum. This can't go
* through the generic conversion function, because Python attaches a * through the generic conversion function, because Python attaches a
...@@ -743,17 +874,16 @@ PLyList_FromArray_recurse(PLyDatumToOb *elm, int *dims, int ndim, int dim, ...@@ -743,17 +874,16 @@ PLyList_FromArray_recurse(PLyDatumToOb *elm, int *dims, int ndim, int dim,
* type can parse. * type can parse.
*/ */
static Datum static Datum
PLyObject_ToBool(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray) PLyObject_ToBool(PLyObToDatum *arg, PyObject *plrv,
bool *isnull, bool inarray)
{ {
Datum rv; if (plrv == Py_None)
{
Assert(plrv != Py_None); *isnull = true;
rv = BoolGetDatum(PyObject_IsTrue(plrv)); return (Datum) 0;
}
if (get_typtype(arg->typoid) == TYPTYPE_DOMAIN) *isnull = false;
domain_check(rv, false, arg->typoid, &arg->typfunc.fn_extra, arg->typfunc.fn_mcxt); return BoolGetDatum(PyObject_IsTrue(plrv));
return rv;
} }
/* /*
...@@ -762,12 +892,18 @@ PLyObject_ToBool(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray) ...@@ -762,12 +892,18 @@ PLyObject_ToBool(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray)
* with embedded nulls. And it's faster this way. * with embedded nulls. And it's faster this way.
*/ */
static Datum static Datum
PLyObject_ToBytea(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray) PLyObject_ToBytea(PLyObToDatum *arg, PyObject *plrv,
bool *isnull, bool inarray)
{ {
PyObject *volatile plrv_so = NULL; PyObject *volatile plrv_so = NULL;
Datum rv; Datum rv;
Assert(plrv != Py_None); if (plrv == Py_None)
{
*isnull = true;
return (Datum) 0;
}
*isnull = false;
plrv_so = PyObject_Bytes(plrv); plrv_so = PyObject_Bytes(plrv);
if (!plrv_so) if (!plrv_so)
...@@ -793,9 +929,6 @@ PLyObject_ToBytea(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray) ...@@ -793,9 +929,6 @@ PLyObject_ToBytea(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray)
Py_XDECREF(plrv_so); Py_XDECREF(plrv_so);
if (get_typtype(arg->typoid) == TYPTYPE_DOMAIN)
domain_check(rv, false, arg->typoid, &arg->typfunc.fn_extra, arg->typfunc.fn_mcxt);
return rv; return rv;
} }
...@@ -806,45 +939,87 @@ PLyObject_ToBytea(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray) ...@@ -806,45 +939,87 @@ PLyObject_ToBytea(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray)
* for obtaining PostgreSQL tuples. * for obtaining PostgreSQL tuples.
*/ */
static Datum static Datum
PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray) PLyObject_ToComposite(PLyObToDatum *arg, PyObject *plrv,
bool *isnull, bool inarray)
{ {
Datum rv; Datum rv;
PLyTypeInfo info;
TupleDesc desc; TupleDesc desc;
MemoryContext cxt;
if (typmod != -1) if (plrv == Py_None)
elog(ERROR, "received unnamed record type as input"); {
*isnull = true;
return (Datum) 0;
}
*isnull = false;
/*
* The string conversion case doesn't require a tupdesc, nor per-field
* conversion data, so just go for it if that's the case to use.
*/
if (PyString_Check(plrv) || PyUnicode_Check(plrv))
return PLyString_ToComposite(arg, plrv, inarray);
/* Create a dummy PLyTypeInfo */ /*
cxt = AllocSetContextCreate(CurrentMemoryContext, * If we're dealing with a named composite type, we must look up the
"PL/Python temp context", * tupdesc every time, to protect against possible changes to the type.
ALLOCSET_DEFAULT_SIZES); * RECORD types can't change between calls; but we must still be willing
MemSet(&info, 0, sizeof(PLyTypeInfo)); * to set up the info the first time, if nobody did yet.
PLy_typeinfo_init(&info, cxt); */
/* Mark it as needing output routines lookup */ if (arg->typoid != RECORDOID)
info.is_rowtype = 2; {
desc = lookup_rowtype_tupdesc(arg->typoid, arg->typmod);
/* We should have the descriptor of the type's typcache entry */
Assert(desc == arg->u.tuple.typentry->tupDesc);
/* Detect change of descriptor, update cache if needed */
if (arg->u.tuple.tupdescseq != arg->u.tuple.typentry->tupDescSeqNo)
{
PLy_output_setup_tuple(arg, desc,
PLy_current_execution_context()->curr_proc);
arg->u.tuple.tupdescseq = arg->u.tuple.typentry->tupDescSeqNo;
}
}
else
{
desc = arg->u.tuple.recdesc;
if (desc == NULL)
{
desc = lookup_rowtype_tupdesc(arg->typoid, arg->typmod);
arg->u.tuple.recdesc = desc;
}
else
{
/* Pin descriptor to match unpin below */
PinTupleDesc(desc);
}
}
desc = lookup_rowtype_tupdesc(arg->typoid, arg->typmod); /* Simple sanity check on our caching */
Assert(desc->natts == arg->u.tuple.natts);
/* /*
* This will set up the dummy PLyTypeInfo's output conversion routines, * Convert, using the appropriate method depending on the type of the
* since we left is_rowtype as 2. A future optimization could be caching * supplied Python object.
* that info instead of looking it up every time a tuple is returned from
* the function.
*/ */
rv = PLyObject_ToCompositeDatum(&info, desc, plrv, inarray); if (PySequence_Check(plrv))
/* composite type as sequence (tuple, list etc) */
rv = PLySequence_ToComposite(arg, desc, plrv);
else if (PyMapping_Check(plrv))
/* composite type as mapping (currently only dict) */
rv = PLyMapping_ToComposite(arg, desc, plrv);
else
/* returned as smth, must provide method __getattr__(name) */
rv = PLyGenericObject_ToComposite(arg, desc, plrv, inarray);
ReleaseTupleDesc(desc); ReleaseTupleDesc(desc);
MemoryContextDelete(cxt);
return rv; return rv;
} }
/* /*
* Convert Python object to C string in server encoding. * Convert Python object to C string in server encoding.
*
* Note: this is exported for use by add-on transform modules.
*/ */
char * char *
PLyObject_AsString(PyObject *plrv) PLyObject_AsString(PyObject *plrv)
...@@ -901,74 +1076,71 @@ PLyObject_AsString(PyObject *plrv) ...@@ -901,74 +1076,71 @@ PLyObject_AsString(PyObject *plrv)
/* /*
* Generic conversion function: Convert PyObject to cstring and * Generic output conversion function: convert PyObject to cstring and
* cstring into PostgreSQL type. * cstring into PostgreSQL type.
*/ */
static Datum static Datum
PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray) PLyObject_ToScalar(PLyObToDatum *arg, PyObject *plrv,
bool *isnull, bool inarray)
{ {
char *str; char *str;
Assert(plrv != Py_None); if (plrv == Py_None)
{
*isnull = true;
return (Datum) 0;
}
*isnull = false;
str = PLyObject_AsString(plrv); str = PLyObject_AsString(plrv);
/* return InputFunctionCall(&arg->u.scalar.typfunc,
* If we are parsing a composite type within an array, and the string str,
* isn't a valid record literal, there's a high chance that the function arg->u.scalar.typioparam,
* did something like: arg->typmod);
* }
* CREATE FUNCTION .. RETURNS comptype[] AS $$ return [['foo', 'bar']] $$
* LANGUAGE plpython;
*
* Before PostgreSQL 10, that was interpreted as a single-dimensional
* array, containing record ('foo', 'bar'). PostgreSQL 10 added support
* for multi-dimensional arrays, and it is now interpreted as a
* two-dimensional array, containing two records, 'foo', and 'bar'.
* record_in() will throw an error, because "foo" is not a valid record
* literal.
*
* To make that less confusing to users who are upgrading from older
* versions, try to give a hint in the typical instances of that. If we
* are parsing an array of composite types, and we see a string literal
* that is not a valid record literal, give a hint. We only want to give
* the hint in the narrow case of a malformed string literal, not any
* error from record_in(), so check for that case here specifically.
*
* This check better match the one in record_in(), so that we don't forbid
* literals that are actually valid!
*/
if (inarray && arg->typfunc.fn_oid == F_RECORD_IN)
{
char *ptr = str;
/* Allow leading whitespace */
while (*ptr && isspace((unsigned char) *ptr))
ptr++;
if (*ptr++ != '(')
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("malformed record literal: \"%s\"", str),
errdetail("Missing left parenthesis."),
errhint("To return a composite type in an array, return the composite type as a Python tuple, e.g., \"[('foo',)]\".")));
}
return InputFunctionCall(&arg->typfunc, /*
str, * Convert to a domain type.
arg->typioparam, */
typmod); static Datum
PLyObject_ToDomain(PLyObToDatum *arg, PyObject *plrv,
bool *isnull, bool inarray)
{
Datum result;
PLyObToDatum *base = arg->u.domain.base;
result = base->func(base, plrv, isnull, inarray);
domain_check(result, *isnull, arg->typoid,
&arg->u.domain.domain_info, arg->mcxt);
return result;
} }
/*
* Convert using a to-SQL transform function.
*/
static Datum static Datum
PLyObject_ToTransform(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray) PLyObject_ToTransform(PLyObToDatum *arg, PyObject *plrv,
bool *isnull, bool inarray)
{ {
return FunctionCall1(&arg->typtransform, PointerGetDatum(plrv)); if (plrv == Py_None)
{
*isnull = true;
return (Datum) 0;
}
*isnull = false;
return FunctionCall1(&arg->u.transform.typtransform, PointerGetDatum(plrv));
} }
/*
* Convert Python sequence to SQL array.
*/
static Datum static Datum
PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray) PLySequence_ToArray(PLyObToDatum *arg, PyObject *plrv,
bool *isnull, bool inarray)
{ {
ArrayType *array; ArrayType *array;
int i; int i;
...@@ -979,11 +1151,15 @@ PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarra ...@@ -979,11 +1151,15 @@ PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarra
int dims[MAXDIM]; int dims[MAXDIM];
int lbs[MAXDIM]; int lbs[MAXDIM];
int currelem; int currelem;
Datum rv;
PyObject *pyptr = plrv; PyObject *pyptr = plrv;
PyObject *next; PyObject *next;
Assert(plrv != Py_None); if (plrv == Py_None)
{
*isnull = true;
return (Datum) 0;
}
*isnull = false;
/* /*
* Determine the number of dimensions, and their sizes. * Determine the number of dimensions, and their sizes.
...@@ -1049,7 +1225,7 @@ PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarra ...@@ -1049,7 +1225,7 @@ PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarra
elems = palloc(sizeof(Datum) * len); elems = palloc(sizeof(Datum) * len);
nulls = palloc(sizeof(bool) * len); nulls = palloc(sizeof(bool) * len);
currelem = 0; currelem = 0;
PLySequence_ToArray_recurse(arg->elm, plrv, PLySequence_ToArray_recurse(arg->u.array.elm, plrv,
dims, ndim, 0, dims, ndim, 0,
elems, nulls, &currelem); elems, nulls, &currelem);
...@@ -1061,19 +1237,12 @@ PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarra ...@@ -1061,19 +1237,12 @@ PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarra
ndim, ndim,
dims, dims,
lbs, lbs,
get_base_element_type(arg->typoid), arg->u.array.elmbasetype,
arg->elm->typlen, arg->u.array.elm->typlen,
arg->elm->typbyval, arg->u.array.elm->typbyval,
arg->elm->typalign); arg->u.array.elm->typalign);
/* return PointerGetDatum(array);
* If the result type is a domain of array, the resulting array must be
* checked.
*/
rv = PointerGetDatum(array);
if (get_typtype(arg->typoid) == TYPTYPE_DOMAIN)
domain_check(rv, false, arg->typoid, &arg->typfunc.fn_extra, arg->typfunc.fn_mcxt);
return rv;
} }
/* /*
...@@ -1110,16 +1279,7 @@ PLySequence_ToArray_recurse(PLyObToDatum *elm, PyObject *list, ...@@ -1110,16 +1279,7 @@ PLySequence_ToArray_recurse(PLyObToDatum *elm, PyObject *list,
{ {
PyObject *obj = PySequence_GetItem(list, i); PyObject *obj = PySequence_GetItem(list, i);
if (obj == Py_None) elems[*currelem] = elm->func(elm, obj, &nulls[*currelem], true);
{
nulls[*currelem] = true;
elems[*currelem] = (Datum) 0;
}
else
{
nulls[*currelem] = false;
elems[*currelem] = elm->func(elm, -1, obj, true);
}
Py_XDECREF(obj); Py_XDECREF(obj);
(*currelem)++; (*currelem)++;
} }
...@@ -1127,42 +1287,72 @@ PLySequence_ToArray_recurse(PLyObToDatum *elm, PyObject *list, ...@@ -1127,42 +1287,72 @@ PLySequence_ToArray_recurse(PLyObToDatum *elm, PyObject *list,
} }
/*
* Convert a Python string to composite, using record_in.
*/
static Datum static Datum
PLyString_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *string, bool inarray) PLyString_ToComposite(PLyObToDatum *arg, PyObject *string, bool inarray)
{ {
Datum result; char *str;
HeapTuple typeTup;
PLyTypeInfo locinfo;
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
MemoryContext cxt;
/* Create a dummy PLyTypeInfo */
cxt = AllocSetContextCreate(CurrentMemoryContext,
"PL/Python temp context",
ALLOCSET_DEFAULT_SIZES);
MemSet(&locinfo, 0, sizeof(PLyTypeInfo));
PLy_typeinfo_init(&locinfo, cxt);
typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(desc->tdtypeid));
if (!HeapTupleIsValid(typeTup))
elog(ERROR, "cache lookup failed for type %u", desc->tdtypeid);
PLy_output_datum_func2(&locinfo.out.d, locinfo.mcxt, typeTup, /*
exec_ctx->curr_proc->langid, * Set up call data for record_in, if we didn't already. (We can't just
exec_ctx->curr_proc->trftypes); * use DirectFunctionCall, because record_in needs a fn_extra field.)
*/
if (!OidIsValid(arg->u.tuple.recinfunc.fn_oid))
fmgr_info_cxt(F_RECORD_IN, &arg->u.tuple.recinfunc, arg->mcxt);
ReleaseSysCache(typeTup); str = PLyObject_AsString(string);
result = PLyObject_ToDatum(&locinfo.out.d, desc->tdtypmod, string, inarray); /*
* If we are parsing a composite type within an array, and the string
* isn't a valid record literal, there's a high chance that the function
* did something like:
*
* CREATE FUNCTION .. RETURNS comptype[] AS $$ return [['foo', 'bar']] $$
* LANGUAGE plpython;
*
* Before PostgreSQL 10, that was interpreted as a single-dimensional
* array, containing record ('foo', 'bar'). PostgreSQL 10 added support
* for multi-dimensional arrays, and it is now interpreted as a
* two-dimensional array, containing two records, 'foo', and 'bar'.
* record_in() will throw an error, because "foo" is not a valid record
* literal.
*
* To make that less confusing to users who are upgrading from older
* versions, try to give a hint in the typical instances of that. If we
* are parsing an array of composite types, and we see a string literal
* that is not a valid record literal, give a hint. We only want to give
* the hint in the narrow case of a malformed string literal, not any
* error from record_in(), so check for that case here specifically.
*
* This check better match the one in record_in(), so that we don't forbid
* literals that are actually valid!
*/
if (inarray)
{
char *ptr = str;
MemoryContextDelete(cxt); /* Allow leading whitespace */
while (*ptr && isspace((unsigned char) *ptr))
ptr++;
if (*ptr++ != '(')
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("malformed record literal: \"%s\"", str),
errdetail("Missing left parenthesis."),
errhint("To return a composite type in an array, return the composite type as a Python tuple, e.g., \"[('foo',)]\".")));
}
return result; return InputFunctionCall(&arg->u.tuple.recinfunc,
str,
arg->typoid,
arg->typmod);
} }
static Datum static Datum
PLyMapping_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping) PLyMapping_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *mapping)
{ {
Datum result; Datum result;
HeapTuple tuple; HeapTuple tuple;
...@@ -1172,10 +1362,6 @@ PLyMapping_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping) ...@@ -1172,10 +1362,6 @@ PLyMapping_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping)
Assert(PyMapping_Check(mapping)); Assert(PyMapping_Check(mapping));
if (info->is_rowtype == 2)
PLy_output_tuple_funcs(info, desc);
Assert(info->is_rowtype == 1);
/* Build tuple */ /* Build tuple */
values = palloc(sizeof(Datum) * desc->natts); values = palloc(sizeof(Datum) * desc->natts);
nulls = palloc(sizeof(bool) * desc->natts); nulls = palloc(sizeof(bool) * desc->natts);
...@@ -1195,27 +1381,19 @@ PLyMapping_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping) ...@@ -1195,27 +1381,19 @@ PLyMapping_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping)
key = NameStr(attr->attname); key = NameStr(attr->attname);
value = NULL; value = NULL;
att = &info->out.r.atts[i]; att = &arg->u.tuple.atts[i];
PG_TRY(); PG_TRY();
{ {
value = PyMapping_GetItemString(mapping, key); value = PyMapping_GetItemString(mapping, key);
if (value == Py_None) if (!value)
{
values[i] = (Datum) NULL;
nulls[i] = true;
}
else if (value)
{
values[i] = (att->func) (att, -1, value, false);
nulls[i] = false;
}
else
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN), (errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("key \"%s\" not found in mapping", key), errmsg("key \"%s\" not found in mapping", key),
errhint("To return null in a column, " errhint("To return null in a column, "
"add the value None to the mapping with the key named after the column."))); "add the value None to the mapping with the key named after the column.")));
values[i] = att->func(att, value, &nulls[i], false);
Py_XDECREF(value); Py_XDECREF(value);
value = NULL; value = NULL;
} }
...@@ -1239,7 +1417,7 @@ PLyMapping_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping) ...@@ -1239,7 +1417,7 @@ PLyMapping_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping)
static Datum static Datum
PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence) PLySequence_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *sequence)
{ {
Datum result; Datum result;
HeapTuple tuple; HeapTuple tuple;
...@@ -1266,10 +1444,6 @@ PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence) ...@@ -1266,10 +1444,6 @@ PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence)
(errcode(ERRCODE_DATATYPE_MISMATCH), (errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("length of returned sequence did not match number of columns in row"))); errmsg("length of returned sequence did not match number of columns in row")));
if (info->is_rowtype == 2)
PLy_output_tuple_funcs(info, desc);
Assert(info->is_rowtype == 1);
/* Build tuple */ /* Build tuple */
values = palloc(sizeof(Datum) * desc->natts); values = palloc(sizeof(Datum) * desc->natts);
nulls = palloc(sizeof(bool) * desc->natts); nulls = palloc(sizeof(bool) * desc->natts);
...@@ -1287,21 +1461,13 @@ PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence) ...@@ -1287,21 +1461,13 @@ PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence)
} }
value = NULL; value = NULL;
att = &info->out.r.atts[i]; att = &arg->u.tuple.atts[i];
PG_TRY(); PG_TRY();
{ {
value = PySequence_GetItem(sequence, idx); value = PySequence_GetItem(sequence, idx);
Assert(value); Assert(value);
if (value == Py_None)
{ values[i] = att->func(att, value, &nulls[i], false);
values[i] = (Datum) NULL;
nulls[i] = true;
}
else if (value)
{
values[i] = (att->func) (att, -1, value, false);
nulls[i] = false;
}
Py_XDECREF(value); Py_XDECREF(value);
value = NULL; value = NULL;
...@@ -1328,7 +1494,7 @@ PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence) ...@@ -1328,7 +1494,7 @@ PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence)
static Datum static Datum
PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object, bool inarray) PLyGenericObject_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *object, bool inarray)
{ {
Datum result; Datum result;
HeapTuple tuple; HeapTuple tuple;
...@@ -1336,10 +1502,6 @@ PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object ...@@ -1336,10 +1502,6 @@ PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object
bool *nulls; bool *nulls;
volatile int i; volatile int i;
if (info->is_rowtype == 2)
PLy_output_tuple_funcs(info, desc);
Assert(info->is_rowtype == 1);
/* Build tuple */ /* Build tuple */
values = palloc(sizeof(Datum) * desc->natts); values = palloc(sizeof(Datum) * desc->natts);
nulls = palloc(sizeof(bool) * desc->natts); nulls = palloc(sizeof(bool) * desc->natts);
...@@ -1359,21 +1521,11 @@ PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object ...@@ -1359,21 +1521,11 @@ PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object
key = NameStr(attr->attname); key = NameStr(attr->attname);
value = NULL; value = NULL;
att = &info->out.r.atts[i]; att = &arg->u.tuple.atts[i];
PG_TRY(); PG_TRY();
{ {
value = PyObject_GetAttrString(object, key); value = PyObject_GetAttrString(object, key);
if (value == Py_None) if (!value)
{
values[i] = (Datum) NULL;
nulls[i] = true;
}
else if (value)
{
values[i] = (att->func) (att, -1, value, false);
nulls[i] = false;
}
else
{ {
/* /*
* No attribute for this column in the object. * No attribute for this column in the object.
...@@ -1384,7 +1536,7 @@ PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object ...@@ -1384,7 +1536,7 @@ PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object
* array, with a composite type (123, 'foo') in it. But now * array, with a composite type (123, 'foo') in it. But now
* it's interpreted as a two-dimensional array, and we try to * it's interpreted as a two-dimensional array, and we try to
* interpret "123" as the composite type. See also similar * interpret "123" as the composite type. See also similar
* heuristic in PLyObject_ToDatum(). * heuristic in PLyObject_ToScalar().
*/ */
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN), (errcode(ERRCODE_UNDEFINED_COLUMN),
...@@ -1394,6 +1546,8 @@ PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object ...@@ -1394,6 +1546,8 @@ PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object
errhint("To return null in a column, let the returned object have an attribute named after column with value None."))); errhint("To return null in a column, let the returned object have an attribute named after column with value None.")));
} }
values[i] = att->func(att, value, &nulls[i], false);
Py_XDECREF(value); Py_XDECREF(value);
value = NULL; value = NULL;
} }
......
...@@ -6,117 +6,169 @@ ...@@ -6,117 +6,169 @@
#define PLPY_TYPEIO_H #define PLPY_TYPEIO_H
#include "access/htup.h" #include "access/htup.h"
#include "access/tupdesc.h"
#include "fmgr.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 struct PLyDatumToOb PLyDatumToOb; /* forward reference */
typedef PyObject *(*PLyDatumToObFunc) (struct PLyDatumToOb *arg, Datum val);
typedef struct PLyDatumToOb typedef PyObject *(*PLyDatumToObFunc) (PLyDatumToOb *arg, Datum val);
typedef struct PLyScalarToOb
{ {
PLyDatumToObFunc func; FmgrInfo typfunc; /* lookup info for type's output function */
FmgrInfo typfunc; /* The type's output function */ } PLyScalarToOb;
FmgrInfo typtransform; /* from-SQL transform */
Oid typoid; /* The OID of the type */ typedef struct PLyArrayToOb
int32 typmod; /* The typmod of the type */ {
Oid typioparam; PLyDatumToOb *elm; /* conversion info for array's element type */
bool typbyval; } PLyArrayToOb;
int16 typlen;
char typalign;
struct PLyDatumToOb *elm;
} PLyDatumToOb;
typedef struct PLyTupleToOb typedef struct PLyTupleToOb
{ {
PLyDatumToOb *atts; /* If we're dealing with a RECORD type, actual descriptor is here: */
int natts; 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; } PLyTupleToOb;
typedef union PLyTypeInput typedef struct PLyTransformToOb
{
FmgrInfo typtransform; /* lookup info for from-SQL transform func */
} PLyTransformToOb;
struct PLyDatumToOb
{ {
PLyDatumToOb d; PLyDatumToObFunc func; /* conversion control function */
PLyTupleToOb r; Oid typoid; /* OID of the source type */
} PLyTypeInput; int32 typmod; /* typmod of the source type */
bool typbyval; /* its physical representation details */
int16 typlen;
char typalign;
MemoryContext mcxt; /* context this info is stored in */
union /* conversion-type-specific data */
{
PLyScalarToOb scalar;
PLyArrayToOb array;
PLyTupleToOb tuple;
PLyTransformToOb transform;
} u;
};
/* /*
* Conversion from Python object to a PostgreSQL Datum. * "Output" conversion from Python object to a PostgreSQL Datum.
*
* arg is the previously-set-up conversion data, val is the value to convert.
* *
* The 'inarray' argument to the conversion function is true, if the * *isnull is set to true if val is Py_None, false otherwise.
* converted value was in an array (Python list). It is used to give a * (The conversion function *must* be called even for Py_None,
* better error message in some cases. * 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.
*/ */
struct PLyObToDatum; typedef struct PLyObToDatum PLyObToDatum; /* forward reference */
typedef Datum (*PLyObToDatumFunc) (struct PLyObToDatum *arg, int32 typmod, PyObject *val, bool inarray);
typedef Datum (*PLyObToDatumFunc) (PLyObToDatum *arg, PyObject *val,
bool *isnull,
bool inarray);
typedef struct PLyObToDatum typedef struct PLyObToScalar
{ {
PLyObToDatumFunc func; FmgrInfo typfunc; /* lookup info for type's input function */
FmgrInfo typfunc; /* The type's input function */ Oid typioparam; /* argument to pass to it */
FmgrInfo typtransform; /* to-SQL transform */ } PLyObToScalar;
Oid typoid; /* The OID of the type */
int32 typmod; /* The typmod of the type */ typedef struct PLyObToArray
Oid typioparam; {
bool typbyval; PLyObToDatum *elm; /* conversion info for array's element type */
int16 typlen; Oid elmbasetype; /* element base type */
char typalign; } PLyObToArray;
struct PLyObToDatum *elm;
} PLyObToDatum;
typedef struct PLyObToTuple typedef struct PLyObToTuple
{ {
PLyObToDatum *atts; /* If we're dealing with a RECORD type, actual descriptor is here: */
int natts; 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; } PLyObToTuple;
typedef union PLyTypeOutput typedef struct PLyObToDomain
{ {
PLyObToDatum d; PLyObToDatum *base; /* conversion info for domain's base type */
PLyObToTuple r; void *domain_info; /* cache space for domain_check() */
} PLyTypeOutput; } PLyObToDomain;
/* all we need to move PostgreSQL data to Python objects, typedef struct PLyObToTransform
* and vice versa
*/
typedef struct PLyTypeInfo
{ {
PLyTypeInput in; FmgrInfo typtransform; /* lookup info for to-SQL transform function */
PLyTypeOutput out; } 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;
/* context for subsidiary data (doesn't belong to this struct though) */
MemoryContext mcxt;
} PLyTypeInfo;
extern void PLy_typeinfo_init(PLyTypeInfo *arg, MemoryContext mcxt);
extern void PLy_input_datum_func(PLyTypeInfo *arg, Oid typeOid, HeapTuple typeTup, Oid langid, List *trftypes); struct PLyObToDatum
extern void PLy_output_datum_func(PLyTypeInfo *arg, HeapTuple typeTup, Oid langid, List *trftypes); {
PLyObToDatumFunc func; /* conversion control function */
extern void PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc); Oid typoid; /* OID of the target type */
extern void PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc); int32 typmod; /* typmod of the target type */
bool typbyval; /* its physical representation details */
extern void PLy_output_record_funcs(PLyTypeInfo *arg, TupleDesc desc); int16 typlen;
char typalign;
/* conversion from Python objects to composite Datums */ MemoryContext mcxt; /* context this info is stored in */
extern Datum PLyObject_ToCompositeDatum(PLyTypeInfo *info, TupleDesc desc, PyObject *plrv, bool isarray); union /* conversion-type-specific data */
{
/* conversion from heap tuples to Python dictionaries */ PLyObToScalar scalar;
extern PyObject *PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc); PLyObToArray array;
PLyObToTuple tuple;
/* conversion from Python objects to C strings */ PLyObToDomain domain;
PLyObToTransform transform;
} u;
};
extern PyObject *PLy_input_convert(PLyDatumToOb *arg, Datum val);
extern Datum PLy_output_convert(PLyObToDatum *arg, PyObject *val,
bool *isnull);
extern PyObject *PLy_input_from_tuple(PLyDatumToOb *arg, HeapTuple tuple,
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_input_setup_tuple(PLyDatumToOb *arg, TupleDesc desc,
struct PLyProcedure *proc);
extern void PLy_output_setup_tuple(PLyObToDatum *arg, TupleDesc desc,
struct PLyProcedure *proc);
extern void PLy_output_setup_record(PLyObToDatum *arg, TupleDesc desc,
struct PLyProcedure *proc);
/* conversion from Python objects to C strings --- exported for transforms */
extern char *PLyObject_AsString(PyObject *plrv); extern char *PLyObject_AsString(PyObject *plrv);
#endif /* PLPY_TYPEIO_H */ #endif /* PLPY_TYPEIO_H */
...@@ -387,6 +387,55 @@ $$ LANGUAGE plpythonu; ...@@ -387,6 +387,55 @@ $$ LANGUAGE plpythonu;
SELECT * FROM test_type_conversion_array_domain_check_violation(); 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 --- Composite types
--- ---
...@@ -430,6 +479,48 @@ ALTER TYPE named_pair RENAME TO named_pair_2; ...@@ -430,6 +479,48 @@ ALTER TYPE named_pair RENAME TO named_pair_2;
SELECT test_composite_type_input(row(1, 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 -- Prepared statements
-- --
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment