Skip to content

Commit b05186f

Browse files
committed
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
1 parent 964b46d commit b05186f

File tree

4 files changed

+223
-2
lines changed

4 files changed

+223
-2
lines changed

src/pl/plpython/expected/plpython_types.out

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,58 @@ SELECT * FROM test_type_conversion_array_error();
603603
ERROR: return value of function with array return type is not a Python sequence
604604
CONTEXT: while creating return value
605605
PL/Python function "test_type_conversion_array_error"
606+
---
607+
--- Composite types
608+
---
609+
CREATE TABLE employee (
610+
name text,
611+
basesalary integer,
612+
bonus integer
613+
);
614+
INSERT INTO employee VALUES ('John', 100, 10), ('Mary', 200, 10);
615+
CREATE OR REPLACE FUNCTION test_composite_table_input(e employee) RETURNS integer AS $$
616+
return e['basesalary'] + e['bonus']
617+
$$ LANGUAGE plpythonu;
618+
SELECT name, test_composite_table_input(employee.*) FROM employee;
619+
name | test_composite_table_input
620+
------+----------------------------
621+
John | 110
622+
Mary | 210
623+
(2 rows)
624+
625+
ALTER TABLE employee DROP bonus;
626+
SELECT name, test_composite_table_input(employee.*) FROM employee;
627+
ERROR: KeyError: 'bonus'
628+
CONTEXT: PL/Python function "test_composite_table_input"
629+
ALTER TABLE employee ADD bonus integer;
630+
UPDATE employee SET bonus = 10;
631+
SELECT name, test_composite_table_input(employee.*) FROM employee;
632+
name | test_composite_table_input
633+
------+----------------------------
634+
John | 110
635+
Mary | 210
636+
(2 rows)
637+
638+
CREATE TYPE named_pair AS (
639+
i integer,
640+
j integer
641+
);
642+
CREATE OR REPLACE FUNCTION test_composite_type_input(p named_pair) RETURNS integer AS $$
643+
return sum(p.values())
644+
$$ LANGUAGE plpythonu;
645+
SELECT test_composite_type_input(row(1, 2));
646+
test_composite_type_input
647+
---------------------------
648+
3
649+
(1 row)
650+
651+
ALTER TYPE named_pair RENAME TO named_pair_2;
652+
SELECT test_composite_type_input(row(1, 2));
653+
test_composite_type_input
654+
---------------------------
655+
3
656+
(1 row)
657+
606658
--
607659
-- Prepared statements
608660
--

src/pl/plpython/expected/plpython_types_3.out

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,58 @@ SELECT * FROM test_type_conversion_array_error();
603603
ERROR: return value of function with array return type is not a Python sequence
604604
CONTEXT: while creating return value
605605
PL/Python function "test_type_conversion_array_error"
606+
---
607+
--- Composite types
608+
---
609+
CREATE TABLE employee (
610+
name text,
611+
basesalary integer,
612+
bonus integer
613+
);
614+
INSERT INTO employee VALUES ('John', 100, 10), ('Mary', 200, 10);
615+
CREATE OR REPLACE FUNCTION test_composite_table_input(e employee) RETURNS integer AS $$
616+
return e['basesalary'] + e['bonus']
617+
$$ LANGUAGE plpython3u;
618+
SELECT name, test_composite_table_input(employee.*) FROM employee;
619+
name | test_composite_table_input
620+
------+----------------------------
621+
John | 110
622+
Mary | 210
623+
(2 rows)
624+
625+
ALTER TABLE employee DROP bonus;
626+
SELECT name, test_composite_table_input(employee.*) FROM employee;
627+
ERROR: KeyError: 'bonus'
628+
CONTEXT: PL/Python function "test_composite_table_input"
629+
ALTER TABLE employee ADD bonus integer;
630+
UPDATE employee SET bonus = 10;
631+
SELECT name, test_composite_table_input(employee.*) FROM employee;
632+
name | test_composite_table_input
633+
------+----------------------------
634+
John | 110
635+
Mary | 210
636+
(2 rows)
637+
638+
CREATE TYPE named_pair AS (
639+
i integer,
640+
j integer
641+
);
642+
CREATE OR REPLACE FUNCTION test_composite_type_input(p named_pair) RETURNS integer AS $$
643+
return sum(p.values())
644+
$$ LANGUAGE plpython3u;
645+
SELECT test_composite_type_input(row(1, 2));
646+
test_composite_type_input
647+
---------------------------
648+
3
649+
(1 row)
650+
651+
ALTER TYPE named_pair RENAME TO named_pair_2;
652+
SELECT test_composite_type_input(row(1, 2));
653+
test_composite_type_input
654+
---------------------------
655+
3
656+
(1 row)
657+
606658
--
607659
-- Prepared statements
608660
--

src/pl/plpython/plpython.c

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ typedef int Py_ssize_t;
101101
#include "nodes/makefuncs.h"
102102
#include "parser/parse_type.h"
103103
#include "tcop/tcopprot.h"
104+
#include "access/transam.h"
104105
#include "access/xact.h"
105106
#include "utils/builtins.h"
106107
#include "utils/hsearch.h"
@@ -195,6 +196,10 @@ typedef struct PLyTypeInfo
195196
* datatype; 1 = rowtype; 2 = rowtype, but I/O functions not set up yet
196197
*/
197198
int is_rowtype;
199+
/* used to check if the type has been modified */
200+
Oid typ_relid;
201+
TransactionId typrel_xmin;
202+
ItemPointerData typrel_tid;
198203
} PLyTypeInfo;
199204

200205

