Invalidate PL/Python functions with composite type argument when the

type changes.

The invalidation will cause the type information to be refetched, and
everything will work.

Jan Urbański, reviewed by Alex Hunsaker
This commit is contained in:
Peter Eisentraut 2011-02-19 16:52:24 +02:00
parent 964b46d00e
commit b05186f8a4
4 changed files with 223 additions and 2 deletions

View File

@ -603,6 +603,58 @@ SELECT * FROM test_type_conversion_array_error();
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_array_error"
---
--- Composite types
---
CREATE TABLE employee (
name text,
basesalary integer,
bonus integer
);
INSERT INTO employee VALUES ('John', 100, 10), ('Mary', 200, 10);
CREATE OR REPLACE FUNCTION test_composite_table_input(e employee) RETURNS integer AS $$
return e['basesalary'] + e['bonus']
$$ LANGUAGE plpythonu;
SELECT name, test_composite_table_input(employee.*) FROM employee;
name | test_composite_table_input
------+----------------------------
John | 110
Mary | 210
(2 rows)
ALTER TABLE employee DROP bonus;
SELECT name, test_composite_table_input(employee.*) FROM employee;
ERROR: KeyError: 'bonus'
CONTEXT: PL/Python function "test_composite_table_input"
ALTER TABLE employee ADD bonus integer;
UPDATE employee SET bonus = 10;
SELECT name, test_composite_table_input(employee.*) FROM employee;
name | test_composite_table_input
------+----------------------------
John | 110
Mary | 210
(2 rows)
CREATE TYPE named_pair AS (
i integer,
j integer
);
CREATE OR REPLACE FUNCTION test_composite_type_input(p named_pair) RETURNS integer AS $$
return sum(p.values())
$$ LANGUAGE plpythonu;
SELECT test_composite_type_input(row(1, 2));
test_composite_type_input
---------------------------
3
(1 row)
ALTER TYPE named_pair RENAME TO named_pair_2;
SELECT test_composite_type_input(row(1, 2));
test_composite_type_input
---------------------------
3
(1 row)
--
-- Prepared statements
--

View File

@ -603,6 +603,58 @@ SELECT * FROM test_type_conversion_array_error();
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_array_error"
---
--- Composite types
---
CREATE TABLE employee (
name text,
basesalary integer,
bonus integer
);
INSERT INTO employee VALUES ('John', 100, 10), ('Mary', 200, 10);
CREATE OR REPLACE FUNCTION test_composite_table_input(e employee) RETURNS integer AS $$
return e['basesalary'] + e['bonus']
$$ LANGUAGE plpython3u;
SELECT name, test_composite_table_input(employee.*) FROM employee;
name | test_composite_table_input
------+----------------------------
John | 110
Mary | 210
(2 rows)
ALTER TABLE employee DROP bonus;
SELECT name, test_composite_table_input(employee.*) FROM employee;
ERROR: KeyError: 'bonus'
CONTEXT: PL/Python function "test_composite_table_input"
ALTER TABLE employee ADD bonus integer;
UPDATE employee SET bonus = 10;
SELECT name, test_composite_table_input(employee.*) FROM employee;
name | test_composite_table_input
------+----------------------------
John | 110
Mary | 210
(2 rows)
CREATE TYPE named_pair AS (
i integer,
j integer
);
CREATE OR REPLACE FUNCTION test_composite_type_input(p named_pair) RETURNS integer AS $$
return sum(p.values())
$$ LANGUAGE plpython3u;
SELECT test_composite_type_input(row(1, 2));
test_composite_type_input
---------------------------
3
(1 row)
ALTER TYPE named_pair RENAME TO named_pair_2;
SELECT test_composite_type_input(row(1, 2));
test_composite_type_input
---------------------------
3
(1 row)
--
-- Prepared statements
--

View File

