Invalidate PL/Python functions with composite type argument when the
authorPeter Eisentraut
Sat, 19 Feb 2011 14:52:24 +0000 (16:52 +0200)
committerPeter Eisentraut
Sat, 19 Feb 2011 14:56:02 +0000 (16:56 +0200)
type changes.

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

Jan Urbański, reviewed by Alex Hunsaker

src/pl/plpython/expected/plpython_types.out
src/pl/plpython/expected/plpython_types_3.out
src/pl/plpython/plpython.c
src/pl/plpython/sql/plpython_types.sql

index e74a4009d462adb2a38133ffc511dcf2fb3746e9..d5f2c703fad6c67a5d5386dff2c4109ff8201f80 100644 (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
 --
index 577c1fff4e997f4729423ce8a9c810f642d1d6be..ca81b08a052fd372a61444bbde22e59fe21a6f22 100644 (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
 --
index 82baf940e31959bec4d017a725bd2f830e0f4825..4e54d3e8b0e2b14d04ce6e530b558b147fcef952 100644 (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
index 2afc2ffcc1102dddabe3eafe1cee2444f19d715b..82cd9d42680733cd20e8ee17fdfecb93b6664932 100644 (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
 --