rewrite-plpy_typeio-1.patch
text/x-diff
Filename: rewrite-plpy_typeio-1.patch
Type: text/x-diff
Part: 0
Message:
Rewriting PL/Python's typeio code
Patch
Format: unified
| File | + | − |
|---|---|---|
| contrib/hstore_plpython/expected/hstore_plpython.out | 0 | 0 |
| contrib/hstore_plpython/sql/hstore_plpython.sql | 0 | 0 |
| src/backend/utils/cache/typcache.c | 0 | 0 |
| src/include/utils/typcache.h | 0 | 0 |
| src/pl/plpython/expected/plpython_types_3.out | 0 | 0 |
| src/pl/plpython/expected/plpython_types.out | 0 | 0 |
| src/pl/plpython/plpy_cursorobject.c | 0 | 0 |
| src/pl/plpython/plpy_cursorobject.h | 0 | 0 |
| src/pl/plpython/plpy_exec.c | 0 | 0 |
| src/pl/plpython/plpy_main.c | 0 | 0 |
| src/pl/plpython/plpy_planobject.h | 0 | 0 |
| src/pl/plpython/plpy_procedure.c | 0 | 0 |
| src/pl/plpython/plpy_procedure.h | 0 | 0 |
| src/pl/plpython/plpy_spi.c | 0 | 0 |
| src/pl/plpython/plpy_typeio.c | 0 | 0 |
| src/pl/plpython/plpy_typeio.h | 0 | 0 |
| src/pl/plpython/sql/plpython_types.sql | 0 | 0 |
diff --git a/contrib/hstore_plpython/expected/hstore_plpython.out b/contrib/hstore_plpython/expected/hstore_plpython.out
index df49cd5..1ab5fee 100644
*** a/contrib/hstore_plpython/expected/hstore_plpython.out
--- b/contrib/hstore_plpython/expected/hstore_plpython.out
*************** AS $$
*** 68,79 ****
val = [{'a': 1, 'b': 'boo', 'c': None}, {'d': 2}]
return val
$$;
! SELECT test2arr();
test2arr
--------------------------------------------------------------
{"\"a\"=>\"1\", \"b\"=>\"boo\", \"c\"=>NULL","\"d\"=>\"2\""}
(1 row)
-- test as part of prepare/execute
CREATE FUNCTION test3() RETURNS void
LANGUAGE plpythonu
--- 68,97 ----
val = [{'a': 1, 'b': 'boo', 'c': None}, {'d': 2}]
return val
$$;
! 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 911bbd6..2c54ee6 100644
*** a/contrib/hstore_plpython/sql/hstore_plpython.sql
--- b/contrib/hstore_plpython/sql/hstore_plpython.sql
*************** val = [{'a': 1, 'b': 'boo', 'c': None},
*** 60,66 ****
return val
$$;
! SELECT test2arr();
-- test as part of prepare/execute
--- 60,80 ----
return val
$$;
! 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 7aadc5d..f6450c4 100644
*** a/src/backend/utils/cache/typcache.c
--- b/src/backend/utils/cache/typcache.c
*************** lookup_type_cache(Oid type_id, int flags
*** 377,382 ****
--- 377,383 ----
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)
*************** load_typcache_tupdesc(TypeCacheEntry *ty
*** 791,796 ****
--- 792,803 ----
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 ea799a8..c203dab 100644
*** a/src/include/utils/typcache.h
--- b/src/include/utils/typcache.h
*************** typedef struct TypeCacheEntry
*** 40,45 ****
--- 40,46 ----
char typstorage;
char typtype;
Oid typrelid;
+ Oid typelem;
/*
* Information obtained from opfamily entries
*************** typedef struct TypeCacheEntry
*** 75,83 ****
/*
* 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.)
*/
TupleDesc tupDesc;
/*
* Fields computed when TYPECACHE_RANGE_INFO is requested. Zeroes if not
--- 76,86 ----
/*
* 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.) 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 893de30..eda965a 100644
*** a/src/pl/plpython/expected/plpython_types.out
--- b/src/pl/plpython/expected/plpython_types.out
*************** SELECT * FROM test_type_conversion_array
*** 765,770 ****
--- 765,840 ----
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
---
*************** SELECT test_composite_type_input(row(1,
*** 821,826 ****
--- 891,954 ----
(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 2d853bd..69f958c 100644
*** a/src/pl/plpython/expected/plpython_types_3.out
--- b/src/pl/plpython/expected/plpython_types_3.out
*************** SELECT * FROM test_type_conversion_array
*** 765,770 ****
--- 765,840 ----
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
---
*************** SELECT test_composite_type_input(row(1,
*** 821,826 ****
--- 891,954 ----
(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 0108471..10ca786 100644
*** a/src/pl/plpython/plpy_cursorobject.c
--- b/src/pl/plpython/plpy_cursorobject.c
***************
*** 9,14 ****
--- 9,15 ----
#include <limits.h>
#include "access/xact.h"
+ #include "catalog/pg_type.h"
#include "mb/pg_wchar.h"
#include "utils/memutils.h"
*************** static PyObject *
*** 106,111 ****
--- 107,113 ----
PLy_cursor_query(const char *query)
{
PLyCursorObject *cursor;
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
volatile MemoryContext oldcontext;
volatile ResourceOwner oldowner;
*************** PLy_cursor_query(const char *query)
*** 116,122 ****
cursor->mcxt = AllocSetContextCreate(TopMemoryContext,
"PL/Python cursor context",
ALLOCSET_DEFAULT_SIZES);
! PLy_typeinfo_init(&cursor->result, cursor->mcxt);
oldcontext = CurrentMemoryContext;
oldowner = CurrentResourceOwner;
--- 118,128 ----
cursor->mcxt = AllocSetContextCreate(TopMemoryContext,
"PL/Python cursor context",
ALLOCSET_DEFAULT_SIZES);
!
! /* 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;
*************** PLy_cursor_query(const char *query)
*** 125,131 ****
PG_TRY();
{
- PLyExecutionContext *exec_ctx = PLy_current_execution_context();
SPIPlanPtr plan;
Portal portal;
--- 131,136 ----
*************** PLy_cursor_plan(PyObject *ob, PyObject *
*** 166,171 ****
--- 171,177 ----
volatile int nargs;
int i;
PLyPlanObject *plan;
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
volatile MemoryContext oldcontext;
volatile ResourceOwner oldowner;
*************** PLy_cursor_plan(PyObject *ob, PyObject *
*** 208,214 ****
cursor->mcxt = AllocSetContextCreate(TopMemoryContext,
"PL/Python cursor context",
ALLOCSET_DEFAULT_SIZES);
! PLy_typeinfo_init(&cursor->result, cursor->mcxt);
oldcontext = CurrentMemoryContext;
oldowner = CurrentResourceOwner;
--- 214,224 ----
cursor->mcxt = AllocSetContextCreate(TopMemoryContext,
"PL/Python cursor context",
ALLOCSET_DEFAULT_SIZES);
!
! /* 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;
*************** PLy_cursor_plan(PyObject *ob, PyObject *
*** 217,223 ****
PG_TRY();
{
- PLyExecutionContext *exec_ctx = PLy_current_execution_context();
Portal portal;
char *volatile nulls;
volatile int j;
--- 227,232 ----
*************** PLy_cursor_plan(PyObject *ob, PyObject *
*** 229,267 ****
for (j = 0; j < nargs; j++)
{
PyObject *elem;
elem = PySequence_GetItem(args, j);
! if (elem != Py_None)
{
! 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();
! Py_DECREF(elem);
! nulls[j] = ' ';
}
! else
{
Py_DECREF(elem);
! plan->values[j] =
! InputFunctionCall(&(plan->args[j].out.d.typfunc),
! NULL,
! plan->args[j].out.d.typioparam,
! -1);
! nulls[j] = 'n';
}
}
portal = SPI_cursor_open(NULL, plan->plan, plan->values, nulls,
--- 238,261 ----
for (j = 0; j < nargs; j++)
{
+ PLyObToDatum *arg = &plan->args[j];
PyObject *elem;
elem = PySequence_GetItem(args, j);
! PG_TRY();
{
! bool isnull;
! plan->values[j] = PLy_output_convert(arg, elem, &isnull);
! nulls[j] = isnull ? 'n' : ' ';
}
! PG_CATCH();
{
Py_DECREF(elem);
! PG_RE_THROW();
}
+ PG_END_TRY();
+ Py_DECREF(elem);
}
portal = SPI_cursor_open(NULL, plan->plan, plan->values, nulls,
*************** PLy_cursor_plan(PyObject *ob, PyObject *
*** 281,287 ****
/* cleanup plan->values array */
for (k = 0; k < nargs; k++)
{
! if (!plan->args[k].out.d.typbyval &&
(plan->values[k] != PointerGetDatum(NULL)))
{
pfree(DatumGetPointer(plan->values[k]));
--- 275,281 ----
/* cleanup plan->values array */
for (k = 0; k < nargs; k++)
{
! if (!plan->args[k].typbyval &&
(plan->values[k] != PointerGetDatum(NULL)))
{
pfree(DatumGetPointer(plan->values[k]));
*************** PLy_cursor_plan(PyObject *ob, PyObject *
*** 298,304 ****
for (i = 0; i < nargs; i++)
{
! if (!plan->args[i].out.d.typbyval &&
(plan->values[i] != PointerGetDatum(NULL)))
{
pfree(DatumGetPointer(plan->values[i]));
--- 292,298 ----
for (i = 0; i < nargs; i++)
{
! if (!plan->args[i].typbyval &&
(plan->values[i] != PointerGetDatum(NULL)))
{
pfree(DatumGetPointer(plan->values[i]));
*************** PLy_cursor_iternext(PyObject *self)
*** 339,344 ****
--- 333,339 ----
{
PLyCursorObject *cursor;
PyObject *ret;
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
volatile MemoryContext oldcontext;
volatile ResourceOwner oldowner;
Portal portal;
*************** PLy_cursor_iternext(PyObject *self)
*** 374,384 ****
}
else
{
! if (cursor->result.is_rowtype != 1)
! PLy_input_tuple_funcs(&cursor->result, SPI_tuptable->tupdesc);
! ret = PLyDict_FromTuple(&cursor->result, SPI_tuptable->vals[0],
! SPI_tuptable->tupdesc);
}
SPI_freetuptable(SPI_tuptable);
--- 369,379 ----
}
else
{
! PLy_input_setup_tuple(&cursor->result, SPI_tuptable->tupdesc,
! exec_ctx->curr_proc);
! ret = PLy_input_from_tuple(&cursor->result, SPI_tuptable->vals[0],
! SPI_tuptable->tupdesc);
}
SPI_freetuptable(SPI_tuptable);
*************** PLy_cursor_fetch(PyObject *self, PyObjec
*** 401,406 ****
--- 396,402 ----
PLyCursorObject *cursor;
int count;
PLyResultObject *ret;
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
volatile MemoryContext oldcontext;
volatile ResourceOwner oldowner;
Portal portal;
*************** PLy_cursor_fetch(PyObject *self, PyObjec
*** 437,445 ****
{
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);
--- 433,438 ----
*************** PLy_cursor_fetch(PyObject *self, PyObjec
*** 465,475 ****
Py_DECREF(ret->rows);
ret->rows = PyList_New(SPI_processed);
for (i = 0; i < SPI_processed; i++)
{
! PyObject *row = PLyDict_FromTuple(&cursor->result,
! SPI_tuptable->vals[i],
! SPI_tuptable->tupdesc);
PyList_SetItem(ret->rows, i, row);
}
--- 458,471 ----
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 = 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 018b169..e4d2c0e 100644
*** a/src/pl/plpython/plpy_cursorobject.h
--- b/src/pl/plpython/plpy_cursorobject.h
*************** typedef struct PLyCursorObject
*** 12,18 ****
{
PyObject_HEAD
char *portalname;
! PLyTypeInfo result;
bool closed;
MemoryContext mcxt;
} PLyCursorObject;
--- 12,18 ----
{
PyObject_HEAD
char *portalname;
! PLyDatumToOb result;
bool closed;
MemoryContext mcxt;
} PLyCursorObject;
diff --git a/src/pl/plpython/plpy_exec.c b/src/pl/plpython/plpy_exec.c
index 26f61dd..b84886d 100644
*** a/src/pl/plpython/plpy_exec.c
--- b/src/pl/plpython/plpy_exec.c
*************** PLy_exec_function(FunctionCallInfo fcinf
*** 202,208 ****
* 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 (plrv != Py_None)
ereport(ERROR,
--- 202,208 ----
* return value as a special "void datum" rather than NULL (as is the
* case for non-void-returning functions).
*/
! if (proc->result.typoid == VOIDOID)
{
if (plrv != Py_None)
ereport(ERROR,
*************** PLy_exec_function(FunctionCallInfo fcinf
*** 212,259 ****
fcinfo->isnull = false;
rv = (Datum) 0;
}
! else if (plrv == Py_None)
{
- 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);
}
else
{
! fcinfo->isnull = false;
! rv = (proc->result.out.d.func) (&proc->result.out.d, -1, plrv, false);
}
}
PG_CATCH();
--- 212,233 ----
fcinfo->isnull = false;
rv = (Datum) 0;
}
! else if (plrv == Py_None &&
! srfstate && srfstate->iter == NULL)
{
/*
* In a SETOF function, the iteration-ending null isn't a real
* value; don't pass it through the input function, which might
* complain.
*/
! fcinfo->isnull = true;
! rv = (Datum) 0;
}
else
{
! /* Normal conversion of result */
! rv = PLy_output_convert(&proc->result, plrv,
! &fcinfo->isnull);
}
}
PG_CATCH();
*************** PLy_exec_trigger(FunctionCallInfo fcinfo
*** 328,347 ****
PyObject *volatile plargs = NULL;
PyObject *volatile plrv = NULL;
TriggerData *tdata;
Assert(CALLED_AS_TRIGGER(fcinfo));
/*
! * Input/output conversion for trigger tuples. Use the result TypeInfo
! * variable to store the tuple conversion info. We do this over again on
! * each call to cover the possibility that the relation's tupdesc changed
! * since the trigger was last called. PLy_input_tuple_funcs and
! * PLy_output_tuple_funcs are responsible for not doing repetitive work.
*/
! tdata = (TriggerData *) fcinfo->context;
!
! PLy_input_tuple_funcs(&(proc->result), tdata->tg_relation->rd_att);
! PLy_output_tuple_funcs(&(proc->result), tdata->tg_relation->rd_att);
PG_TRY();
{
--- 302,333 ----
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. We use the result and
! * resultin variables to store the tuple conversion info. We do this over
! * again on each call to cover the possibility that the relation's tupdesc
! * changed since the trigger was last called. The PLy_xxx_setup_func
! * calls should only happen once, but PLy_input_setup_tuple and
! * PLy_output_setup_tuple are responsible for not doing repetitive work.
*/
! rel_descr = RelationGetDescr(tdata->tg_relation);
! if (proc->result.typoid != rel_descr->tdtypeid)
! PLy_output_setup_func(&proc->result, proc->mcxt,
! rel_descr->tdtypeid,
! rel_descr->tdtypmod,
! proc);
! if (proc->resultin.typoid != rel_descr->tdtypeid)
! PLy_input_setup_func(&proc->resultin, proc->mcxt,
! rel_descr->tdtypeid,
! rel_descr->tdtypmod,
! proc);
! PLy_output_setup_tuple(&proc->result, rel_descr, proc);
! PLy_input_setup_tuple(&proc->resultin, rel_descr, proc);
PG_TRY();
{
*************** PLy_function_build_args(FunctionCallInfo
*** 436,481 ****
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);
! }
! }
else
! {
! if (fcinfo->argnull[i])
! arg = NULL;
! else
! {
! arg = (proc->args[i].in.d.func) (&(proc->args[i].in.d),
! fcinfo->arg[i]);
! }
! }
if (arg == NULL)
{
--- 422,433 ----
args = PyList_New(proc->nargs);
for (i = 0; i < proc->nargs; i++)
{
! PLyDatumToOb *arginfo = &proc->args[i];
! if (fcinfo->argnull[i])
! arg = NULL;
else
! arg = PLy_input_convert(arginfo, fcinfo->arg[i]);
if (arg == NULL)
{
*************** PLy_function_build_args(FunctionCallInfo
*** 493,499 ****
}
/* Set up output conversion for functions returning RECORD */
! if (proc->result.out.d.typoid == RECORDOID)
{
TupleDesc desc;
--- 445,451 ----
}
/* Set up output conversion for functions returning RECORD */
! if (proc->result.typoid == RECORDOID)
{
TupleDesc desc;
*************** PLy_function_build_args(FunctionCallInfo
*** 504,510 ****
"that cannot accept type record")));
/* cache the output conversion functions */
! PLy_output_record_funcs(&(proc->result), desc);
}
}
PG_CATCH();
--- 456,462 ----
"that cannot accept type record")));
/* cache the output conversion functions */
! PLy_output_setup_record(&proc->result, desc, proc);
}
}
PG_CATCH();
*************** static PyObject *
*** 723,728 ****
--- 675,681 ----
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,
*************** PLy_trigger_build_args(FunctionCallInfo
*** 790,797 ****
pltevent = PyString_FromString("INSERT");
PyDict_SetItemString(pltdata, "old", Py_None);
! pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple,
! tdata->tg_relation->rd_att);
PyDict_SetItemString(pltdata, "new", pytnew);
Py_DECREF(pytnew);
*rv = tdata->tg_trigtuple;
--- 743,751 ----
pltevent = PyString_FromString("INSERT");
PyDict_SetItemString(pltdata, "old", Py_None);
! pytnew = PLy_input_from_tuple(&proc->resultin,
! tdata->tg_trigtuple,
! rel_descr);
PyDict_SetItemString(pltdata, "new", pytnew);
Py_DECREF(pytnew);
*rv = tdata->tg_trigtuple;
*************** PLy_trigger_build_args(FunctionCallInfo
*** 801,808 ****
pltevent = PyString_FromString("DELETE");
PyDict_SetItemString(pltdata, "new", Py_None);
! pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple,
! tdata->tg_relation->rd_att);
PyDict_SetItemString(pltdata, "old", pytold);
Py_DECREF(pytold);
*rv = tdata->tg_trigtuple;
--- 755,763 ----
pltevent = PyString_FromString("DELETE");
PyDict_SetItemString(pltdata, "new", Py_None);
! pytold = PLy_input_from_tuple(&proc->resultin,
! tdata->tg_trigtuple,
! rel_descr);
PyDict_SetItemString(pltdata, "old", pytold);
Py_DECREF(pytold);
*rv = tdata->tg_trigtuple;
*************** PLy_trigger_build_args(FunctionCallInfo
*** 811,822 ****
{
pltevent = PyString_FromString("UPDATE");
! pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_newtuple,
! tdata->tg_relation->rd_att);
PyDict_SetItemString(pltdata, "new", pytnew);
Py_DECREF(pytnew);
! pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple,
! tdata->tg_relation->rd_att);
PyDict_SetItemString(pltdata, "old", pytold);
Py_DECREF(pytold);
*rv = tdata->tg_newtuple;
--- 766,779 ----
{
pltevent = PyString_FromString("UPDATE");
! pytnew = PLy_input_from_tuple(&proc->resultin,
! tdata->tg_newtuple,
! rel_descr);
PyDict_SetItemString(pltdata, "new", pytnew);
Py_DECREF(pytnew);
! pytold = PLy_input_from_tuple(&proc->resultin,
! tdata->tg_trigtuple,
! rel_descr);
PyDict_SetItemString(pltdata, "old", pytold);
Py_DECREF(pytold);
*rv = tdata->tg_newtuple;
*************** PLy_trigger_build_args(FunctionCallInfo
*** 897,902 ****
--- 854,862 ----
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)
*************** PLy_modify_tuple(PLyProcedure *proc, PyO
*** 938,944 ****
plkeys = PyDict_Keys(plntup);
nkeys = PyList_Size(plkeys);
! tupdesc = tdata->tg_relation->rd_att;
modvalues = (Datum *) palloc0(tupdesc->natts * sizeof(Datum));
modnulls = (bool *) palloc0(tupdesc->natts * sizeof(bool));
--- 898,904 ----
plkeys = PyDict_Keys(plntup);
nkeys = PyList_Size(plkeys);
! tupdesc = RelationGetDescr(tdata->tg_relation);
modvalues = (Datum *) palloc0(tupdesc->natts * sizeof(Datum));
modnulls = (bool *) palloc0(tupdesc->natts * sizeof(bool));
*************** PLy_modify_tuple(PLyProcedure *proc, PyO
*** 950,956 ****
char *plattstr;
int attn;
PLyObToDatum *att;
- Form_pg_attribute attr;
platt = PyList_GetItem(plkeys, i);
if (PyString_Check(platt))
--- 910,915 ----
*************** PLy_modify_tuple(PLyProcedure *proc, PyO
*** 975,981 ****
(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)
--- 934,939 ----
*************** PLy_modify_tuple(PLyProcedure *proc, PyO
*** 983,1007 ****
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;
! }
modrepls[attn - 1] = true;
Py_DECREF(plval);
--- 941,952 ----
Py_INCREF(plval);
! /* 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 7df50c0..29db90e 100644
*** a/src/pl/plpython/plpy_main.c
--- b/src/pl/plpython/plpy_main.c
*************** plpython_inline_handler(PG_FUNCTION_ARGS
*** 318,324 ****
ALLOCSET_DEFAULT_SIZES);
proc.pyname = MemoryContextStrdup(proc.mcxt, "__plpython_inline_block");
proc.langid = codeblock->langOid;
! proc.result.out.d.typoid = VOIDOID;
/*
* Push execution context onto stack. It is important that this get
--- 318,329 ----
ALLOCSET_DEFAULT_SIZES);
proc.pyname = MemoryContextStrdup(proc.mcxt, "__plpython_inline_block");
proc.langid = codeblock->langOid;
!
! /*
! * 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 5adc957..729effb 100644
*** a/src/pl/plpython/plpy_planobject.h
--- b/src/pl/plpython/plpy_planobject.h
*************** typedef struct PLyPlanObject
*** 16,22 ****
int nargs;
Oid *types;
Datum *values;
! PLyTypeInfo *args;
MemoryContext mcxt;
} PLyPlanObject;
--- 16,22 ----
int nargs;
Oid *types;
Datum *values;
! PLyObToDatum *args;
MemoryContext mcxt;
} PLyPlanObject;
diff --git a/src/pl/plpython/plpy_procedure.c b/src/pl/plpython/plpy_procedure.c
index 26acc88..0c0d6ce 100644
*** a/src/pl/plpython/plpy_procedure.c
--- b/src/pl/plpython/plpy_procedure.c
***************
*** 15,20 ****
--- 15,21 ----
#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,35 ****
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);
--- 30,35 ----
*************** PLy_procedure_create(HeapTuple procTup,
*** 165,170 ****
--- 165,171 ----
*ptr = '_';
}
+ /* Create long-lived context that all procedure info will live in */
cxt = AllocSetContextCreate(TopMemoryContext,
procName,
ALLOCSET_DEFAULT_SIZES);
*************** PLy_procedure_create(HeapTuple procTup,
*** 188,198 ****
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->nargs = 0;
proc->langid = procStruct->prolang;
protrftypes_datum = SysCacheGetAttr(PROCOID, procTup,
--- 189,197 ----
proc->fn_tid = procTup->t_self;
proc->fn_readonly = (procStruct->provolatile != PROVOLATILE_VOLATILE);
proc->is_setof = procStruct->proretset;
proc->src = NULL;
proc->argnames = NULL;
! proc->args = NULL;
proc->nargs = 0;
proc->langid = procStruct->prolang;
protrftypes_datum = SysCacheGetAttr(PROCOID, procTup,
*************** PLy_procedure_create(HeapTuple procTup,
*** 211,260 ****
*/
if (!is_trigger)
{
HeapTuple rvTypeTup;
Form_pg_type rvTypeStruct;
! rvTypeTup = SearchSysCache1(TYPEOID,
! ObjectIdGetDatum(procStruct->prorettype));
if (!HeapTupleIsValid(rvTypeTup))
! elog(ERROR, "cache lookup failed for type %u",
! procStruct->prorettype);
rvTypeStruct = (Form_pg_type) GETSTRUCT(rvTypeTup);
/* Disallow pseudotype result, except for void or record */
if (rvTypeStruct->typtype == TYPTYPE_PSEUDO)
{
! if (procStruct->prorettype == TRIGGEROID)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("trigger functions can only be called as triggers")));
! else if (procStruct->prorettype != VOIDOID &&
! procStruct->prorettype != RECORDOID)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("PL/Python functions cannot return type %s",
! format_type_be(procStruct->prorettype))));
}
! 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);
! }
ReleaseSysCache(rvTypeTup);
}
/*
* Now get information required for input conversion of the
--- 210,257 ----
*/
if (!is_trigger)
{
+ Oid rettype = procStruct->prorettype;
HeapTuple rvTypeTup;
Form_pg_type rvTypeStruct;
! rvTypeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(rettype));
if (!HeapTupleIsValid(rvTypeTup))
! 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 (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
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("PL/Python functions cannot return type %s",
! format_type_be(rettype))));
}
! /* 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->resultin
+ * 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->resultin.typoid = InvalidOid;
+ }
/*
* Now get information required for input conversion of the
*************** PLy_procedure_create(HeapTuple procTup,
*** 287,293 ****
--- 284,293 ----
}
}
+ /* 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;
*************** PLy_procedure_create(HeapTuple procTup,
*** 306,333 ****
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;
! }
/* get argument name */
proc->argnames[pos] = names ? pstrdup(names[i]) : NULL;
--- 306,322 ----
elog(ERROR, "cache lookup failed for type %u", types[i]);
argTypeStruct = (Form_pg_type) GETSTRUCT(argTypeTup);
! /* 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;
*************** PLy_procedure_delete(PLyProcedure *proc)
*** 425,477 ****
}
/*
- * 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;
--- 414,424 ----
*************** PLy_procedure_valid(PLyProcedure *proc,
*** 480,501 ****
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;
}
static char *
--- 427,433 ----
ItemPointerEquals(&proc->fn_tid, &procTup->t_self)))
return false;
! return true;
}
static char *
diff --git a/src/pl/plpython/plpy_procedure.h b/src/pl/plpython/plpy_procedure.h
index d05944f..eddd6fe 100644
*** a/src/pl/plpython/plpy_procedure.h
--- b/src/pl/plpython/plpy_procedure.h
*************** typedef struct PLyProcedure
*** 31,42 ****
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 */
char *src; /* textual procedure code, after mangling */
char **argnames; /* Argument names */
! PLyTypeInfo args[FUNC_MAX_ARGS];
! int nargs;
Oid langid; /* OID of plpython pg_language entry */
List *trftypes; /* OID list of transform types */
PyObject *code; /* compiled procedure code */
--- 31,42 ----
ItemPointerData fn_tid;
bool fn_readonly;
bool is_setof; /* true, if procedure returns result set */
! PLyObToDatum result; /* Function result output conversion info */
! PLyDatumToOb resultin; /* For converting input tuples in a trigger */
char *src; /* textual procedure code, after mangling */
char **argnames; /* Argument names */
! 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 955769c..69eb6b3 100644
*** a/src/pl/plpython/plpy_spi.c
--- b/src/pl/plpython/plpy_spi.c
*************** PLy_spi_prepare(PyObject *self, PyObject
*** 46,51 ****
--- 46,52 ----
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;
*************** PLy_spi_prepare(PyObject *self, PyObject
*** 71,79 ****
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;
MemoryContextSwitchTo(oldcontext);
--- 72,80 ----
nargs = list ? PySequence_Length(list) : 0;
plan->nargs = nargs;
! 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);
*************** PLy_spi_prepare(PyObject *self, PyObject
*** 85,106 ****
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;
--- 86,95 ----
*************** PLy_spi_prepare(PyObject *self, PyObject
*** 124,134 ****
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);
/*
--- 113,118 ----
*************** PLy_spi_prepare(PyObject *self, PyObject
*** 138,145 ****
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);
}
pg_verifymbstr(query, strlen(query), false);
--- 122,130 ----
optr = NULL;
plan->types[i] = typeId;
! PLy_output_setup_func(&plan->args[i], plan->mcxt,
! typeId, typmod,
! exec_ctx->curr_proc);
}
pg_verifymbstr(query, strlen(query), false);
*************** PLy_spi_execute_plan(PyObject *ob, PyObj
*** 253,291 ****
for (j = 0; j < nargs; j++)
{
PyObject *elem;
elem = PySequence_GetItem(list, j);
! if (elem != Py_None)
{
! 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();
! Py_DECREF(elem);
! nulls[j] = ' ';
}
! else
{
Py_DECREF(elem);
! plan->values[j] =
! InputFunctionCall(&(plan->args[j].out.d.typfunc),
! NULL,
! plan->args[j].out.d.typioparam,
! -1);
! nulls[j] = 'n';
}
}
rv = SPI_execute_plan(plan->plan, plan->values, nulls,
--- 238,261 ----
for (j = 0; j < nargs; j++)
{
+ PLyObToDatum *arg = &plan->args[j];
PyObject *elem;
elem = PySequence_GetItem(list, j);
! PG_TRY();
{
! bool isnull;
! plan->values[j] = PLy_output_convert(arg, elem, &isnull);
! nulls[j] = isnull ? 'n' : ' ';
}
! PG_CATCH();
{
Py_DECREF(elem);
! PG_RE_THROW();
}
+ PG_END_TRY();
+ Py_DECREF(elem);
}
rv = SPI_execute_plan(plan->plan, plan->values, nulls,
*************** PLy_spi_execute_plan(PyObject *ob, PyObj
*** 306,312 ****
*/
for (k = 0; k < nargs; k++)
{
! if (!plan->args[k].out.d.typbyval &&
(plan->values[k] != PointerGetDatum(NULL)))
{
pfree(DatumGetPointer(plan->values[k]));
--- 276,282 ----
*/
for (k = 0; k < nargs; k++)
{
! if (!plan->args[k].typbyval &&
(plan->values[k] != PointerGetDatum(NULL)))
{
pfree(DatumGetPointer(plan->values[k]));
*************** PLy_spi_execute_plan(PyObject *ob, PyObj
*** 321,327 ****
for (i = 0; i < nargs; i++)
{
! if (!plan->args[i].out.d.typbyval &&
(plan->values[i] != PointerGetDatum(NULL)))
{
pfree(DatumGetPointer(plan->values[i]));
--- 291,297 ----
for (i = 0; i < nargs; i++)
{
! if (!plan->args[i].typbyval &&
(plan->values[i] != PointerGetDatum(NULL)))
{
pfree(DatumGetPointer(plan->values[i]));
*************** static PyObject *
*** 386,391 ****
--- 356,362 ----
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();
*************** PLy_spi_execute_fetch_result(SPITupleTab
*** 401,407 ****
}
else if (status > 0 && tuptable != NULL)
{
! PLyTypeInfo args;
MemoryContext cxt;
Py_DECREF(result->nrows);
--- 372,378 ----
}
else if (status > 0 && tuptable != NULL)
{
! PLyDatumToOb ininfo;
MemoryContext cxt;
Py_DECREF(result->nrows);
*************** PLy_spi_execute_fetch_result(SPITupleTab
*** 412,418 ****
cxt = AllocSetContextCreate(CurrentMemoryContext,
"PL/Python temp context",
ALLOCSET_DEFAULT_SIZES);
! PLy_typeinfo_init(&args, cxt);
oldcontext = CurrentMemoryContext;
PG_TRY();
--- 383,392 ----
cxt = AllocSetContextCreate(CurrentMemoryContext,
"PL/Python temp context",
ALLOCSET_DEFAULT_SIZES);
!
! /* Initialize for converting result tuples to Python */
! PLy_input_setup_func(&ininfo, cxt, RECORDOID, -1,
! exec_ctx->curr_proc);
oldcontext = CurrentMemoryContext;
PG_TRY();
*************** PLy_spi_execute_fetch_result(SPITupleTab
*** 436,447 ****
Py_DECREF(result->rows);
result->rows = PyList_New(rows);
! PLy_input_tuple_funcs(&args, tuptable->tupdesc);
for (i = 0; i < rows; i++)
{
! PyObject *row = PLyDict_FromTuple(&args,
! tuptable->vals[i],
! tuptable->tupdesc);
PyList_SetItem(result->rows, i, row);
}
--- 410,423 ----
Py_DECREF(result->rows);
result->rows = PyList_New(rows);
! PLy_input_setup_tuple(&ininfo, tuptable->tupdesc,
! exec_ctx->curr_proc);
!
for (i = 0; i < rows; i++)
{
! 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 e4af8cc..ce15270 100644
*** a/src/pl/plpython/plpy_typeio.c
--- b/src/pl/plpython/plpy_typeio.c
***************
*** 7,25 ****
#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 "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"
--- 7,21 ----
#include "postgres.h"
#include "access/htup_details.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "mb/pg_wchar.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 "plpython.h"
***************
*** 29,38 ****
#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);
--- 25,30 ----
*************** static PyObject *PLyInt_FromInt32(PLyDat
*** 43,403 ****
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 *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);
/* 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 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);
- 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)
{
! 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);
}
! void
! PLy_output_datum_func(PLyTypeInfo *arg, HeapTuple typeTup, Oid langid, List *trftypes)
{
! 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);
}
! void
! PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
{
! 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->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));
! }
! /* 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");
! Assert(OidIsValid(desc->tdtypeid));
! /*
! * 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;
! /* 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);
! /* 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);
}
for (i = 0; i < desc->natts; i++)
{
- HeapTuple typeTup;
Form_pg_attribute attr = TupleDescAttr(desc, i);
if (attr->attisdropped)
continue;
! if (arg->in.r.atts[i].typoid == attr->atttypid)
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);
}
-
- MemoryContextSwitchTo(oldcxt);
}
void
! PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
{
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));
! }
! Assert(OidIsValid(desc->tdtypeid));
! /*
! * RECORDOID means we got called to create output functions for an
! * anonymous record type
! */
! if (desc->tdtypeid != RECORDOID)
{
! 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);
}
for (i = 0; i < desc->natts; i++)
{
- HeapTuple typeTup;
Form_pg_attribute attr = TupleDescAttr(desc, i);
if (attr->attisdropped)
continue;
! if (arg->out.r.atts[i].typoid == attr->atttypid)
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);
}
-
- MemoryContextSwitchTo(oldcxt);
}
void
! PLy_output_record_funcs(PLyTypeInfo *arg, TupleDesc desc)
{
/*
! * If the output record functions are already set, we just have to check
! * if the record descriptor has not changed
*/
- 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
*/
! Assert(arg->is_rowtype == 1);
}
/*
! * Transform a tuple into a Python dict object.
*/
! PyObject *
! PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc)
{
! PyObject *volatile dict;
! PLyExecutionContext *exec_ctx = PLy_current_execution_context();
! MemoryContext scratch_context = PLy_get_scratch_context(exec_ctx);
! MemoryContext oldcontext = CurrentMemoryContext;
! if (info->is_rowtype != 1)
! elog(ERROR, "PLyTypeInfo structure describes a datum");
! dict = PyDict_New();
! if (dict == NULL)
! PLy_elog(ERROR, "could not create new dictionary");
! PG_TRY();
{
! 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);
}
! PG_CATCH();
{
! MemoryContextSwitchTo(oldcontext);
! Py_DECREF(dict);
! PG_RE_THROW();
}
- 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.
*/
!
! if ((funcid = get_transform_tosql(base_type, langid, trftypes)))
{
arg->func = PLyObject_ToTransform;
! fmgr_info_cxt(funcid, &arg->typtransform, arg_mcxt);
}
! else if (typeStruct->typtype == TYPTYPE_COMPOSITE)
{
arg->func = PLyObject_ToComposite;
}
else
! switch (base_type)
{
case BOOLOID:
arg->func = PLyObject_ToBool;
--- 35,399 ----
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_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, 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 */
! 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);
/*
* Conversion functions. Remember output from Python is input to
* PostgreSQL, and vice versa.
*/
!
! /*
! * 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)
{
! 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;
}
! /*
! * 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)
{
! /* at outer level, we are not considering an array element */
! return arg->func(arg, val, isnull, false);
}
! /*
! * 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)
{
! PyObject *dict;
PLyExecutionContext *exec_ctx = PLy_current_execution_context();
! MemoryContext scratch_context = PLy_get_scratch_context(exec_ctx);
! MemoryContext oldcontext;
! /*
! * As in PLy_input_convert, do the work in the scratch context.
! */
! MemoryContextReset(scratch_context);
! oldcontext = MemoryContextSwitchTo(scratch_context);
! dict = PLyDict_FromTuple(arg, tuple, desc);
! MemoryContextSwitchTo(oldcontext);
! return dict;
! }
! /*
! * 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;
! /* We should be working on a previously-set-up struct */
! Assert(arg->func == PLyDict_FromComposite);
! /* Save pointer to tupdesc, but only if this is an anonymous record type */
! if (arg->typoid == RECORDOID && arg->typmod < 0)
! arg->u.tuple.recdesc = desc;
! /* (Re)allocate atts array as needed */
! 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++)
{
Form_pg_attribute attr = TupleDescAttr(desc, i);
+ PLyDatumToOb *att = &arg->u.tuple.atts[i];
if (attr->attisdropped)
continue;
! if (att->typoid == attr->atttypid && att->typmod == attr->atttypmod)
continue; /* already set up this entry */
! PLy_input_setup_func(att, arg->mcxt,
! attr->atttypid, attr->atttypmod,
! proc);
}
}
+ /*
+ * 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_setup_tuple(PLyObToDatum *arg, TupleDesc desc, PLyProcedure *proc)
{
int i;
! /* We should be working on a previously-set-up struct */
! Assert(arg->func == PLyObject_ToComposite);
! /* Save pointer to tupdesc, but only if this is an anonymous record type */
! if (arg->typoid == RECORDOID && arg->typmod < 0)
! arg->u.tuple.recdesc = desc;
! /* (Re)allocate atts array as needed */
! 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 = (PLyObToDatum *)
! MemoryContextAllocZero(arg->mcxt,
! desc->natts * sizeof(PLyObToDatum));
}
+ /* Fill the atts entries, except for dropped columns */
for (i = 0; i < desc->natts; i++)
{
Form_pg_attribute attr = TupleDescAttr(desc, i);
+ PLyObToDatum *att = &arg->u.tuple.atts[i];
if (attr->attisdropped)
continue;
! if (att->typoid == attr->atttypid && att->typmod == attr->atttypmod)
continue; /* already set up this entry */
! PLy_output_setup_func(att, arg->mcxt,
! attr->atttypid, attr->atttypmod,
! proc);
}
}
+ /*
+ * Set up output info for a PL/Python function returning record.
+ *
+ * Note: the given tupdesc is not necessarily long-lived.
+ */
void
! PLy_output_setup_record(PLyObToDatum *arg, TupleDesc desc, PLyProcedure *proc)
{
+ /* Makes no sense unless RECORD */
+ Assert(arg->typoid == RECORDOID);
+ Assert(desc->tdtypeid == RECORDOID);
+
/*
! * 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.
*/
BlessTupleDesc(desc);
/*
! * 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.
*/
! 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);
}
/*
! * 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.
*/
! void
! PLy_output_setup_func(PLyObToDatum *arg, MemoryContext arg_mcxt,
! Oid typeOid, int32 typmod,
! PLyProcedure *proc)
{
! TypeCacheEntry *typentry;
! char typtype;
! Oid trfuncid;
! Oid typinput;
! /* Since this is recursive, it could theoretically be driven to overflow */
! check_stack_depth();
! arg->typoid = typeOid;
! arg->typmod = typmod;
! arg->mcxt = arg_mcxt;
! /*
! * 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';
}
/*
! * 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 */
! 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(trfuncid, &arg->u.transform.typtransform, arg_mcxt);
}
! 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
! {
! /* Scalar type, but we have a couple of special cases */
! switch (typeOid)
{
case BOOLOID:
arg->func = PLyObject_ToBool;
*************** PLy_output_datum_func2(PLyObToDatum *arg
*** 406,471 ****
arg->func = PLyObject_ToBytea;
break;
default:
! arg->func = PLyObject_ToDatum;
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)
{
! Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
! Oid element_type;
! Oid base_type;
! Oid funcid;
! MemoryContext oldcxt;
!
! oldcxt = MemoryContextSwitchTo(arg_mcxt);
! /* 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;
! /* Determine which kind of Python object we will convert to */
! element_type = get_base_element_type(typeOid);
! base_type = getBaseType(element_type ? element_type : typeOid);
! if ((funcid = get_transform_fromsql(base_type, langid, trftypes)))
{
arg->func = PLyObject_FromTransform;
! fmgr_info_cxt(funcid, &arg->typtransform, arg_mcxt);
}
else
! switch (base_type)
{
case BOOLOID:
arg->func = PLyBool_FromBool;
--- 402,512 ----
arg->func = PLyObject_ToBytea;
break;
default:
! arg->func = PLyObject_ToScalar;
! getTypeInputInfo(typeOid, &typinput, &arg->u.scalar.typioparam);
! fmgr_info_cxt(typinput, &arg->u.scalar.typfunc, arg_mcxt);
break;
}
}
}
! /*
! * 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)
{
! TypeCacheEntry *typentry;
! char typtype;
! Oid trfuncid;
! Oid typoutput;
! bool typisvarlena;
! /* Since this is recursive, it could theoretically be driven to overflow */
! check_stack_depth();
! arg->typoid = typeOid;
! arg->typmod = typmod;
! arg->mcxt = arg_mcxt;
! /*
! * 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';
! }
! /*
! * 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(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
! {
! /* Scalar type, but we have a couple of special cases */
! switch (typeOid)
{
case BOOLOID:
arg->func = PLyBool_FromBool;
*************** PLy_input_datum_func2(PLyDatumToOb *arg,
*** 495,524 ****
arg->func = PLyBytes_FromBytea;
break;
default:
! arg->func = PLyString_FromDatum;
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);
}
static PyObject *
PLyBool_FromBool(PLyDatumToOb *arg, Datum d)
{
--- 536,554 ----
arg->func = PLyBytes_FromBytea;
break;
default:
! arg->func = PLyString_FromScalar;
! getTypeOutputInfo(typeOid, &typoutput, &typisvarlena);
! fmgr_info_cxt(typoutput, &arg->u.scalar.typfunc, arg_mcxt);
break;
}
}
}
+
+ /*
+ * Special-purpose input converters.
+ */
+
static PyObject *
PLyBool_FromBool(PLyDatumToOb *arg, Datum d)
{
*************** PLyBytes_FromBytea(PLyDatumToOb *arg, Da
*** 611,637 ****
return PyBytes_FromStringAndSize(str, size);
}
static PyObject *
! PLyString_FromDatum(PLyDatumToOb *arg, Datum d)
{
! char *x = OutputFunctionCall(&arg->typfunc, d);
PyObject *r = PyString_FromString(x);
pfree(x);
return r;
}
static PyObject *
PLyObject_FromTransform(PLyDatumToOb *arg, Datum d)
{
! return (PyObject *) DatumGetPointer(FunctionCall1(&arg->typtransform, d));
}
static PyObject *
PLyList_FromArray(PLyDatumToOb *arg, Datum d)
{
ArrayType *array = DatumGetArrayTypeP(d);
! PLyDatumToOb *elm = arg->elm;
int ndim;
int *dims;
char *dataptr;
--- 641,680 ----
return PyBytes_FromStringAndSize(str, size);
}
+
+ /*
+ * Generic input conversion using a SQL type's output function.
+ */
static PyObject *
! PLyString_FromScalar(PLyDatumToOb *arg, Datum 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)
{
! 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->u.array.elm;
int ndim;
int *dims;
char *dataptr;
*************** PLyList_FromArray_recurse(PLyDatumToOb *
*** 737,759 ****
}
/*
* 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)
{
! 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;
}
/*
--- 780,889 ----
}
/*
+ * 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, PyObject *plrv,
! bool *isnull, bool inarray)
{
! if (plrv == Py_None)
! {
! *isnull = true;
! return (Datum) 0;
! }
! *isnull = false;
! return BoolGetDatum(PyObject_IsTrue(plrv));
}
/*
*************** PLyObject_ToBool(PLyObToDatum *arg, int3
*** 762,773 ****
* with embedded nulls. And it's faster this way.
*/
static Datum
! PLyObject_ToBytea(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray)
{
PyObject *volatile plrv_so = NULL;
Datum rv;
! Assert(plrv != Py_None);
plrv_so = PyObject_Bytes(plrv);
if (!plrv_so)
--- 892,909 ----
* with embedded nulls. And it's faster this way.
*/
static Datum
! PLyObject_ToBytea(PLyObToDatum *arg, PyObject *plrv,
! bool *isnull, bool inarray)
{
PyObject *volatile plrv_so = NULL;
Datum rv;
! if (plrv == Py_None)
! {
! *isnull = true;
! return (Datum) 0;
! }
! *isnull = false;
plrv_so = PyObject_Bytes(plrv);
if (!plrv_so)
*************** PLyObject_ToBytea(PLyObToDatum *arg, int
*** 793,801 ****
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;
}
--- 929,934 ----
*************** PLyObject_ToBytea(PLyObToDatum *arg, int
*** 806,850 ****
* for obtaining PostgreSQL tuples.
*/
static Datum
! PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray)
{
Datum rv;
- PLyTypeInfo info;
TupleDesc desc;
- MemoryContext cxt;
! if (typmod != -1)
! elog(ERROR, "received unnamed record type as input");
! /* 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;
! desc = lookup_rowtype_tupdesc(arg->typoid, arg->typmod);
/*
! * 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.
*/
! rv = PLyObject_ToCompositeDatum(&info, desc, plrv, inarray);
ReleaseTupleDesc(desc);
- MemoryContextDelete(cxt);
-
return rv;
}
/*
* Convert Python object to C string in server encoding.
*/
char *
PLyObject_AsString(PyObject *plrv)
--- 939,1025 ----
* for obtaining PostgreSQL tuples.
*/
static Datum
! PLyObject_ToComposite(PLyObToDatum *arg, PyObject *plrv,
! bool *isnull, bool inarray)
{
Datum rv;
TupleDesc desc;
! 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);
! /*
! * 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);
! }
! }
!
! /* Simple sanity check on our caching */
! Assert(desc->natts == arg->u.tuple.natts);
/*
! * Convert, using the appropriate method depending on the type of the
! * supplied Python object.
*/
! 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);
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)
*************** PLyObject_AsString(PyObject *plrv)
*** 901,974 ****
/*
! * Generic conversion function: Convert PyObject to cstring and
* cstring into PostgreSQL type.
*/
static Datum
! PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray)
{
char *str;
! Assert(plrv != Py_None);
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;
- /* 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);
}
static Datum
! PLyObject_ToTransform(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray)
{
! return FunctionCall1(&arg->typtransform, PointerGetDatum(plrv));
}
static Datum
! PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv, bool inarray)
{
ArrayType *array;
int i;
--- 1076,1146 ----
/*
! * Generic output conversion function: convert PyObject to cstring and
* cstring into PostgreSQL type.
*/
static Datum
! PLyObject_ToScalar(PLyObToDatum *arg, PyObject *plrv,
! bool *isnull, bool inarray)
{
char *str;
! if (plrv == Py_None)
! {
! *isnull = true;
! return (Datum) 0;
! }
! *isnull = false;
str = PLyObject_AsString(plrv);
! return InputFunctionCall(&arg->u.scalar.typfunc,
! str,
! arg->u.scalar.typioparam,
! arg->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, PyObject *plrv,
! bool *isnull, bool inarray)
{
! 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, PyObject *plrv,
! bool *isnull, bool inarray)
{
ArrayType *array;
int i;
*************** PLySequence_ToArray(PLyObToDatum *arg, i
*** 979,989 ****
int dims[MAXDIM];
int lbs[MAXDIM];
int currelem;
- Datum rv;
PyObject *pyptr = plrv;
PyObject *next;
! Assert(plrv != Py_None);
/*
* Determine the number of dimensions, and their sizes.
--- 1151,1165 ----
int dims[MAXDIM];
int lbs[MAXDIM];
int currelem;
PyObject *pyptr = plrv;
PyObject *next;
! if (plrv == Py_None)
! {
! *isnull = true;
! return (Datum) 0;
! }
! *isnull = false;
/*
* Determine the number of dimensions, and their sizes.
*************** PLySequence_ToArray(PLyObToDatum *arg, i
*** 1049,1055 ****
elems = palloc(sizeof(Datum) * len);
nulls = palloc(sizeof(bool) * len);
currelem = 0;
! PLySequence_ToArray_recurse(arg->elm, plrv,
dims, ndim, 0,
elems, nulls, &currelem);
--- 1225,1231 ----
elems = palloc(sizeof(Datum) * len);
nulls = palloc(sizeof(bool) * len);
currelem = 0;
! PLySequence_ToArray_recurse(arg->u.array.elm, plrv,
dims, ndim, 0,
elems, nulls, &currelem);
*************** PLySequence_ToArray(PLyObToDatum *arg, i
*** 1061,1079 ****
ndim,
dims,
lbs,
! get_base_element_type(arg->typoid),
! arg->elm->typlen,
! arg->elm->typbyval,
! arg->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;
}
/*
--- 1237,1248 ----
ndim,
dims,
lbs,
! arg->u.array.elmbasetype,
! arg->u.array.elm->typlen,
! arg->u.array.elm->typbyval,
! arg->u.array.elm->typalign);
! return PointerGetDatum(array);
}
/*
*************** PLySequence_ToArray_recurse(PLyObToDatum
*** 1110,1125 ****
{
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);
! }
Py_XDECREF(obj);
(*currelem)++;
}
--- 1279,1285 ----
{
PyObject *obj = PySequence_GetItem(list, i);
! elems[*currelem] = elm->func(elm, obj, &nulls[*currelem], true);
Py_XDECREF(obj);
(*currelem)++;
}
*************** PLySequence_ToArray_recurse(PLyObToDatum
*** 1127,1168 ****
}
static Datum
! PLyString_ToComposite(PLyTypeInfo *info, TupleDesc desc, 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);
! PLy_output_datum_func2(&locinfo.out.d, locinfo.mcxt, typeTup,
! exec_ctx->curr_proc->langid,
! exec_ctx->curr_proc->trftypes);
! ReleaseSysCache(typeTup);
! result = PLyObject_ToDatum(&locinfo.out.d, desc->tdtypmod, string, inarray);
! MemoryContextDelete(cxt);
! return result;
}
static Datum
! PLyMapping_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping)
{
Datum result;
HeapTuple tuple;
--- 1287,1358 ----
}
+ /*
+ * Convert a Python string to composite, using record_in.
+ */
static Datum
! PLyString_ToComposite(PLyObToDatum *arg, PyObject *string, bool inarray)
{
! char *str;
! /*
! * 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);
! str = PLyObject_AsString(string);
! /*
! * 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;
! /* 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->u.tuple.recinfunc,
! str,
! arg->typoid,
! arg->typmod);
}
static Datum
! PLyMapping_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *mapping)
{
Datum result;
HeapTuple tuple;
*************** PLyMapping_ToComposite(PLyTypeInfo *info
*** 1172,1181 ****
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);
--- 1362,1367 ----
*************** PLyMapping_ToComposite(PLyTypeInfo *info
*** 1195,1221 ****
key = NameStr(attr->attname);
value = NULL;
! att = &info->out.r.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
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.")));
Py_XDECREF(value);
value = NULL;
}
--- 1381,1399 ----
key = NameStr(attr->attname);
value = NULL;
! att = &arg->u.tuple.atts[i];
PG_TRY();
{
value = PyMapping_GetItemString(mapping, key);
! 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;
}
*************** PLyMapping_ToComposite(PLyTypeInfo *info
*** 1239,1245 ****
static Datum
! PLySequence_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence)
{
Datum result;
HeapTuple tuple;
--- 1417,1423 ----
static Datum
! PLySequence_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *sequence)
{
Datum result;
HeapTuple tuple;
*************** PLySequence_ToComposite(PLyTypeInfo *inf
*** 1266,1275 ****
(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);
--- 1444,1449 ----
*************** PLySequence_ToComposite(PLyTypeInfo *inf
*** 1287,1307 ****
}
value = NULL;
! att = &info->out.r.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;
! }
Py_XDECREF(value);
value = NULL;
--- 1461,1473 ----
}
value = NULL;
! att = &arg->u.tuple.atts[i];
PG_TRY();
{
value = PySequence_GetItem(sequence, idx);
Assert(value);
!
! values[i] = att->func(att, value, &nulls[i], false);
Py_XDECREF(value);
value = NULL;
*************** PLySequence_ToComposite(PLyTypeInfo *inf
*** 1328,1334 ****
static Datum
! PLyGenericObject_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *object, bool inarray)
{
Datum result;
HeapTuple tuple;
--- 1494,1500 ----
static Datum
! PLyGenericObject_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *object, bool inarray)
{
Datum result;
HeapTuple tuple;
*************** PLyGenericObject_ToComposite(PLyTypeInfo
*** 1336,1345 ****
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);
--- 1502,1507 ----
*************** PLyGenericObject_ToComposite(PLyTypeInfo
*** 1359,1379 ****
key = NameStr(attr->attname);
value = NULL;
! att = &info->out.r.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
{
/*
* No attribute for this column in the object.
--- 1521,1531 ----
key = NameStr(attr->attname);
value = NULL;
! att = &arg->u.tuple.atts[i];
PG_TRY();
{
value = PyObject_GetAttrString(object, key);
! if (!value)
{
/*
* No attribute for this column in the object.
*************** PLyGenericObject_ToComposite(PLyTypeInfo
*** 1384,1390 ****
* 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().
*/
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
--- 1536,1542 ----
* 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_ToScalar().
*/
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
*************** PLyGenericObject_ToComposite(PLyTypeInfo
*** 1394,1399 ****
--- 1546,1553 ----
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 95f84d8..91870c9 100644
*** a/src/pl/plpython/plpy_typeio.h
--- b/src/pl/plpython/plpy_typeio.h
***************
*** 6,122 ****
#define PLPY_TYPEIO_H
#include "access/htup.h"
- #include "access/tupdesc.h"
#include "fmgr.h"
! #include "storage/itemptr.h"
/*
! * Conversion from PostgreSQL Datum to a Python object.
*/
! struct PLyDatumToOb;
! typedef PyObject *(*PLyDatumToObFunc) (struct PLyDatumToOb *arg, Datum val);
! typedef struct PLyDatumToOb
{
! 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;
typedef struct PLyTupleToOb
{
! PLyDatumToOb *atts;
! int natts;
} PLyTupleToOb;
! typedef union PLyTypeInput
{
! PLyDatumToOb d;
! PLyTupleToOb r;
! } PLyTypeInput;
/*
! * Conversion from Python object to a PostgreSQL Datum.
*
! * The 'inarray' argument to the conversion function is true, if the
! * converted value was in an array (Python list). It is used to give a
! * better error message in some cases.
*/
! struct PLyObToDatum;
! typedef Datum (*PLyObToDatumFunc) (struct PLyObToDatum *arg, int32 typmod, PyObject *val, bool inarray);
! typedef struct PLyObToDatum
{
! 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;
typedef struct PLyObToTuple
{
! PLyObToDatum *atts;
! int natts;
} PLyObToTuple;
! typedef union PLyTypeOutput
{
! PLyObToDatum d;
! PLyObToTuple r;
! } PLyTypeOutput;
! /* all we need to move PostgreSQL data to Python objects,
! * and vice versa
! */
! typedef struct PLyTypeInfo
{
! 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);
! 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 */
extern char *PLyObject_AsString(PyObject *plrv);
#endif /* PLPY_TYPEIO_H */
--- 6,174 ----
#define PLPY_TYPEIO_H
#include "access/htup.h"
#include "fmgr.h"
! #include "utils/typcache.h"
!
! struct PLyProcedure; /* avoid requiring plpy_procedure.h here */
!
/*
! * "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.
*/
! typedef struct PLyDatumToOb PLyDatumToOb; /* forward reference */
! typedef PyObject *(*PLyDatumToObFunc) (PLyDatumToOb *arg, Datum val);
!
! typedef struct PLyScalarToOb
{
! 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
{
! /* 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 struct PLyTransformToOb
{
! FmgrInfo typtransform; /* lookup info for from-SQL transform func */
! } PLyTransformToOb;
!
! struct PLyDatumToOb
! {
! 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;
! };
/*
! * "Output" conversion from Python object to a PostgreSQL Datum.
*
! * arg is the previously-set-up conversion data, val is the value to convert.
! *
! * *isnull is set to true if val is Py_None, false otherwise.
! * (The conversion function *must* be called even for Py_None,
! * so that domain constraints can be checked.)
! *
! * inarray is true if the converted value was in an array (Python list).
! * It is used to give a better error message in some cases.
*/
! typedef struct PLyObToDatum PLyObToDatum; /* forward reference */
! typedef Datum (*PLyObToDatumFunc) (PLyObToDatum *arg, PyObject *val,
! bool *isnull,
! bool inarray);
!
! typedef struct PLyObToScalar
{
! FmgrInfo typfunc; /* lookup info for type's input function */
! Oid typioparam; /* argument to pass to it */
! } PLyObToScalar;
!
! typedef struct PLyObToArray
! {
! PLyObToDatum *elm; /* conversion info for array's element type */
! Oid elmbasetype; /* element base type */
! } PLyObToArray;
typedef struct PLyObToTuple
{
! /* 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 struct PLyObToDomain
{
! PLyObToDatum *base; /* conversion info for domain's base type */
! void *domain_info; /* cache space for domain_check() */
! } PLyObToDomain;
! typedef struct PLyObToTransform
{
! FmgrInfo typtransform; /* lookup info for to-SQL transform function */
! } PLyObToTransform;
! 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 8c57297..cc0524e 100644
*** a/src/pl/plpython/sql/plpython_types.sql
--- b/src/pl/plpython/sql/plpython_types.sql
*************** $$ LANGUAGE plpythonu;
*** 387,392 ****
--- 387,441 ----
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
---
*************** SELECT test_composite_type_input(row(1,
*** 431,436 ****
--- 480,527 ----
--
+ -- 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
--