@ -101,6 +101,7 @@ typedef int Py_ssize_t;
#include "nodes/makefuncs.h"
#include "parser/parse_type.h"
#include "tcop/tcopprot.h"
#include "access/transam.h"
#include "access/xact.h"
#include "utils/builtins.h"
#include "utils/hsearch.h"
@ -195,6 +196,10 @@ typedef struct PLyTypeInfo
* 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;
} PLyTypeInfo;
@ -1335,11 +1340,50 @@ PLy_function_delete_args(PLyProcedure *proc)
static bool
PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup)
{
int i;
bool valid;
Assert(proc != NULL);
/* If the pg_proc tuple has changed, it's not valid */
return (proc->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) &&
ItemPointerEquals(&proc->fn_tid, &procTup->t_self));
if (!(proc->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) &&
ItemPointerEquals(&proc->fn_tid, &procTup->t_self)))
return false;
valid = true;
/* If there are composite input arguments, they might have changed */
for (i = 0; i < proc->nargs; i++)
{
Oid relid;
HeapTuple relTup;
/* Short-circuit on first changed argument */
if (!valid)
break;
/* Only check input arguments that are composite */
if (proc->args[i].is_rowtype != 1)
continue;
Assert(OidIsValid(proc->args[i].typ_relid));
Assert(TransactionIdIsValid(proc->args[i].typrel_xmin));
Assert(ItemPointerIsValid(&proc->args[i].typrel_tid));
/* Get the pg_class tuple for the argument type */
relid = proc->args[i].typ_relid;
relTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
if (!HeapTupleIsValid(relTup))
elog(ERROR, "cache lookup failed for relation %u", relid);
/* If it has changed, the function is not valid */
if (!(proc->args[i].typrel_xmin == HeapTupleHeaderGetXmin(relTup->t_data) &&
ItemPointerEquals(&proc->args[i].typrel_tid, &relTup->t_self)))
valid = false;
ReleaseSysCache(relTup);
}
return valid;
}
@ -1747,6 +1791,33 @@ PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
arg->in.r.atts = PLy_malloc0(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 && !TransactionIdIsValid(arg->typrel_xmin))
{
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);
/* Extract the XMIN value to later use it in PLy_procedure_valid */
arg->typrel_xmin = HeapTupleHeaderGetXmin(relTup->t_data);
arg->typrel_tid = relTup->t_self;
ReleaseSysCache(relTup);
}
for (i = 0; i < desc->natts; i++)
{
HeapTuple typeTup;
@ -1951,6 +2022,9 @@ PLy_typeinfo_init(PLyTypeInfo *arg)
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);
}
static void

View File

@ -279,6 +279,49 @@ $$ LANGUAGE plpythonu;
SELECT * FROM test_type_conversion_array_error();
---
--- Composite types
---
CREATE TABLE employee (
name text,
basesalary integer,
bonus integer
);
INSERT INTO employee VALUES ('John', 100, 10), ('Mary', 200, 10);
CREATE OR REPLACE FUNCTION test_composite_table_input(e employee) RETURNS integer AS $$
return e['basesalary'] + e['bonus']
$$ LANGUAGE plpythonu;
SELECT name, test_composite_table_input(employee.*) FROM employee;
ALTER TABLE employee DROP bonus;
SELECT name, test_composite_table_input(employee.*) FROM employee;
ALTER TABLE employee ADD bonus integer;
UPDATE employee SET bonus = 10;
SELECT name, test_composite_table_input(employee.*) FROM employee;
CREATE TYPE named_pair AS (
i integer,
j integer
);
CREATE OR REPLACE FUNCTION test_composite_type_input(p named_pair) RETURNS integer AS $$
return sum(p.values())
$$ LANGUAGE plpythonu;
SELECT test_composite_type_input(row(1, 2));
ALTER TYPE named_pair RENAME TO named_pair_2;
SELECT test_composite_type_input(row(1, 2));
--
-- Prepared statements
--