aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--contrib/hstore_plpython/expected/hstore_plpython.out20
-rw-r--r--contrib/hstore_plpython/sql/hstore_plpython.sql16
-rw-r--r--src/backend/utils/cache/typcache.c7
-rw-r--r--src/include/utils/typcache.h5
-rw-r--r--src/pl/plpython/expected/plpython_types.out128
-rw-r--r--src/pl/plpython/expected/plpython_types_3.out128
-rw-r--r--src/pl/plpython/plpy_cursorobject.c76
-rw-r--r--src/pl/plpython/plpy_cursorobject.h2
-rw-r--r--src/pl/plpython/plpy_exec.c173
-rw-r--r--src/pl/plpython/plpy_main.c7
-rw-r--r--src/pl/plpython/plpy_planobject.h2
-rw-r--r--src/pl/plpython/plpy_procedure.c148
-rw-r--r--src/pl/plpython/plpy_procedure.h8
-rw-r--r--src/pl/plpython/plpy_spi.c84
-rw-r--r--src/pl/plpython/plpy_typeio.c1192
-rw-r--r--src/pl/plpython/plpy_typeio.h220
-rw-r--r--src/pl/plpython/sql/plpython_types.sql91
17 files changed, 1378 insertions, 929 deletions
diff --git a/contrib/hstore_plpython/expected/hstore_plpython.out b/contrib/hstore_plpython/expected/hstore_plpython.out
index df49cd5f373..1ab5feea93d 100644
--- a/contrib/hstore_plpython/expected/hstore_plpython.out
+++ b/contrib/hstore_plpython/expected/hstore_plpython.out
@@ -68,12 +68,30 @@ AS $$
val = [{'a': 1, 'b': 'boo', 'c': None}, {'d': 2}]
return val
$$;
- SELECT test2arr();
+SELECT test2arr();
test2arr
--------------------------------------------------------------
{"\"a\"=>\"1\", \"b\"=>\"boo\", \"c\"=>NULL","\"d\"=>\"2\""}
(1 row)
+-- test python -> domain over hstore
+CREATE DOMAIN hstore_foo AS hstore CHECK(VALUE ? 'foo');
+CREATE FUNCTION test2dom(fn text) RETURNS hstore_foo
+LANGUAGE plpythonu
+TRANSFORM FOR TYPE hstore
+AS $$
+return {'a': 1, fn: 'boo', 'c': None}
+$$;
+SELECT test2dom('foo');
+ test2dom
+-----------------------------------
+ "a"=>"1", "c"=>NULL, "foo"=>"boo"
+(1 row)
+
+SELECT test2dom('bar'); -- fail
+ERROR: value for domain hstore_foo violates check constraint "hstore_foo_check"
+CONTEXT: while creating return value
+PL/Python function "test2dom"
-- test as part of prepare/execute
CREATE FUNCTION test3() RETURNS void
LANGUAGE plpythonu
diff --git a/contrib/hstore_plpython/sql/hstore_plpython.sql b/contrib/hstore_plpython/sql/hstore_plpython.sql
index 911bbd67fed..2c54ee6aaad 100644
--- a/contrib/hstore_plpython/sql/hstore_plpython.sql
+++ b/contrib/hstore_plpython/sql/hstore_plpython.sql
@@ -60,7 +60,21 @@ val = [{'a': 1, 'b': 'boo', 'c': None}, {'d': 2}]
return val
$$;
- SELECT test2arr();
+SELECT test2arr();
+
+
+-- test python -> domain over hstore
+CREATE DOMAIN hstore_foo AS hstore CHECK(VALUE ? 'foo');
+
+CREATE FUNCTION test2dom(fn text) RETURNS hstore_foo
+LANGUAGE plpythonu
+TRANSFORM FOR TYPE hstore
+AS $$
+return {'a': 1, fn: 'boo', 'c': None}
+$$;
+
+SELECT test2dom('foo');
+SELECT test2dom('bar'); -- fail
-- test as part of prepare/execute
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 7aadc5d6ef7..f6450c402c3 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -377,6 +377,7 @@ lookup_type_cache(Oid type_id, int flags)
typentry->typstorage = typtup->typstorage;
typentry->typtype = typtup->typtype;
typentry->typrelid = typtup->typrelid;
+ typentry->typelem = typtup->typelem;
/* If it's a domain, immediately thread it into the domain cache list */
if (typentry->typtype == TYPTYPE_DOMAIN)
@@ -791,6 +792,12 @@ load_typcache_tupdesc(TypeCacheEntry *typentry)
Assert(typentry->tupDesc->tdrefcount > 0);
typentry->tupDesc->tdrefcount++;
+ /*
+ * In future, we could take some pains to not increment the seqno if the
+ * tupdesc didn't really change; but for now it's not worth it.
+ */
+ typentry->tupDescSeqNo++;
+
relation_close(rel, AccessShareLock);
}
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index ea799a8894f..c203dabbd0a 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -40,6 +40,7 @@ typedef struct TypeCacheEntry
char typstorage;
char typtype;
Oid typrelid;
+ Oid typelem;
/*
* Information obtained from opfamily entries
@@ -75,9 +76,11 @@ typedef struct TypeCacheEntry
/*
* Tuple descriptor if it's a composite type (row type). NULL if not
* composite or information hasn't yet been requested. (NOTE: this is a
- * reference-counted tupledesc.)
+ * reference-counted tupledesc.) To simplify caching dependent info,
+ * tupDescSeqNo is incremented each time tupDesc is rebuilt in a session.
*/
TupleDesc tupDesc;
+ int64 tupDescSeqNo;
/*
* Fields computed when TYPECACHE_RANGE_INFO is requested. Zeroes if not
diff --git a/src/pl/plpython/expected/plpython_types.out b/src/pl/plpython/expected/plpython_types.out
index 893de301dda..eda965a9e0d 100644
--- a/src/pl/plpython/expected/plpython_types.out
+++ b/src/pl/plpython/expected/plpython_types.out
@@ -765,6 +765,76 @@ SELECT * FROM test_type_conversion_array_domain_check_violation();
ERROR: value for domain ordered_pair_domain violates check constraint "ordered_pair_domain_check"
CONTEXT: while creating return value
PL/Python function "test_type_conversion_array_domain_check_violation"
+--
+-- Arrays of domains
+--
+CREATE FUNCTION test_read_uint2_array(x uint2[]) RETURNS uint2 AS $$
+plpy.info(x, type(x))
+return x[0]
+$$ LANGUAGE plpythonu;
+select test_read_uint2_array(array[1::uint2]);
+INFO: ([1], <type 'list'>)
+ test_read_uint2_array
+-----------------------
+ 1
+(1 row)
+
+CREATE FUNCTION test_build_uint2_array(x int2) RETURNS uint2[] AS $$
+return [x, x]
+$$ LANGUAGE plpythonu;
+select test_build_uint2_array(1::int2);
+ test_build_uint2_array
+------------------------
+ {1,1}
+(1 row)
+
+select test_build_uint2_array(-1::int2); -- fail
+ERROR: value for domain uint2 violates check constraint "uint2_check"
+CONTEXT: while creating return value
+PL/Python function "test_build_uint2_array"
+--
+-- ideally this would work, but for now it doesn't, because the return value
+-- is [[2,4], [2,4]] which our conversion code thinks should become a 2-D
+-- integer array, not an array of arrays.
+--
+CREATE FUNCTION test_type_conversion_domain_array(x integer[])
+ RETURNS ordered_pair_domain[] AS $$
+return [x, x]
+$$ LANGUAGE plpythonu;
+select test_type_conversion_domain_array(array[2,4]);
+ERROR: return value of function with array return type is not a Python sequence
+CONTEXT: while creating return value
+PL/Python function "test_type_conversion_domain_array"
+select test_type_conversion_domain_array(array[4,2]); -- fail
+ERROR: return value of function with array return type is not a Python sequence
+CONTEXT: while creating return value
+PL/Python function "test_type_conversion_domain_array"
+CREATE FUNCTION test_type_conversion_domain_array2(x ordered_pair_domain)
+ RETURNS integer AS $$
+plpy.info(x, type(x))
+return x[1]
+$$ LANGUAGE plpythonu;
+select test_type_conversion_domain_array2(array[2,4]);
+INFO: ([2, 4], <type 'list'>)
+ test_type_conversion_domain_array2
+------------------------------------
+ 4
+(1 row)
+
+select test_type_conversion_domain_array2(array[4,2]); -- fail
+ERROR: value for domain ordered_pair_domain violates check constraint "ordered_pair_domain_check"
+CREATE FUNCTION test_type_conversion_array_domain_array(x ordered_pair_domain[])
+ RETURNS ordered_pair_domain AS $$
+plpy.info(x, type(x))
+return x[0]
+$$ LANGUAGE plpythonu;
+select test_type_conversion_array_domain_array(array[array[2,4]::ordered_pair_domain]);
+INFO: ([[2, 4]], <type 'list'>)
+ test_type_conversion_array_domain_array
+-----------------------------------------
+ {2,4}
+(1 row)
+
---
--- Composite types
---
@@ -821,6 +891,64 @@ SELECT test_composite_type_input(row(1, 2));
(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
--
CREATE OR REPLACE FUNCTION test_prep_bool_input() RETURNS int
diff --git a/src/pl/plpython/expected/plpython_types_3.out b/src/pl/plpython/expected/plpython_types_3.out
index 2d853bd5731..69f958cbf28 100644
--- a/src/pl/plpython/expected/plpython_types_3.out
+++ b/src/pl/plpython/expected/plpython_types_3.out
@@ -765,6 +765,76 @@ SELECT * FROM test_type_conversion_array_domain_check_violation();
ERROR: value for domain ordered_pair_domain violates check constraint "ordered_pair_domain_check"
CONTEXT: while creating return value
PL/Python function "test_type_conversion_array_domain_check_violation"
+--
+-- Arrays of domains
+--
+CREATE FUNCTION test_read_uint2_array(x uint2[]) RETURNS uint2 AS $$
+plpy.info(x, type(x))
+return x[0]
+$$ LANGUAGE plpythonu;
+select test_read_uint2_array(array[1::uint2]);
+INFO: ([1], <class 'list'>)
+ test_read_uint2_array
+-----------------------
+ 1
+(1 row)
+
+CREATE FUNCTION test_build_uint2_array(x int2) RETURNS uint2[] AS $$
+return [x, x]
+$$ LANGUAGE plpythonu;
+select test_build_uint2_array(1::int2);
+ test_build_uint2_array
+------------------------
+ {1,1}
+(1 row)
+
+select test_build_uint2_array(-1::int2); -- fail
+ERROR: value for domain uint2 violates check constraint "uint2_check"
+CONTEXT: while creating return value
+PL/Python function "test_build_uint2_array"
+--
+-- ideally this would work, but for now it doesn't, because the return value
+-- is [[2,4], [2,4]] which our conversion code thinks should become a 2-D
+-- integer array, not an array of arrays.
+--
+CREATE FUNCTION test_type_conversion_domain_array(x integer[])
+ RETURNS ordered_pair_domain[] AS $$
+return [x, x]
+$$ LANGUAGE plpythonu;
+select test_type_conversion_domain_array(array[2,4]);
+ERROR: return value of function with array return type is not a Python sequence
+CONTEXT: while creating return value
+PL/Python function "test_type_conversion_domain_array"
+select test_type_conversion_domain_array(array[4,2]); -- fail
+ERROR: return value of function with array return type is not a Python sequence
+CONTEXT: while creating return value
+PL/Python function "test_type_conversion_domain_array"
+CREATE FUNCTION test_type_conversion_domain_array2(x ordered_pair_domain)
+ RETURNS integer AS $$
+plpy.info(x, type(x))
+return x[1]
+$$ LANGUAGE plpythonu;
+select test_type_conversion_domain_array2(array[2,4]);
+INFO: ([2, 4], <class 'list'>)
+ test_type_conversion_domain_array2
+------------------------------------
+ 4
+(1 row)
+
+select test_type_conversion_domain_array2(array[4,2]); -- fail
+ERROR: value for domain ordered_pair_domain violates check constraint "ordered_pair_domain_check"
+CREATE FUNCTION test_type_conversion_array_domain_array(x ordered_pair_domain[])
+ RETURNS ordered_pair_domain AS $$
+plpy.info(x, type(x))
+return x[0]
+$$ LANGUAGE plpythonu;
+select test_type_conversion_array_domain_array(array[array[2,4]::ordered_pair_domain]);
+INFO: ([[2, 4]], <class 'list'>)
+ test_type_conversion_array_domain_array
+-----------------------------------------
+ {2,4}
+(1 row)
+
---
--- Composite types
---
@@ -821,6 +891,64 @@ SELECT test_composite_type_input(row(1, 2));
(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
--
CREATE OR REPLACE FUNCTION test_prep_bool_input() RETURNS int
diff --git a/src/pl/plpython/plpy_cursorobject.c b/src/pl/plpython/plpy_cursorobject.c
index 0108471bfe2..10ca786fbc2 100644
--- a/src/pl/plpython/plpy_cursorobject.c
+++ b/src/pl/plpython/plpy_cursorobject.c
@@ -9,6 +9,7 @@
#include <limits.h>
#include "access/xact.h"
+#include "catalog/pg_type.h"
#include "mb/pg_wchar.h"
#include "utils/memutils.h"
@@ -106,6 +107,7 @@ static PyObject *
PLy_cursor_query(const char *query)
{
PLyCursorObject *cursor;
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
volatile MemoryContext oldcontext;
volatile ResourceOwner oldowner;
@@ -116,7 +118,11 @@ PLy_cursor_query(const char *query)
cursor->mcxt = AllocSetContextCreate(TopMemoryContext,
"PL/Python cursor context",
ALLOCSET_DEFAULT_SIZES);
- PLy_typeinfo_init(&cursor->result, cursor->mcxt);
+
+ /* Initialize for converting result tuples to Python */
+ PLy_input_setup_func(&cursor->result, cursor->mcxt,
+ RECORDOID, -1,
+ exec_ctx->curr_proc);
oldcontext = CurrentMemoryContext;
oldowner = CurrentResourceOwner;
@@ -125,7 +131,6 @@ PLy_cursor_query(const char *query)
PG_TRY();
{
- PLyExecutionContext *exec_ctx = PLy_current_execution_context();
SPIPlanPtr plan;
Portal portal;
@@ -166,6 +171,7 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
volatile int nargs;
int i;
PLyPlanObject *plan;
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
volatile MemoryContext oldcontext;
volatile ResourceOwner oldowner;
@@ -208,7 +214,11 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
cursor->mcxt = AllocSetContextCreate(TopMemoryContext,
"PL/Python cursor context",
ALLOCSET_DEFAULT_SIZES);
- PLy_typeinfo_init(&cursor->result, cursor->mcxt);
+
+ /* Initialize for converting result tuples to Python */
+ PLy_input_setup_func(&cursor->result, cursor->mcxt,
+ RECORDOID, -1,
+ exec_ctx->curr_proc);
oldcontext = CurrentMemoryContext;
oldowner = CurrentResourceOwner;
@@ -217,7 +227,6 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
PG_TRY();
{
- PLyExecutionContext *exec_ctx = PLy_current_execution_context();
Portal portal;
char *volatile nulls;
volatile int j;
@@ -229,39 +238,24 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
for (j = 0; j < nargs; j++)
{
+ PLyObToDatum *arg = &plan->args[j];
PyObject *elem;
elem = PySequence_GetItem(args, j);
- if (elem != Py_None)
+ PG_TRY();
{
- PG_TRY();
- {
- plan->values[j] =
- plan->args[j].out.d.func(&(plan->args[j].out.d),
- -1,
- elem,
- false);
- }
- PG_CATCH();
- {
- Py_DECREF(elem);
- PG_RE_THROW();
- }
- PG_END_TRY();
+ bool isnull;
- Py_DECREF(elem);
- nulls[j] = ' ';
+ plan->values[j] = PLy_output_convert(arg, elem, &isnull);
+ nulls[j] = isnull ? 'n' : ' ';
}
- else
+ PG_CATCH();
{
Py_DECREF(elem);
- plan->values[j] =
- InputFunctionCall(&(plan->args[j].out.d.typfunc),
- NULL,
- plan->args[j].out.d.typioparam,
- -1);
- nulls[j] = 'n';
+ PG_RE_THROW();
}
+ PG_END_TRY();
+ Py_DECREF(elem);
}
portal = SPI_cursor_open(NULL, plan->plan, plan->values, nulls,
@@ -281,7 +275,7 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
/* cleanup plan->values array */
for (k = 0; k < nargs; k++)
{
- if (!plan->args[k].out.d.typbyval &&
+ if (!plan->args[k].typbyval &&
(plan->values[k] != PointerGetDatum(NULL)))
{
pfree(DatumGetPointer(plan->values[k]));
@@ -298,7 +292,7 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
for (i = 0; i < nargs; i++)
{
- if (!plan->args[i].out.d.typbyval &&
+ if (!plan->args[i].typbyval &&
(plan->values[i] != PointerGetDatum(NULL)))
{
pfree(DatumGetPointer(plan->values[i]));
@@ -339,6 +333,7 @@ PLy_cursor_iternext(PyObject *self)
{
PLyCursorObject *cursor;
PyObject *ret;
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
volatile MemoryContext oldcontext;
volatile ResourceOwner oldowner;
Portal portal;
@@ -374,11 +369,11 @@ PLy_cursor_iternext(PyObject *self)
}
else
{
- if (cursor->result.is_rowtype != 1)
- PLy_input_tuple_funcs(&cursor->result, SPI_tuptable->tupdesc);
+ PLy_input_setup_tuple(&cursor->result, SPI_tuptable->tupdesc,
+ exec_ctx->curr_proc);
- ret = PLyDict_FromTuple(&cursor->result, SPI_tuptable->vals[0],
- SPI_tuptable->tupdesc);
+ ret = PLy_input_from_tuple(&cursor->result, SPI_tuptable->vals[0],
+ SPI_tuptable->tupdesc);
}
SPI_freetuptable(SPI_tuptable);
@@ -401,6 +396,7 @@ PLy_cursor_fetch(PyObject *self, PyObject *args)
PLyCursorObject *cursor;
int count;
PLyResultObject *ret;
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
volatile MemoryContext oldcontext;
volatile ResourceOwner oldowner;
Portal portal;
@@ -437,9 +433,6 @@ PLy_cursor_fetch(PyObject *self, PyObject *args)
{
SPI_cursor_fetch(portal, true, count);
- if (cursor->result.is_rowtype != 1)
- PLy_input_tuple_funcs(&cursor->result, SPI_tuptable->tupdesc);
-
Py_DECREF(ret->status);
ret->status = PyInt_FromLong(SPI_OK_FETCH);
@@ -465,11 +458,14 @@ PLy_cursor_fetch(PyObject *self, PyObject *args)
Py_DECREF(ret->rows);
ret->rows = PyList_New(SPI_processed);
+ PLy_input_setup_tuple(&cursor->result, SPI_tuptable->tupdesc,
+ exec_ctx->curr_proc);
+
for (i = 0; i < SPI_processed; i++)
{
- PyObject *row = PLyDict_FromTuple(&cursor->result,
- SPI_tuptable->vals[i],
- SPI_tuptable->tupdesc);
+ PyObject *row = PLy_input_from_tuple(&cursor->result,
+ SPI_tuptable->vals[i],
+ SPI_tuptable->tupdesc);
PyList_SetItem(ret->rows, i, row);
}
diff --git a/src/pl/plpython/plpy_cursorobject.h b/src/pl/plpython/plpy_cursorobject.h
index 018b169cbf2..e4d2c0ed253 100644
--- a/src/pl/plpython/plpy_cursorobject.h
+++ b/src/pl/plpython/plpy_cursorobject.h
@@ -12,7 +12,7 @@ typedef struct PLyCursorObject
{
PyObject_HEAD
char *portalname;
- PLyTypeInfo result;
+ PLyDatumToOb result;
bool closed;
MemoryContext mcxt;
} PLyCursorObject;
diff --git a/src/pl/plpython/plpy_exec.c b/src/pl/plpython/plpy_exec.c
index 26f61dd0f37..02d7d2ad5f8 100644
--- a/src/pl/plpython/plpy_exec.c
+++ b/src/pl/plpython/plpy_exec.c
@@ -202,7 +202,7 @@ PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc)
* return value as a special "void datum" rather than NULL (as is the
* case for non-void-returning functions).
*/
- if (proc->result.out.d.typoid == VOIDOID)
+ if (proc->result.typoid == VOIDOID)
{
if (plrv != Py_None)
ereport(ERROR,
@@ -212,48 +212,22 @@ PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc)
fcinfo->isnull = false;
rv = (Datum) 0;
}
- else if (plrv == Py_None)
+ else if (plrv == Py_None &&
+ srfstate && srfstate->iter == NULL)
{
- fcinfo->isnull = true;
-
/*
* In a SETOF function, the iteration-ending null isn't a real
* value; don't pass it through the input function, which might
* complain.
*/
- if (srfstate && srfstate->iter == NULL)
- rv = (Datum) 0;
- else if (proc->result.is_rowtype < 1)
- rv = InputFunctionCall(&proc->result.out.d.typfunc,
- NULL,
- proc->result.out.d.typioparam,
- -1);
- else
- /* Tuple as None */
- rv = (Datum) NULL;
- }
- else if (proc->result.is_rowtype >= 1)
- {
- TupleDesc desc;
-
- /* make sure it's not an unnamed record */
- Assert((proc->result.out.d.typoid == RECORDOID &&
- proc->result.out.d.typmod != -1) ||
- (proc->result.out.d.typoid != RECORDOID &&
- proc->result.out.d.typmod == -1));
-
- desc = lookup_rowtype_tupdesc(proc->result.out.d.typoid,
- proc->result.out.d.typmod);
-
- rv = PLyObject_ToCompositeDatum(&proc->result, desc, plrv, false);
- fcinfo->isnull = (rv == (Datum) NULL);
-
- ReleaseTupleDesc(desc);
+ fcinfo->isnull = true;
+ rv = (Datum) 0;
}
else
{
- fcinfo->isnull = false;
- rv = (proc->result.out.d.func) (&proc->result.out.d, -1, plrv, false);
+ /* Normal conversion of result */
+ rv = PLy_output_convert(&proc->result, plrv,
+ &fcinfo->isnull);
}
}
PG_CATCH();
@@ -328,20 +302,32 @@ PLy_exec_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc)
PyObject *volatile plargs = NULL;
PyObject *volatile plrv = NULL;
TriggerData *tdata;
+ TupleDesc rel_descr;
Assert(CALLED_AS_TRIGGER(fcinfo));
+ tdata = (TriggerData *) fcinfo->context;
/*
- * Input/output conversion for trigger tuples. Use the result TypeInfo
- * variable to store the tuple conversion info. We do this over again on
- * each call to cover the possibility that the relation's tupdesc changed
- * since the trigger was last called. PLy_input_tuple_funcs and
- * PLy_output_tuple_funcs are responsible for not doing repetitive work.
+ * Input/output conversion for trigger tuples. We use the result and
+ * result_in fields to store the tuple conversion info. We do this over
+ * again on each call to cover the possibility that the relation's tupdesc
+ * changed since the trigger was last called. The PLy_xxx_setup_func
+ * calls should only happen once, but PLy_input_setup_tuple and
+ * PLy_output_setup_tuple are responsible for not doing repetitive work.
*/
- tdata = (TriggerData *) fcinfo->context;
-
- PLy_input_tuple_funcs(&(proc->result), tdata->tg_relation->rd_att);
- PLy_output_tuple_funcs(&(proc->result), tdata->tg_relation->rd_att);
+ rel_descr = RelationGetDescr(tdata->tg_relation);
+ if (proc->result.typoid != rel_descr->tdtypeid)
+ PLy_output_setup_func(&proc->result, proc->mcxt,
+ rel_descr->tdtypeid,
+ rel_descr->tdtypmod,
+ proc);
+ if (proc->result_in.typoid != rel_descr->tdtypeid)
+ PLy_input_setup_func(&proc->result_in, proc->mcxt,
+ rel_descr->tdtypeid,
+ rel_descr->tdtypmod,
+ proc);
+ PLy_output_setup_tuple(&proc->result, rel_descr, proc);
+ PLy_input_setup_tuple(&proc->result_in, rel_descr, proc);
PG_TRY();
{
@@ -436,46 +422,12 @@ PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc)
args = PyList_New(proc->nargs);
for (i = 0; i < proc->nargs; i++)
{
- if (proc->args[i].is_rowtype > 0)
- {
- if (fcinfo->argnull[i])
- arg = NULL;
- else
- {
- HeapTupleHeader td;
- Oid tupType;
- int32 tupTypmod;
- TupleDesc tupdesc;
- HeapTupleData tmptup;
-
- 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);
- }
- }
+ PLyDatumToOb *arginfo = &proc->args[i];
+
+ if (fcinfo->argnull[i])
+ arg = NULL;
else
- {
- if (fcinfo->argnull[i])
- arg = NULL;
- else
- {
- arg = (proc->args[i].in.d.func) (&(proc->args[i].in.d),
- fcinfo->arg[i]);
- }
- }
+ arg = PLy_input_convert(arginfo, fcinfo->arg[i]);
if (arg == NULL)
{
@@ -493,7 +445,7 @@ PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc)
}
/* Set up output conversion for functions returning RECORD */
- if (proc->result.out.d.typoid == RECORDOID)
+ if (proc->result.typoid == RECORDOID)
{
TupleDesc desc;
@@ -504,7 +456,7 @@ PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc)
"that cannot accept type record")));
/* cache the output conversion functions */
- PLy_output_record_funcs(&(proc->result), desc);
+ PLy_output_setup_record(&proc->result, desc, proc);
}
}
PG_CATCH();
@@ -723,6 +675,7 @@ static PyObject *
PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *rv)
{
TriggerData *tdata = (TriggerData *) fcinfo->context;
+ TupleDesc rel_descr = RelationGetDescr(tdata->tg_relation);
PyObject *pltname,
*pltevent,
*pltwhen,
@@ -790,8 +743,9 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r
pltevent = PyString_FromString("INSERT");
PyDict_SetItemString(pltdata, "old", Py_None);
- pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple,
- tdata->tg_relation->rd_att);
+ pytnew = PLy_input_from_tuple(&proc->result_in,
+ tdata->tg_trigtuple,
+ rel_descr);
PyDict_SetItemString(pltdata, "new", pytnew);
Py_DECREF(pytnew);
*rv = tdata->tg_trigtuple;
@@ -801,8 +755,9 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r
pltevent = PyString_FromString("DELETE");
PyDict_SetItemString(pltdata, "new", Py_None);
- pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple,
- tdata->tg_relation->rd_att);
+ pytold = PLy_input_from_tuple(&proc->result_in,
+ tdata->tg_trigtuple,
+ rel_descr);
PyDict_SetItemString(pltdata, "old", pytold);
Py_DECREF(pytold);
*rv = tdata->tg_trigtuple;
@@ -811,12 +766,14 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r
{
pltevent = PyString_FromString("UPDATE");
- pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_newtuple,
- tdata->tg_relation->rd_att);
+ pytnew = PLy_input_from_tuple(&proc->result_in,
+ tdata->tg_newtuple,
+ rel_descr);
PyDict_SetItemString(pltdata, "new", pytnew);
Py_DECREF(pytnew);
- pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple,
- tdata->tg_relation->rd_att);
+ pytold = PLy_input_from_tuple(&proc->result_in,
+ tdata->tg_trigtuple,
+ rel_descr);
PyDict_SetItemString(pltdata, "old", pytold);
Py_DECREF(pytold);
*rv = tdata->tg_newtuple;
@@ -897,6 +854,9 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r
return pltdata;
}
+/*
+ * Apply changes requested by a MODIFY return from a trigger function.
+ */
static HeapTuple
PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata,
HeapTuple otup)
@@ -938,7 +898,7 @@ PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata,
plkeys = PyDict_Keys(plntup);
nkeys = PyList_Size(plkeys);
- tupdesc = tdata->tg_relation->rd_att;
+ tupdesc = RelationGetDescr(tdata->tg_relation);
modvalues = (Datum *) palloc0(tupdesc->natts * sizeof(Datum));
modnulls = (bool *) palloc0(tupdesc->natts * sizeof(bool));
@@ -950,7 +910,6 @@ PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata,
char *plattstr;
int attn;
PLyObToDatum *att;
- Form_pg_attribute attr;
platt = PyList_GetItem(plkeys, i);
if (PyString_Check(platt))
@@ -975,7 +934,6 @@ PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot set system attribute \"%s\"",
plattstr)));
- att = &proc->result.out.r.atts[attn - 1];
plval = PyDict_GetItem(plntup, platt);
if (plval == NULL)
@@ -983,25 +941,12 @@ PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata,
Py_INCREF(plval);
- attr = TupleDescAttr(tupdesc, attn - 1);
- if (plval != Py_None)
- {
- modvalues[attn - 1] =
- (att->func) (att,
- attr->atttypmod,
- plval,
- false);
- modnulls[attn - 1] = false;
- }
- else
- {
- modvalues[attn - 1] =
- InputFunctionCall(&att->typfunc,
- NULL,
- att->typioparam,
- attr->atttypmod);
- modnulls[attn - 1] = true;
- }
+ /* We assume proc->result is set up to convert tuples properly */
+ att = &proc->result.u.tuple.atts[attn - 1];
+
+ modvalues[attn - 1] = PLy_output_convert(att,
+ plval,
+ &modnulls[attn - 1]);
modrepls[attn - 1] = true;
Py_DECREF(plval);
diff --git a/src/pl/plpython/plpy_main.c b/src/pl/plpython/plpy_main.c
index 7df50c09c82..29db90e4489 100644
--- a/src/pl/plpython/plpy_main.c
+++ b/src/pl/plpython/plpy_main.c
@@ -318,7 +318,12 @@ plpython_inline_handler(PG_FUNCTION_ARGS)
ALLOCSET_DEFAULT_SIZES);
proc.pyname = MemoryContextStrdup(proc.mcxt, "__plpython_inline_block");
proc.langid = codeblock->langOid;
- proc.result.out.d.typoid = VOIDOID;
+
+ /*
+ * This is currently sufficient to get PLy_exec_function to work, but
+ * someday we might need to be honest and use PLy_output_setup_func.
+ */
+ proc.result.typoid = VOIDOID;
/*
* Push execution context onto stack. It is important that this get
diff --git a/src/pl/plpython/plpy_planobject.h b/src/pl/plpython/plpy_planobject.h
index 5adc957053f..729effb1631 100644
--- a/src/pl/plpython/plpy_planobject.h
+++ b/src/pl/plpython/plpy_planobject.h
@@ -16,7 +16,7 @@ typedef struct PLyPlanObject
int nargs;
Oid *types;
Datum *values;
- PLyTypeInfo *args;
+ PLyObToDatum *args;
MemoryContext mcxt;
} PLyPlanObject;
diff --git a/src/pl/plpython/plpy_procedure.c b/src/pl/plpython/plpy_procedure.c
index 26acc88b270..58d69882025 100644
--- a/src/pl/plpython/plpy_procedure.c
+++ b/src/pl/plpython/plpy_procedure.c
@@ -15,6 +15,7 @@
#include "utils/builtins.h"
#include "utils/hsearch.h"
#include "utils/inval.h"
+#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/syscache.h"
@@ -29,7 +30,6 @@
static HTAB *PLy_procedure_cache = NULL;
static PLyProcedure *PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger);
-static bool PLy_procedure_argument_valid(PLyTypeInfo *arg);
static bool PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup);
static char *PLy_procedure_munge_source(const char *name, const char *src);
@@ -165,6 +165,7 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
*ptr = '_';
}
+ /* Create long-lived context that all procedure info will live in */
cxt = AllocSetContextCreate(TopMemoryContext,
procName,
ALLOCSET_DEFAULT_SIZES);
@@ -188,11 +189,9 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
proc->fn_tid = procTup->t_self;
proc->fn_readonly = (procStruct->provolatile != PROVOLATILE_VOLATILE);
proc->is_setof = procStruct->proretset;
- PLy_typeinfo_init(&proc->result, proc->mcxt);
proc->src = NULL;
proc->argnames = NULL;
- for (i = 0; i < FUNC_MAX_ARGS; i++)
- PLy_typeinfo_init(&proc->args[i], proc->mcxt);
+ proc->args = NULL;
proc->nargs = 0;
proc->langid = procStruct->prolang;
protrftypes_datum = SysCacheGetAttr(PROCOID, procTup,
@@ -211,50 +210,48 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
*/
if (!is_trigger)
{
+ Oid rettype = procStruct->prorettype;
HeapTuple rvTypeTup;
Form_pg_type rvTypeStruct;
- rvTypeTup = SearchSysCache1(TYPEOID,
- ObjectIdGetDatum(procStruct->prorettype));
+ rvTypeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(rettype));
if (!HeapTupleIsValid(rvTypeTup))
- elog(ERROR, "cache lookup failed for type %u",
- procStruct->prorettype);
+ elog(ERROR, "cache lookup failed for type %u", rettype);
rvTypeStruct = (Form_pg_type) GETSTRUCT(rvTypeTup);
/* Disallow pseudotype result, except for void or record */
if (rvTypeStruct->typtype == TYPTYPE_PSEUDO)
{
- if (procStruct->prorettype == TRIGGEROID)
+ if (rettype == VOIDOID ||
+ rettype == RECORDOID)
+ /* okay */ ;
+ else if (rettype == TRIGGEROID || rettype == EVTTRIGGEROID)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("trigger functions can only be called as triggers")));
- else if (procStruct->prorettype != VOIDOID &&
- procStruct->prorettype != RECORDOID)
+ else
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("PL/Python functions cannot return type %s",
- format_type_be(procStruct->prorettype))));
+ format_type_be(rettype))));
}
- if (rvTypeStruct->typtype == TYPTYPE_COMPOSITE ||
- procStruct->prorettype == RECORDOID)
- {
- /*
- * Tuple: set up later, during first call to
- * PLy_function_handler
- */
- proc->result.out.d.typoid = procStruct->prorettype;
- proc->result.out.d.typmod = -1;
- proc->result.is_rowtype = 2;
- }
- else
- {
- /* do the real work */
- PLy_output_datum_func(&proc->result, rvTypeTup, proc->langid, proc->trftypes);
- }
+ /* set up output function for procedure result */
+ PLy_output_setup_func(&proc->result, proc->mcxt,
+ rettype, -1, proc);
ReleaseSysCache(rvTypeTup);
}
+ else
+ {
+ /*
+ * In a trigger function, we use proc->result and proc->result_in
+ * for converting tuples, but we don't yet have enough info to set
+ * them up. PLy_exec_trigger will deal with it.
+ */
+ proc->result.typoid = InvalidOid;
+ proc->result_in.typoid = InvalidOid;
+ }
/*
* Now get information required for input conversion of the
@@ -287,7 +284,10 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
}
}
+ /* Allocate arrays for per-input-argument data */
proc->argnames = (char **) palloc0(sizeof(char *) * proc->nargs);
+ proc->args = (PLyDatumToOb *) palloc0(sizeof(PLyDatumToOb) * proc->nargs);
+
for (i = pos = 0; i < total; i++)
{
HeapTuple argTypeTup;
@@ -306,28 +306,17 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
elog(ERROR, "cache lookup failed for type %u", types[i]);
argTypeStruct = (Form_pg_type) GETSTRUCT(argTypeTup);
- /* check argument type is OK, set up I/O function info */
- switch (argTypeStruct->typtype)
- {
- case TYPTYPE_PSEUDO:
- /* Disallow pseudotype argument */
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("PL/Python functions cannot accept type %s",
- format_type_be(types[i]))));
- break;
- case TYPTYPE_COMPOSITE:
- /* we'll set IO funcs at first call */
- proc->args[pos].is_rowtype = 2;
- break;
- default:
- PLy_input_datum_func(&(proc->args[pos]),
- types[i],
- argTypeTup,
- proc->langid,
- proc->trftypes);
- break;
- }
+ /* disallow pseudotype arguments */
+ if (argTypeStruct->typtype == TYPTYPE_PSEUDO)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("PL/Python functions cannot accept type %s",
+ format_type_be(types[i]))));
+
+ /* set up I/O function info */
+ PLy_input_setup_func(&proc->args[pos], proc->mcxt,
+ types[i], -1, /* typmod not known */
+ proc);
/* get argument name */
proc->argnames[pos] = names ? pstrdup(names[i]) : NULL;
@@ -425,53 +414,11 @@ PLy_procedure_delete(PLyProcedure *proc)
}
/*
- * Check if our cached information about a datatype is still valid
- */
-static bool
-PLy_procedure_argument_valid(PLyTypeInfo *arg)
-{
- HeapTuple relTup;
- bool valid;
-
- /* Nothing to cache unless type is composite */
- if (arg->is_rowtype != 1)
- return true;
-
- /*
- * Zero typ_relid means that we got called on an output argument of a
- * function returning an unnamed record type; the info for it can't
- * change.
- */
- if (!OidIsValid(arg->typ_relid))
- return true;
-
- /* Else we should have some cached data */
- Assert(TransactionIdIsValid(arg->typrel_xmin));
- Assert(ItemPointerIsValid(&arg->typrel_tid));
-
- /* Get the pg_class tuple for the data type */
- relTup = SearchSysCache1(RELOID, ObjectIdGetDatum(arg->typ_relid));
- if (!HeapTupleIsValid(relTup))
- elog(ERROR, "cache lookup failed for relation %u", arg->typ_relid);
-
- /* If it has changed, the cached data is not valid */
- valid = (arg->typrel_xmin == HeapTupleHeaderGetRawXmin(relTup->t_data) &&
- ItemPointerEquals(&arg->typrel_tid, &relTup->t_self));
-
- ReleaseSysCache(relTup);
-
- return valid;
-}
-
-/*
* Decide whether a cached PLyProcedure struct is still valid
*/
static bool
PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup)
{
- int i;
- bool valid;
-
if (proc == NULL)
return false;
@@ -480,22 +427,7 @@ PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup)
ItemPointerEquals(&proc->fn_tid, &procTup->t_self)))
return false;
- /* Else check the input argument datatypes */
- valid = true;
- for (i = 0; i < proc->nargs; i++)
- {
- valid = PLy_procedure_argument_valid(&proc->args[i]);
-
- /* Short-circuit on first changed argument */
- if (!valid)
- break;
- }
-
- /* if the output type is composite, it might have changed */
- if (valid)
- valid = PLy_procedure_argument_valid(&proc->result);
-
- return valid;
+ return true;
}
static char *
diff --git a/src/pl/plpython/plpy_procedure.h b/src/pl/plpython/plpy_procedure.h
index d05944fc398..cd1b87fdc3c 100644
--- a/src/pl/plpython/plpy_procedure.h
+++ b/src/pl/plpython/plpy_procedure.h
@@ -31,12 +31,12 @@ typedef struct PLyProcedure
ItemPointerData fn_tid;
bool fn_readonly;
bool is_setof; /* true, if procedure returns result set */
- PLyTypeInfo result; /* also used to store info for trigger tuple
- * type */
+ PLyObToDatum result; /* Function result output conversion info */
+ PLyDatumToOb result_in; /* For converting input tuples in a trigger */
char *src; /* textual procedure code, after mangling */
char **argnames; /* Argument names */
- PLyTypeInfo args[FUNC_MAX_ARGS];
- int nargs;
+ PLyDatumToOb *args; /* Argument input conversion info */
+ int nargs; /* Number of elements in above arrays */
Oid langid; /* OID of plpython pg_language entry */
List *trftypes; /* OID list of transform types */
PyObject *code; /* compiled procedure code */
diff --git a/src/pl/plpython/plpy_spi.c b/src/pl/plpython/plpy_spi.c
index 955769c5e32..69eb6b39f67 100644
--- a/src/pl/plpython/plpy_spi.c
+++ b/src/pl/plpython/plpy_spi.c
@@ -46,6 +46,7 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
PyObject *list = NULL;
PyObject *volatile optr = NULL;
char *query;
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
volatile MemoryContext oldcontext;
volatile ResourceOwner oldowner;
volatile int nargs;
@@ -71,9 +72,9 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
nargs = list ? PySequence_Length(list) : 0;
plan->nargs = nargs;
- plan->types = nargs ? palloc(sizeof(Oid) * nargs) : NULL;
- plan->values = nargs ? palloc(sizeof(Datum) * nargs) : NULL;
- plan->args = nargs ? palloc(sizeof(PLyTypeInfo) * nargs) : NULL;
+ plan->types = nargs ? palloc0(sizeof(Oid) * nargs) : NULL;
+ plan->values = nargs ? palloc0(sizeof(Datum) * nargs) : NULL;
+ plan->args = nargs ? palloc0(sizeof(PLyObToDatum) * nargs) : NULL;
MemoryContextSwitchTo(oldcontext);
@@ -85,22 +86,10 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
PG_TRY();
{
int i;
- PLyExecutionContext *exec_ctx = PLy_current_execution_context();
-
- /*
- * the other loop might throw an exception, if PLyTypeInfo member
- * isn't properly initialized the Py_DECREF(plan) will go boom
- */
- for (i = 0; i < nargs; i++)
- {
- PLy_typeinfo_init(&plan->args[i], plan->mcxt);
- plan->values[i] = PointerGetDatum(NULL);
- }
for (i = 0; i < nargs; i++)
{
char *sptr;
- HeapTuple typeTup;
Oid typeId;
int32 typmod;
@@ -124,11 +113,6 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
parseTypeString(sptr, &typeId, &typmod, false);
- typeTup = SearchSysCache1(TYPEOID,
- ObjectIdGetDatum(typeId));
- if (!HeapTupleIsValid(typeTup))
- elog(ERROR, "cache lookup failed for type %u", typeId);
-
Py_DECREF(optr);
/*
@@ -138,8 +122,9 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
optr = NULL;
plan->types[i] = typeId;
- PLy_output_datum_func(&plan->args[i], typeTup, exec_ctx->curr_proc->langid, exec_ctx->curr_proc->trftypes);
- ReleaseSysCache(typeTup);
+ PLy_output_setup_func(&plan->args[i], plan->mcxt,
+ typeId, typmod,
+ exec_ctx->curr_proc);
}
pg_verifymbstr(query, strlen(query), false);
@@ -253,39 +238,24 @@ PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
for (j = 0; j < nargs; j++)
{
+ PLyObToDatum *arg = &plan->args[j];
PyObject *elem;
elem = PySequence_GetItem(list, j);
- if (elem != Py_None)
+ PG_TRY();
{
- PG_TRY();
- {
- plan->values[j] =
- plan->args[j].out.d.func(&(plan->args[j].out.d),
- -1,
- elem,
- false);
- }
- PG_CATCH();
- {
- Py_DECREF(elem);
- PG_RE_THROW();
- }
- PG_END_TRY();
+ bool isnull;
- Py_DECREF(elem);
- nulls[j] = ' ';
+ plan->values[j] = PLy_output_convert(arg, elem, &isnull);
+ nulls[j] = isnull ? 'n' : ' ';
}
- else
+ PG_CATCH();
{
Py_DECREF(elem);
- plan->values[j] =
- InputFunctionCall(&(plan->args[j].out.d.typfunc),
- NULL,
- plan->args[j].out.d.typioparam,
- -1);
- nulls[j] = 'n';
+ PG_RE_THROW();
}
+ PG_END_TRY();
+ Py_DECREF(elem);
}
rv = SPI_execute_plan(plan->plan, plan->values, nulls,
@@ -306,7 +276,7 @@ PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
*/
for (k = 0; k < nargs; k++)
{
- if (!plan->args[k].out.d.typbyval &&
+ if (!plan->args[k].typbyval &&
(plan->values[k] != PointerGetDatum(NULL)))
{
pfree(DatumGetPointer(plan->values[k]));
@@ -321,7 +291,7 @@ PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
for (i = 0; i < nargs; i++)
{
- if (!plan->args[i].out.d.typbyval &&
+ if (!plan->args[i].typbyval &&
(plan->values[i] != PointerGetDatum(NULL)))
{
pfree(DatumGetPointer(plan->values[i]));
@@ -386,6 +356,7 @@ static PyObject *
PLy_spi_execute_fetch_result(SPITupleTable *tuptable, uint64 rows, int status)
{
PLyResultObject *result;
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
volatile MemoryContext oldcontext;
result = (PLyResultObject *) PLy_result_new();
@@ -401,7 +372,7 @@ PLy_spi_execute_fetch_result(SPITupleTable *tuptable, uint64 rows, int status)
}
else if (status > 0 && tuptable != NULL)
{
- PLyTypeInfo args;
+ PLyDatumToOb ininfo;
MemoryContext cxt;
Py_DECREF(result->nrows);
@@ -412,7 +383,10 @@ PLy_spi_execute_fetch_result(SPITupleTable *tuptable, uint64 rows, int status)
cxt = AllocSetContextCreate(CurrentMemoryContext,
"PL/Python temp context",
ALLOCSET_DEFAULT_SIZES);
- PLy_typeinfo_init(&args, cxt);
+
+ /* Initialize for converting result tuples to Python */
+ PLy_input_setup_func(&ininfo, cxt, RECORDOID, -1,
+ exec_ctx->curr_proc);
oldcontext = CurrentMemoryContext;
PG_TRY();
@@ -436,12 +410,14 @@ PLy_spi_execute_fetch_result(SPITupleTable *tuptable, uint64 rows, int status)
Py_DECREF(result->rows);
result->rows = PyList_New(rows);
- PLy_input_tuple_funcs(&args, tuptable->tupdesc);
+ PLy_input_setup_tuple(&ininfo, tuptable->tupdesc,
+ exec_ctx->curr_proc);
+
for (i = 0; i < rows; i++)
{
- PyObject *row = PLyDict_FromTuple(&args,
- tuptable->vals[i],
- tuptable->tupdesc);
+ PyObject *row = PLy_input_from_tuple(&ininfo,
+ tuptable->vals[i],
+ tuptable->tupdesc);
PyList_SetItem(result->rows, i, row);
}
diff --git a/src/pl/plpython/plpy_typeio.c b/src/pl/plpython/plpy_typeio.c
index e4af8cc9ef0..ce1527072e4 100644
--- a/src/pl/plpython/plpy_typeio.c
+++ b/src/pl/plpython/plpy_typeio.c
@@ -7,19 +7,15 @@
#include "postgres.h"
#include "access/htup_details.h"
-#include "access/transam.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "mb/pg_wchar.h"
-#include "parser/parse_type.h"
+#include "miscadmin.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
-#include "utils/numeric.h"
-#include "utils/syscache.h"
-#include "utils/typcache.h"
#include "plpython.h"
@@ -29,10 +25,6 @@
#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 */
static PyObject *PLyBool_FromBool(PLyDatumToOb *arg, Datum d);
static PyObject *PLyFloat_FromFloat4(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_FromOid(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 *PLyList_FromArray(PLyDatumToOb *arg, Datum d);
static PyObject *PLyList_FromArray_recurse(PLyDatumToOb *elm, int *dims, int ndim, int dim,
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 */
-static Datum PLyObject_ToBool(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray);
-static Datum PLyObject_ToBytea(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray);
-static Datum PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray);
-static Datum PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray);
-static Datum PLyObject_ToTransform(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray);
-static Datum PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray);
+static Datum PLyObject_ToBool(PLyObToDatum *arg, PyObject *plrv,
+ bool *isnull, bool inarray);
+static Datum PLyObject_ToBytea(PLyObToDatum *arg, PyObject *plrv,
+ bool *isnull, bool inarray);
+static Datum PLyObject_ToComposite(PLyObToDatum *arg, PyObject *plrv,
+ 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,
int *dims, int ndim, int dim,
Datum *elems, bool *nulls, int *currelem);
-/* conversion from Python objects to composite Datums (used by triggers and SRFs) */
-static Datum PLyString_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *string, bool inarray);
-static Datum PLyMapping_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping);
-static Datum PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence);
-static Datum PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object, bool inarray);
+/* conversion from Python objects to composite Datums */
+static Datum PLyString_ToComposite(PLyObToDatum *arg, PyObject *string, bool inarray);
+static Datum PLyMapping_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *mapping);
+static Datum PLySequence_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *sequence);
+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
* 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)
- elog(ERROR, "PLyTypeInfo struct is initialized for Tuple");
- arg->is_rowtype = 0;
- PLy_input_datum_func2(&(arg->in.d), arg->mcxt, typeOid, typeTup, langid, trftypes);
+ PyObject *result;
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
+ MemoryContext scratch_context = PLy_get_scratch_context(exec_ctx);
+ 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)
- elog(ERROR, "PLyTypeInfo struct is initialized for a Tuple");
- arg->is_rowtype = 0;
- PLy_output_datum_func2(&(arg->out.d), arg->mcxt, typeTup, langid, trftypes);
+ /* at outer level, we are not considering an array element */
+ return arg->func(arg, val, isnull, false);
}
-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();
- 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)
- elog(ERROR, "PLyTypeInfo struct is initialized for a Datum");
- arg->is_rowtype = 1;
+ oldcontext = MemoryContextSwitchTo(scratch_context);
- if (arg->in.r.natts != desc->natts)
- {
- 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));
- }
+ dict = PLyDict_FromTuple(arg, tuple, desc);
- /* Can this be an unnamed tuple? If not, then an Assert would be enough */
- if (desc->tdtypmod != -1)
- elog(ERROR, "received unnamed record type as input");
+ MemoryContextSwitchTo(oldcontext);
- Assert(OidIsValid(desc->tdtypeid));
+ return dict;
+}
- /*
- * RECORDOID means we got called to create input functions for a tuple
- * fetched by plpy.execute or for an anonymous record type
- */
- if (desc->tdtypeid != RECORDOID)
- {
- HeapTuple relTup;
+/*
+ * Initialize, or re-initialize, per-column input info for a composite type.
+ *
+ * This is separate from PLy_input_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
+PLy_input_setup_tuple(PLyDatumToOb *arg, TupleDesc desc, PLyProcedure *proc)
+{
+ int i;
- /* Get the pg_class tuple corresponding to the type of the input */
- arg->typ_relid = typeidTypeRelid(desc->tdtypeid);
- relTup = SearchSysCache1(RELOID, ObjectIdGetDatum(arg->typ_relid));
- if (!HeapTupleIsValid(relTup))
- elog(ERROR, "cache lookup failed for relation %u", arg->typ_relid);
+ /* We should be working on a previously-set-up struct */
+ Assert(arg->func == PLyDict_FromComposite);
- /* 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;
+ /* 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;
- 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++)
{
- HeapTuple typeTup;
Form_pg_attribute attr = TupleDescAttr(desc, i);
+ PLyDatumToOb *att = &arg->u.tuple.atts[i];
if (attr->attisdropped)
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 */
- typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(attr->atttypid));
- if (!HeapTupleIsValid(typeTup))
- elog(ERROR, "cache lookup failed for type %u",
- 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);
+ PLy_input_setup_func(att, arg->mcxt,
+ attr->atttypid, attr->atttypmod,
+ proc);
}
-
- 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
-PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
+PLy_output_setup_tuple(PLyObToDatum *arg, TupleDesc desc, PLyProcedure *proc)
{
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)
- {
- 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));
- }
+ /* We should be working on a previously-set-up struct */
+ Assert(arg->func == PLyObject_ToComposite);
- 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;
- /*
- * RECORDOID means we got called to create output functions for an
- * anonymous record type
- */
- if (desc->tdtypeid != RECORDOID)
+ /* (Re)allocate atts array as needed */
+ if (arg->u.tuple.natts != desc->natts)
{
- HeapTuple relTup;
-
- /* Get the pg_class tuple corresponding to the type of the output */
- arg->typ_relid = typeidTypeRelid(desc->tdtypeid);
- 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 */
- arg->typrel_xmin = HeapTupleHeaderGetRawXmin(relTup->t_data);
- arg->typrel_tid = relTup->t_self;
-
- ReleaseSysCache(relTup);
+ if (arg->u.tuple.atts)
+ pfree(arg->u.tuple.atts);
+ arg->u.tuple.natts = desc->natts;
+ arg->u.tuple.atts = (PLyObToDatum *)
+ MemoryContextAllocZero(arg->mcxt,
+ desc->natts * sizeof(PLyObToDatum));
}
+ /* Fill the atts entries, except for dropped columns */
for (i = 0; i < desc->natts; i++)
{
- HeapTuple typeTup;
Form_pg_attribute attr = TupleDescAttr(desc, i);
+ PLyObToDatum *att = &arg->u.tuple.atts[i];
if (attr->attisdropped)
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 */
- typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(attr->atttypid));
- if (!HeapTupleIsValid(typeTup))
- elog(ERROR, "cache lookup failed for type %u",
- 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);
+ PLy_output_setup_func(att, arg->mcxt,
+ attr->atttypid, attr->atttypmod,
+ proc);
}
-
- MemoryContextSwitchTo(oldcxt);
}
+/*
+ * Set up output info for a PL/Python function returning record.
+ *
+ * Note: the given tupdesc is not necessarily long-lived.
+ */
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
- * if the record descriptor has not changed
+ * Bless the record type if not already done. We'd have to do this anyway
+ * 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);
- /* 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
- * unless the output record description changes
+ * Update arg->typmod, and clear the recdesc link if it's changed. The
+ * 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 *
-PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc)
+void
+PLy_output_setup_func(PLyObToDatum *arg, MemoryContext arg_mcxt,
+ Oid typeOid, int32 typmod,
+ PLyProcedure *proc)
{
- PyObject *volatile dict;
- PLyExecutionContext *exec_ctx = PLy_current_execution_context();
- MemoryContext scratch_context = PLy_get_scratch_context(exec_ctx);
- MemoryContext oldcontext = CurrentMemoryContext;
+ TypeCacheEntry *typentry;
+ char typtype;
+ Oid trfuncid;
+ Oid typinput;
- if (info->is_rowtype != 1)
- elog(ERROR, "PLyTypeInfo structure describes a datum");
+ /* Since this is recursive, it could theoretically be driven to overflow */
+ check_stack_depth();
- dict = PyDict_New();
- if (dict == NULL)
- PLy_elog(ERROR, "could not create new dictionary");
+ arg->typoid = typeOid;
+ arg->typmod = typmod;
+ 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;
-
- /*
- * Do the work in the scratch context to avoid leaking memory from the
- * datatype output function calls.
- */
- 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);
+ typentry = lookup_type_cache(typeOid, TYPECACHE_DOMAIN_BASE_INFO);
+ typtype = typentry->typtype;
+ arg->typbyval = typentry->typbyval;
+ arg->typlen = typentry->typlen;
+ arg->typalign = typentry->typalign;
}
- PG_CATCH();
+ else
{
- MemoryContextSwitchTo(oldcontext);
- Py_DECREF(dict);
- PG_RE_THROW();
+ typentry = NULL;
+ typtype = TYPTYPE_COMPOSITE;
+ /* 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
- * datums.
+ * 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 ((funcid = get_transform_tosql(base_type, langid, trftypes)))
+ if (typtype == TYPTYPE_DOMAIN)
+ {
+ /* 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;
- 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;
+ /* 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
- switch (base_type)
+ {
+ /* Scalar type, but we have a couple of special cases */
+ switch (typeOid)
{
case BOOLOID:
arg->func = PLyObject_ToBool;
@@ -406,66 +402,111 @@ PLy_output_datum_func2(PLyObToDatum *arg, MemoryContext arg_mcxt, HeapTuple type
arg->func = PLyObject_ToBytea;
break;
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;
}
-
- 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);
- Oid element_type;
- Oid base_type;
- Oid funcid;
- MemoryContext oldcxt;
-
- oldcxt = MemoryContextSwitchTo(arg_mcxt);
+ TypeCacheEntry *typentry;
+ char typtype;
+ Oid trfuncid;
+ Oid typoutput;
+ bool typisvarlena;
- /* Get the type's conversion information */
- fmgr_info_cxt(typeStruct->typoutput, &arg->typfunc, arg_mcxt);
- arg->typoid = HeapTupleGetOid(typeTup);
- arg->typmod = -1;
- arg->typioparam = getTypeIOParam(typeTup);
- arg->typbyval = typeStruct->typbyval;
- arg->typlen = typeStruct->typlen;
- arg->typalign = typeStruct->typalign;
+ /* Since this is recursive, it could theoretically be driven to overflow */
+ check_stack_depth();
- /* 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;
- 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
- switch (base_type)
+ {
+ /* Scalar type, but we have a couple of special cases */
+ switch (typeOid)
{
case BOOLOID:
arg->func = PLyBool_FromBool;
@@ -495,30 +536,19 @@ PLy_input_datum_func2(PLyDatumToOb *arg, MemoryContext arg_mcxt, Oid typeOid, He
arg->func = PLyBytes_FromBytea;
break;
default:
- arg->func = PLyString_FromDatum;
+ arg->func = PLyString_FromScalar;
+ getTypeOutputInfo(typeOid, &typoutput, &typisvarlena);
+ fmgr_info_cxt(typoutput, &arg->u.scalar.typfunc, arg_mcxt);
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 *
PLyBool_FromBool(PLyDatumToOb *arg, Datum d)
{
@@ -611,27 +641,40 @@ PLyBytes_FromBytea(PLyDatumToOb *arg, Datum d)
return PyBytes_FromStringAndSize(str, size);
}
+
+/*
+ * Generic input conversion using a SQL type's output function.
+ */
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);
pfree(x);
return r;
}
+/*
+ * Convert using a from-SQL transform function.
+ */
static PyObject *
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 *
PLyList_FromArray(PLyDatumToOb *arg, Datum d)
{
ArrayType *array = DatumGetArrayTypeP(d);
- PLyDatumToOb *elm = arg->elm;
+ PLyDatumToOb *elm = arg->u.array.elm;
int ndim;
int *dims;
char *dataptr;
@@ -737,23 +780,110 @@ PLyList_FromArray_recurse(PLyDatumToOb *elm, int *dims, int ndim, int dim,
}
/*
+ * 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
* through the generic conversion function, because Python attaches a
* Boolean value to everything, more things than the PostgreSQL bool
* type can parse.
*/
static Datum
-PLyObject_ToBool(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray)
+PLyObject_ToBool(PLyObToDatum *arg, PyObject *plrv,
+ bool *isnull, bool inarray)
{
- Datum rv;
-
- Assert(plrv != Py_None);
- rv = BoolGetDatum(PyObject_IsTrue(plrv));
-
- if (get_typtype(arg->typoid) == TYPTYPE_DOMAIN)
- domain_check(rv, false, arg->typoid, &arg->typfunc.fn_extra, arg->typfunc.fn_mcxt);
-
- return rv;
+ if (plrv == Py_None)
+ {
+ *isnull = true;
+ return (Datum) 0;
+ }
+ *isnull = false;
+ return BoolGetDatum(PyObject_IsTrue(plrv));
}
/*
@@ -762,12 +892,18 @@ PLyObject_ToBool(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray)
* with embedded nulls. And it's faster this way.
*/
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;
Datum rv;
- Assert(plrv != Py_None);
+ if (plrv == Py_None)
+ {
+ *isnull = true;
+ return (Datum) 0;
+ }
+ *isnull = false;
plrv_so = PyObject_Bytes(plrv);
if (!plrv_so)
@@ -793,9 +929,6 @@ PLyObject_ToBytea(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray)
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;
}
@@ -806,45 +939,87 @@ PLyObject_ToBytea(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray)
* for obtaining PostgreSQL tuples.
*/
static Datum
-PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray)
+PLyObject_ToComposite(PLyObToDatum *arg, PyObject *plrv,
+ bool *isnull, bool inarray)
{
Datum rv;
- PLyTypeInfo info;
TupleDesc desc;
- MemoryContext cxt;
- if (typmod != -1)
- elog(ERROR, "received unnamed record type as input");
+ if (plrv == Py_None)
+ {
+ *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,
- "PL/Python temp context",
- ALLOCSET_DEFAULT_SIZES);
- MemSet(&info, 0, sizeof(PLyTypeInfo));
- PLy_typeinfo_init(&info, cxt);
- /* Mark it as needing output routines lookup */
- info.is_rowtype = 2;
+ /*
+ * If we're dealing with a named composite type, we must look up the
+ * tupdesc every time, to protect against possible changes to the type.
+ * RECORD types can't change between calls; but we must still be willing
+ * to set up the info the first time, if nobody did yet.
+ */
+ if (arg->typoid != RECORDOID)
+ {
+ 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,
- * since we left is_rowtype as 2. A future optimization could be caching
- * that info instead of looking it up every time a tuple is returned from
- * the function.
+ * Convert, using the appropriate method depending on the type of the
+ * supplied Python object.
*/
- 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);
- MemoryContextDelete(cxt);
-
return rv;
}
/*
* Convert Python object to C string in server encoding.
+ *
+ * Note: this is exported for use by add-on transform modules.
*/
char *
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.
*/
static Datum
-PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray)
+PLyObject_ToScalar(PLyObToDatum *arg, PyObject *plrv,
+ bool *isnull, bool inarray)
{
char *str;
- Assert(plrv != Py_None);
+ if (plrv == Py_None)
+ {
+ *isnull = true;
+ return (Datum) 0;
+ }
+ *isnull = false;
str = PLyObject_AsString(plrv);
- /*
- * 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 && arg->typfunc.fn_oid == F_RECORD_IN)
- {
- char *ptr = str;
+ return InputFunctionCall(&arg->u.scalar.typfunc,
+ str,
+ arg->u.scalar.typioparam,
+ arg->typmod);
+}
- /* 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,
- arg->typioparam,
- typmod);
+/*
+ * Convert to a domain type.
+ */
+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
-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
-PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray)
+PLySequence_ToArray(PLyObToDatum *arg, PyObject *plrv,
+ bool *isnull, bool inarray)
{
ArrayType *array;
int i;
@@ -979,11 +1151,15 @@ PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarra
int dims[MAXDIM];
int lbs[MAXDIM];
int currelem;
- Datum rv;
PyObject *pyptr = plrv;
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.
@@ -1049,7 +1225,7 @@ PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarra
elems = palloc(sizeof(Datum) * len);
nulls = palloc(sizeof(bool) * len);
currelem = 0;
- PLySequence_ToArray_recurse(arg->elm, plrv,
+ PLySequence_ToArray_recurse(arg->u.array.elm, plrv,
dims, ndim, 0,
elems, nulls, &currelem);
@@ -1061,19 +1237,12 @@ PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarra
ndim,
dims,
lbs,
- get_base_element_type(arg->typoid),
- arg->elm->typlen,
- arg->elm->typbyval,
- arg->elm->typalign);
+ arg->u.array.elmbasetype,
+ arg->u.array.elm->typlen,
+ arg->u.array.elm->typbyval,
+ arg->u.array.elm->typalign);
- /*
- * 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;
+ return PointerGetDatum(array);
}
/*
@@ -1110,16 +1279,7 @@ PLySequence_ToArray_recurse(PLyObToDatum *elm, PyObject *list,
{
PyObject *obj = PySequence_GetItem(list, i);
- if (obj == Py_None)
- {
- nulls[*currelem] = true;
- elems[*currelem] = (Datum) 0;
- }
- else
- {
- nulls[*currelem] = false;
- elems[*currelem] = elm->func(elm, -1, obj, true);
- }
+ elems[*currelem] = elm->func(elm, obj, &nulls[*currelem], true);
Py_XDECREF(obj);
(*currelem)++;
}
@@ -1127,42 +1287,72 @@ PLySequence_ToArray_recurse(PLyObToDatum *elm, PyObject *list,
}
+/*
+ * Convert a Python string to composite, using record_in.
+ */
static Datum
-PLyString_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *string, bool inarray)
+PLyString_ToComposite(PLyObToDatum *arg, PyObject *string, bool inarray)
{
- Datum result;
- 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);
+ char *str;
- PLy_output_datum_func2(&locinfo.out.d, locinfo.mcxt, typeTup,
- exec_ctx->curr_proc->langid,
- exec_ctx->curr_proc->trftypes);
+ /*
+ * Set up call data for record_in, if we didn't already. (We can't just
+ * 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
-PLyMapping_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping)
+PLyMapping_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *mapping)
{
Datum result;
HeapTuple tuple;
@@ -1172,10 +1362,6 @@ PLyMapping_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping)
Assert(PyMapping_Check(mapping));
- if (info->is_rowtype == 2)
- PLy_output_tuple_funcs(info, desc);
- Assert(info->is_rowtype == 1);
-
/* Build tuple */
values = palloc(sizeof(Datum) * desc->natts);
nulls = palloc(sizeof(bool) * desc->natts);
@@ -1195,27 +1381,19 @@ PLyMapping_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping)
key = NameStr(attr->attname);
value = NULL;
- att = &info->out.r.atts[i];
+ att = &arg->u.tuple.atts[i];
PG_TRY();
{
value = PyMapping_GetItemString(mapping, key);
- if (value == Py_None)
- {
- values[i] = (Datum) NULL;
- nulls[i] = true;
- }
- else if (value)
- {
- values[i] = (att->func) (att, -1, value, false);
- nulls[i] = false;
- }
- else
+ if (!value)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("key \"%s\" not found in mapping", key),
errhint("To return null in a 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);
value = NULL;
}
@@ -1239,7 +1417,7 @@ PLyMapping_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping)
static Datum
-PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence)
+PLySequence_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *sequence)
{
Datum result;
HeapTuple tuple;
@@ -1266,10 +1444,6 @@ PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence)
(errcode(ERRCODE_DATATYPE_MISMATCH),
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 */
values = palloc(sizeof(Datum) * desc->natts);
nulls = palloc(sizeof(bool) * desc->natts);
@@ -1287,21 +1461,13 @@ PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence)
}
value = NULL;
- att = &info->out.r.atts[i];
+ att = &arg->u.tuple.atts[i];
PG_TRY();
{
value = PySequence_GetItem(sequence, idx);
Assert(value);
- if (value == Py_None)
- {
- values[i] = (Datum) NULL;
- nulls[i] = true;
- }
- else if (value)
- {
- values[i] = (att->func) (att, -1, value, false);
- nulls[i] = false;
- }
+
+ values[i] = att->func(att, value, &nulls[i], false);
Py_XDECREF(value);
value = NULL;
@@ -1328,7 +1494,7 @@ PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence)
static Datum
-PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object, bool inarray)
+PLyGenericObject_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *object, bool inarray)
{
Datum result;
HeapTuple tuple;
@@ -1336,10 +1502,6 @@ PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object
bool *nulls;
volatile int i;
- if (info->is_rowtype == 2)
- PLy_output_tuple_funcs(info, desc);
- Assert(info->is_rowtype == 1);
-
/* Build tuple */
values = palloc(sizeof(Datum) * desc->natts);
nulls = palloc(sizeof(bool) * desc->natts);
@@ -1359,21 +1521,11 @@ PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object
key = NameStr(attr->attname);
value = NULL;
- att = &info->out.r.atts[i];
+ att = &arg->u.tuple.atts[i];
PG_TRY();
{
value = PyObject_GetAttrString(object, key);
- if (value == Py_None)
- {
- values[i] = (Datum) NULL;
- nulls[i] = true;
- }
- else if (value)
- {
- values[i] = (att->func) (att, -1, value, false);
- nulls[i] = false;
- }
- else
+ if (!value)
{
/*
* No attribute for this column in the object.
@@ -1384,7 +1536,7 @@ PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object
* array, with a composite type (123, 'foo') in it. But now
* it's interpreted as a two-dimensional array, and we try to
* interpret "123" as the composite type. See also similar
- * heuristic in PLyObject_ToDatum().
+ * heuristic in PLyObject_ToScalar().
*/
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
@@ -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.")));
}
+ values[i] = att->func(att, value, &nulls[i], false);
+
Py_XDECREF(value);
value = NULL;
}
diff --git a/src/pl/plpython/plpy_typeio.h b/src/pl/plpython/plpy_typeio.h
index 95f84d83418..91870c91b02 100644
--- a/src/pl/plpython/plpy_typeio.h
+++ b/src/pl/plpython/plpy_typeio.h
@@ -6,117 +6,169 @@
#define PLPY_TYPEIO_H
#include "access/htup.h"
-#include "access/tupdesc.h"
#include "fmgr.h"
-#include "storage/itemptr.h"
+#include "utils/typcache.h"
+
+struct PLyProcedure; /* avoid requiring plpy_procedure.h here */
+
/*
- * Conversion from PostgreSQL Datum to a Python object.
+ * "Input" conversion from PostgreSQL Datum to a Python object.
+ *
+ * arg is the previously-set-up conversion data, val is the value to convert.
+ * val mustn't be NULL.
+ *
+ * Note: the conversion data structs should be regarded as private to
+ * plpy_typeio.c. We declare them here only so that other modules can
+ * define structs containing them.
*/
-struct PLyDatumToOb;
-typedef PyObject *(*PLyDatumToObFunc) (struct PLyDatumToOb *arg, Datum val);
+typedef struct PLyDatumToOb PLyDatumToOb; /* forward reference */
-typedef struct PLyDatumToOb
+typedef PyObject *(*PLyDatumToObFunc) (PLyDatumToOb *arg, Datum val);
+
+typedef struct PLyScalarToOb
{
- PLyDatumToObFunc func;
- FmgrInfo typfunc; /* The type's output function */
- FmgrInfo typtransform; /* from-SQL transform */
- Oid typoid; /* The OID of the type */
- int32 typmod; /* The typmod of the type */
- Oid typioparam;
- bool typbyval;
- int16 typlen;
- char typalign;
- struct PLyDatumToOb *elm;
-} PLyDatumToOb;
+ FmgrInfo typfunc; /* lookup info for type's output function */
+} PLyScalarToOb;
+
+typedef struct PLyArrayToOb
+{
+ PLyDatumToOb *elm; /* conversion info for array's element type */
+} PLyArrayToOb;
typedef struct PLyTupleToOb
{
- PLyDatumToOb *atts;
- int natts;
+ /* If we're dealing with a RECORD type, actual descriptor is here: */
+ TupleDesc recdesc;
+ /* If we're dealing with a named composite type, these fields are set: */
+ TypeCacheEntry *typentry; /* typcache entry for type */
+ int64 tupdescseq; /* last tupdesc seqno seen in typcache */
+ /* These fields are NULL/0 if not yet set: */
+ PLyDatumToOb *atts; /* array of per-column conversion info */
+ int natts; /* length of array */
} PLyTupleToOb;
-typedef union PLyTypeInput
+typedef struct PLyTransformToOb
+{
+ FmgrInfo typtransform; /* lookup info for from-SQL transform func */
+} PLyTransformToOb;
+
+struct PLyDatumToOb
{
- PLyDatumToOb d;
- PLyTupleToOb r;
-} PLyTypeInput;
+ PLyDatumToObFunc func; /* conversion control function */
+ Oid typoid; /* OID of the source type */
+ int32 typmod; /* typmod of the source type */
+ bool typbyval; /* its physical representation details */
+ int16 typlen;
+ char typalign;
+ 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
- * converted value was in an array (Python list). It is used to give a
- * better error message in some cases.
+ * *isnull is set to true if val is Py_None, false otherwise.
+ * (The conversion function *must* be called even for Py_None,
+ * so that domain constraints can be checked.)
+ *
+ * inarray is true if the converted value was in an array (Python list).
+ * It is used to give a better error message in some cases.
*/
-struct PLyObToDatum;
-typedef Datum (*PLyObToDatumFunc) (struct PLyObToDatum *arg, int32 typmod, PyObject *val, bool inarray);
+typedef struct PLyObToDatum PLyObToDatum; /* forward reference */
+
+typedef Datum (*PLyObToDatumFunc) (PLyObToDatum *arg, PyObject *val,
+ bool *isnull,
+ bool inarray);
-typedef struct PLyObToDatum
+typedef struct PLyObToScalar
{
- PLyObToDatumFunc func;
- FmgrInfo typfunc; /* The type's input function */
- FmgrInfo typtransform; /* to-SQL transform */
- Oid typoid; /* The OID of the type */
- int32 typmod; /* The typmod of the type */
- Oid typioparam;
- bool typbyval;
- int16 typlen;
- char typalign;
- struct PLyObToDatum *elm;
-} PLyObToDatum;
+ FmgrInfo typfunc; /* lookup info for type's input function */
+ Oid typioparam; /* argument to pass to it */
+} PLyObToScalar;
+
+typedef struct PLyObToArray
+{
+ PLyObToDatum *elm; /* conversion info for array's element type */
+ Oid elmbasetype; /* element base type */
+} PLyObToArray;
typedef struct PLyObToTuple
{
- PLyObToDatum *atts;
- int natts;
+ /* If we're dealing with a RECORD type, actual descriptor is here: */
+ TupleDesc recdesc;
+ /* If we're dealing with a named composite type, these fields are set: */
+ TypeCacheEntry *typentry; /* typcache entry for type */
+ int64 tupdescseq; /* last tupdesc seqno seen in typcache */
+ /* These fields are NULL/0 if not yet set: */
+ PLyObToDatum *atts; /* array of per-column conversion info */
+ int natts; /* length of array */
+ /* We might need to convert using record_in(); if so, cache info here */
+ FmgrInfo recinfunc; /* lookup info for record_in */
} PLyObToTuple;
-typedef union PLyTypeOutput
+typedef struct PLyObToDomain
{
- PLyObToDatum d;
- PLyObToTuple r;
-} PLyTypeOutput;
+ PLyObToDatum *base; /* conversion info for domain's base type */
+ void *domain_info; /* cache space for domain_check() */
+} PLyObToDomain;
-/* all we need to move PostgreSQL data to Python objects,
- * and vice versa
- */
-typedef struct PLyTypeInfo
+typedef struct PLyObToTransform
{
- PLyTypeInput in;
- PLyTypeOutput out;
-
- /*
- * 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);
+ FmgrInfo typtransform; /* lookup info for to-SQL transform function */
+} PLyObToTransform;
-extern void PLy_input_datum_func(PLyTypeInfo *arg, Oid typeOid, HeapTuple typeTup, Oid langid, List *trftypes);
-extern void PLy_output_datum_func(PLyTypeInfo *arg, HeapTuple typeTup, Oid langid, List *trftypes);
-
-extern void PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc);
-extern void PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc);
-
-extern void PLy_output_record_funcs(PLyTypeInfo *arg, TupleDesc desc);
-
-/* conversion from Python objects to composite Datums */
-extern Datum PLyObject_ToCompositeDatum(PLyTypeInfo *info, TupleDesc desc, PyObject *plrv, bool isarray);
-
-/* conversion from heap tuples to Python dictionaries */
-extern PyObject *PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc);
-
-/* conversion from Python objects to C strings */
+struct PLyObToDatum
+{
+ PLyObToDatumFunc func; /* conversion control function */
+ Oid typoid; /* OID of the target type */
+ int32 typmod; /* typmod of the target type */
+ bool typbyval; /* its physical representation details */
+ int16 typlen;
+ char typalign;
+ MemoryContext mcxt; /* context this info is stored in */
+ union /* conversion-type-specific data */
+ {
+ PLyObToScalar scalar;
+ PLyObToArray array;
+ PLyObToTuple tuple;
+ PLyObToDomain domain;
+ PLyObToTransform transform;
+ } u;
+};
+
+
+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);
#endif /* PLPY_TYPEIO_H */
diff --git a/src/pl/plpython/sql/plpython_types.sql b/src/pl/plpython/sql/plpython_types.sql
index 8c57297c24e..cc0524ee806 100644
--- a/src/pl/plpython/sql/plpython_types.sql
+++ b/src/pl/plpython/sql/plpython_types.sql
@@ -387,6 +387,55 @@ $$ LANGUAGE plpythonu;
SELECT * FROM test_type_conversion_array_domain_check_violation();
+--
+-- Arrays of domains
+--
+
+CREATE FUNCTION test_read_uint2_array(x uint2[]) RETURNS uint2 AS $$
+plpy.info(x, type(x))
+return x[0]
+$$ LANGUAGE plpythonu;
+
+select test_read_uint2_array(array[1::uint2]);
+
+CREATE FUNCTION test_build_uint2_array(x int2) RETURNS uint2[] AS $$
+return [x, x]
+$$ LANGUAGE plpythonu;
+
+select test_build_uint2_array(1::int2);
+select test_build_uint2_array(-1::int2); -- fail
+
+--
+-- ideally this would work, but for now it doesn't, because the return value
+-- is [[2,4], [2,4]] which our conversion code thinks should become a 2-D
+-- integer array, not an array of arrays.
+--
+CREATE FUNCTION test_type_conversion_domain_array(x integer[])
+ RETURNS ordered_pair_domain[] AS $$
+return [x, x]
+$$ LANGUAGE plpythonu;
+
+select test_type_conversion_domain_array(array[2,4]);
+select test_type_conversion_domain_array(array[4,2]); -- fail
+
+CREATE FUNCTION test_type_conversion_domain_array2(x ordered_pair_domain)
+ RETURNS integer AS $$
+plpy.info(x, type(x))
+return x[1]
+$$ LANGUAGE plpythonu;
+
+select test_type_conversion_domain_array2(array[2,4]);
+select test_type_conversion_domain_array2(array[4,2]); -- fail
+
+CREATE FUNCTION test_type_conversion_array_domain_array(x ordered_pair_domain[])
+ RETURNS ordered_pair_domain AS $$
+plpy.info(x, type(x))
+return x[0]
+$$ LANGUAGE plpythonu;
+
+select test_type_conversion_array_domain_array(array[array[2,4]::ordered_pair_domain]);
+
+
---
--- Composite types
---
@@ -431,6 +480,48 @@ 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
--