@@ -1335,11 +1340,50 @@ PLy_function_delete_args(PLyProcedure *proc)
13351340
static bool
13361341
PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup)
13371342
{
1343+
int i;
1344+
bool valid;
1345+
13381346
Assert(proc != NULL);
13391347

13401348
/* If the pg_proc tuple has changed, it's not valid */
1341-
return (proc->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) &&
1342-
ItemPointerEquals(&proc->fn_tid, &procTup->t_self));
1349+
if (!(proc->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) &&
1350+
ItemPointerEquals(&proc->fn_tid, &procTup->t_self)))
1351+
return false;
1352+
1353+
valid = true;
1354+
/* If there are composite input arguments, they might have changed */
1355+
for (i = 0; i < proc->nargs; i++)
1356+
{
1357+
Oid relid;
1358+
HeapTuple relTup;
1359+
1360+
/* Short-circuit on first changed argument */
1361+
if (!valid)
1362+
break;
1363+
1364+
/* Only check input arguments that are composite */
1365+
if (proc->args[i].is_rowtype != 1)
1366+
continue;
1367+
1368+
Assert(OidIsValid(proc->args[i].typ_relid));
1369+
Assert(TransactionIdIsValid(proc->args[i].typrel_xmin));
1370+
Assert(ItemPointerIsValid(&proc->args[i].typrel_tid));
1371+
1372+
/* Get the pg_class tuple for the argument type */
1373+
relid = proc->args[i].typ_relid;
1374+
relTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
1375+
if (!HeapTupleIsValid(relTup))
1376+
elog(ERROR, "cache lookup failed for relation %u", relid);
1377+
1378+
/* If it has changed, the function is not valid */
1379+
if (!(proc->args[i].typrel_xmin == HeapTupleHeaderGetXmin(relTup->t_data) &&
1380+
ItemPointerEquals(&proc->args[i].typrel_tid, &relTup->t_self)))
1381+
valid = false;
1382+
1383+
ReleaseSysCache(relTup);
1384+
}
1385+
1386+
return valid;
13431387
}
13441388

13451389

@@ -1747,6 +1791,33 @@ PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
17471791
arg->in.r.atts = PLy_malloc0(desc->natts * sizeof(PLyDatumToOb));
17481792
}
17491793

1794+
/* Can this be an unnamed tuple? If not, then an Assert would be enough */
1795+
if (desc->tdtypmod != -1)
1796+
elog(ERROR, "received unnamed record type as input");
1797+
1798+
Assert(OidIsValid(desc->tdtypeid));
1799+
1800+
/*
1801+
* RECORDOID means we got called to create input functions for a tuple
1802+
* fetched by plpy.execute or for an anonymous record type
1803+
*/
1804+
if (desc->tdtypeid != RECORDOID && !TransactionIdIsValid(arg->typrel_xmin))
1805+
{
1806+
HeapTuple relTup;
1807+
1808+
/* Get the pg_class tuple corresponding to the type of the input */
1809+
arg->typ_relid = typeidTypeRelid(desc->tdtypeid);
1810+
relTup = SearchSysCache1(RELOID, ObjectIdGetDatum(arg->typ_relid));
1811+
if (!HeapTupleIsValid(relTup))
1812+
elog(ERROR, "cache lookup failed for relation %u", arg->typ_relid);
1813+
1814+
/* Extract the XMIN value to later use it in PLy_procedure_valid */
1815+
arg->typrel_xmin = HeapTupleHeaderGetXmin(relTup->t_data);
1816+
arg->typrel_tid = relTup->t_self;
1817+
1818+
ReleaseSysCache(relTup);
1819+
}
1820+
17501821
for (i = 0; i < desc->natts; i++)
17511822
{
17521823
HeapTuple typeTup;
@@ -1951,6 +2022,9 @@ PLy_typeinfo_init(PLyTypeInfo *arg)
19512022
arg->in.r.natts = arg->out.r.natts = 0;
19522023
arg->in.r.atts = NULL;
19532024
arg->out.r.atts = NULL;
2025+
arg->typ_relid = InvalidOid;
2026+
arg->typrel_xmin = InvalidTransactionId;
2027+
ItemPointerSetInvalid(&arg->typrel_tid);
19542028
}
19552029

19562030
static void

src/pl/plpython/sql/plpython_types.sql

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,49 @@ $$ LANGUAGE plpythonu;
279279
SELECT * FROM test_type_conversion_array_error();
280280

281281

282+
---
283+
--- Composite types
284+
---
285+
286+
CREATE TABLE employee (
287+
name text,
288+
basesalary integer,
289+
bonus integer
290+
);
291+
292+
INSERT INTO employee VALUES ('John', 100, 10), ('Mary', 200, 10);
293+
294+
CREATE OR REPLACE FUNCTION test_composite_table_input(e employee) RETURNS integer AS $$
295+
return e['basesalary'] + e['bonus']
296+
$$ LANGUAGE plpythonu;
297+
298+
SELECT name, test_composite_table_input(employee.*) FROM employee;
299+
300+
ALTER TABLE employee DROP bonus;
301+
302+
SELECT name, test_composite_table_input(employee.*) FROM employee;
303+
304+
ALTER TABLE employee ADD bonus integer;
305+
UPDATE employee SET bonus = 10;
306+
307+
SELECT name, test_composite_table_input(employee.*) FROM employee;
308+
309+
CREATE TYPE named_pair AS (
310+
i integer,
311+
j integer
312+
);
313+
314+
CREATE OR REPLACE FUNCTION test_composite_type_input(p named_pair) RETURNS integer AS $$
315+
return sum(p.values())
316+
$$ LANGUAGE plpythonu;
317+
318+
SELECT test_composite_type_input(row(1, 2));
319+
320+
ALTER TYPE named_pair RENAME TO named_pair_2;
321+
322+
SELECT test_composite_type_input(row(1, 2));
323+
324+
282325
--
283326
-- Prepared statements
284327
--

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy