Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f26474e

Browse files
committed
Fix two issues in plpython's handling of composite results.
Dropped columns within a composite type were not handled correctly. Also, we did not check for whether a composite result type had changed since we cached the information about it. Jan Urbański, per a bug report from Jean-Baptiste Quenot
1 parent 22a55b3 commit f26474e

File tree

3 files changed

+120
-30
lines changed

3 files changed

+120
-30
lines changed

src/pl/plpython/expected/plpython_record.out

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,27 @@ SELECT * FROM test_inout_params('test_in');
308308
test_in_inout
309309
(1 row)
310310

311+
-- try changing the return types and call functions again
312+
ALTER TABLE table_record DROP COLUMN first;
313+
ALTER TABLE table_record DROP COLUMN second;
314+
ALTER TABLE table_record ADD COLUMN first text;
315+
ALTER TABLE table_record ADD COLUMN second int4;
316+
SELECT * FROM test_table_record_as('obj', 'one', 1, false);
317+
first | second
318+
-------+--------
319+
one | 1
320+
(1 row)
321+
322+
ALTER TYPE type_record DROP ATTRIBUTE first;
323+
ALTER TYPE type_record DROP ATTRIBUTE second;
324+
ALTER TYPE type_record ADD ATTRIBUTE first text;
325+
ALTER TYPE type_record ADD ATTRIBUTE second int4;
326+
SELECT * FROM test_type_record_as('obj', 'one', 1, false);
327+
first | second
328+
-------+--------
329+
one | 1
330+
(1 row)
331+
311332
-- errors cases
312333
CREATE FUNCTION test_type_record_error1() RETURNS type_record AS $$
313334
return { 'first': 'first' }

src/pl/plpython/plpython.c

Lines changed: 84 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1449,6 +1449,44 @@ PLy_function_delete_args(PLyProcedure *proc)
14491449
PyDict_DelItemString(proc->globals, proc->argnames[i]);
14501450
}
14511451

1452+
/*
1453+
* Check if our cached information about a datatype is still valid
1454+
*/
1455+
static bool
1456+
PLy_procedure_argument_valid(PLyTypeInfo *arg)
1457+
{
1458+
HeapTuple relTup;
1459+
bool valid;
1460+
1461+
/* Nothing to cache unless type is composite */
1462+
if (arg->is_rowtype != 1)
1463+
return true;
1464+
1465+
/*
1466+
* Zero typ_relid means that we got called on an output argument of a
1467+
* function returning a unnamed record type; the info for it can't change.
1468+
*/
1469+
if (!OidIsValid(arg->typ_relid))
1470+
return true;
1471+
1472+
/* Else we should have some cached data */
1473+
Assert(TransactionIdIsValid(arg->typrel_xmin));
1474+
Assert(ItemPointerIsValid(&arg->typrel_tid));
1475+
1476+
/* Get the pg_class tuple for the data type */
1477+
relTup = SearchSysCache1(RELOID, ObjectIdGetDatum(arg->typ_relid));
1478+
if (!HeapTupleIsValid(relTup))
1479+
elog(ERROR, "cache lookup failed for relation %u", arg->typ_relid);
1480+
1481+
/* If it has changed, the cached data is not valid */
1482+
valid = (arg->typrel_xmin == HeapTupleHeaderGetXmin(relTup->t_data) &&
1483+
ItemPointerEquals(&arg->typrel_tid, &relTup->t_self));
1484+
1485+
ReleaseSysCache(relTup);
1486+
1487+
return valid;
1488+
}
1489+
14521490
/*
14531491
* Decide whether a cached PLyProcedure struct is still valid
14541492
*/
@@ -1465,39 +1503,21 @@ PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup)
14651503
ItemPointerEquals(&proc->fn_tid, &procTup->t_self)))
14661504
return false;
14671505

1506+
/* Else check the input argument datatypes */
14681507
valid = true;
1469-
/* If there are composite input arguments, they might have changed */
14701508
for (i = 0; i < proc->nargs; i++)
14711509
{
1472-
Oid relid;
1473-
HeapTuple relTup;
1510+
valid = PLy_procedure_argument_valid(&proc->args[i]);
14741511

14751512
/* Short-circuit on first changed argument */
14761513
if (!valid)
14771514
break;
1478-
1479-
/* Only check input arguments that are composite */
1480-
if (proc->args[i].is_rowtype != 1)
1481-
continue;
1482-
1483-
Assert(OidIsValid(proc->args[i].typ_relid));
1484-
Assert(TransactionIdIsValid(proc->args[i].typrel_xmin));
1485-
Assert(ItemPointerIsValid(&proc->args[i].typrel_tid));
1486-
1487-
/* Get the pg_class tuple for the argument type */
1488-
relid = proc->args[i].typ_relid;
1489-
relTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
1490-
if (!HeapTupleIsValid(relTup))
1491-
elog(ERROR, "cache lookup failed for relation %u", relid);
1492-
1493-
/* If it has changed, the function is not valid */
1494-
if (!(proc->args[i].typrel_xmin == HeapTupleHeaderGetXmin(relTup->t_data) &&
1495-
ItemPointerEquals(&proc->args[i].typrel_tid, &relTup->t_self)))
1496-
valid = false;
1497-
1498-
ReleaseSysCache(relTup);
14991515
}
15001516

1517+
/* if the output type is composite, it might have changed */
1518+
if (valid)
1519+
valid = PLy_procedure_argument_valid(&proc->result);
1520+
15011521
return valid;
15021522
}
15031523

@@ -1661,10 +1681,9 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
16611681

16621682
/*
16631683
* Now get information required for input conversion of the
1664-
* procedure's arguments. Note that we ignore output arguments here
1665-
* --- since we don't support returning record, and that was already
1666-
* checked above, there's no need to worry about multiple output
1667-
* arguments.
1684+
* procedure's arguments. Note that we ignore output arguments here.
1685+
* If the function returns record, those I/O functions will be set up
1686+
* when the function is first called.
16681687
*/
16691688
if (procStruct->pronargs)
16701689
{
@@ -1926,7 +1945,7 @@ PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
19261945
* RECORDOID means we got called to create input functions for a tuple
19271946
* fetched by plpy.execute or for an anonymous record type
19281947
*/
1929-
if (desc->tdtypeid != RECORDOID && !TransactionIdIsValid(arg->typrel_xmin))
1948+
if (desc->tdtypeid != RECORDOID)
19301949
{
19311950
HeapTuple relTup;
19321951

@@ -1936,7 +1955,7 @@ PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
19361955
if (!HeapTupleIsValid(relTup))
19371956
elog(ERROR, "cache lookup failed for relation %u", arg->typ_relid);
19381957

1939-
/* Extract the XMIN value to later use it in PLy_procedure_valid */
1958+
/* Remember XMIN and TID for later validation if cache is still OK */
19401959
arg->typrel_xmin = HeapTupleHeaderGetXmin(relTup->t_data);
19411960
arg->typrel_tid = relTup->t_self;
19421961

@@ -2010,6 +2029,29 @@ PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
20102029
arg->out.r.atts = PLy_malloc0(desc->natts * sizeof(PLyDatumToOb));
20112030
}
20122031

2032+
Assert(OidIsValid(desc->tdtypeid));
2033+
2034+
/*
2035+
* RECORDOID means we got called to create output functions for an
2036+
* anonymous record type
2037+
*/
2038+
if (desc->tdtypeid != RECORDOID)
2039+
{
2040+
HeapTuple relTup;
2041+
2042+
/* Get the pg_class tuple corresponding to the type of the output */
2043+
arg->typ_relid = typeidTypeRelid(desc->tdtypeid);
2044+
relTup = SearchSysCache1(RELOID, ObjectIdGetDatum(arg->typ_relid));
2045+
if (!HeapTupleIsValid(relTup))
2046+
elog(ERROR, "cache lookup failed for relation %u", arg->typ_relid);
2047+
2048+
/* Remember XMIN and TID for later validation if cache is still OK */
2049+
arg->typrel_xmin = HeapTupleHeaderGetXmin(relTup->t_data);
2050+
arg->typrel_tid = relTup->t_self;
2051+
2052+
ReleaseSysCache(relTup);
2053+
}
2054+
20132055
for (i = 0; i < desc->natts; i++)
20142056
{
20152057
HeapTuple typeTup;
@@ -2630,7 +2672,11 @@ PLyMapping_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping)
26302672
PLyObToDatum *att;
26312673

26322674
if (desc->attrs[i]->attisdropped)
2675+
{
2676+
values[i] = (Datum) 0;
2677+
nulls[i] = true;
26332678
continue;
2679+
}
26342680

26352681
key = NameStr(desc->attrs[i]->attname);
26362682
value = NULL;
@@ -2716,7 +2762,11 @@ PLySequence_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence)
27162762
PLyObToDatum *att;
27172763

27182764
if (desc->attrs[i]->attisdropped)
2765+
{
2766+
values[i] = (Datum) 0;
2767+
nulls[i] = true;
27192768
continue;
2769+
}
27202770

27212771
value = NULL;
27222772
att = &info->out.r.atts[i];
@@ -2779,7 +2829,11 @@ PLyGenericObject_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *object)
27792829
PLyObToDatum *att;
27802830

27812831
if (desc->attrs[i]->attisdropped)
2832+
{
2833+
values[i] = (Datum) 0;
2834+
nulls[i] = true;
27822835
continue;
2836+
}
27832837

27842838
key = NameStr(desc->attrs[i]->attname);
27852839
value = NULL;

src/pl/plpython/sql/plpython_record.sql

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,21 @@ SELECT * FROM test_in_out_params('test_in');
112112
SELECT * FROM test_in_out_params_multi('test_in');
113113
SELECT * FROM test_inout_params('test_in');
114114

115+
-- try changing the return types and call functions again
116+
117+
ALTER TABLE table_record DROP COLUMN first;
118+
ALTER TABLE table_record DROP COLUMN second;
119+
ALTER TABLE table_record ADD COLUMN first text;
120+
ALTER TABLE table_record ADD COLUMN second int4;
121+
122+
SELECT * FROM test_table_record_as('obj', 'one', 1, false);
123+
124+
ALTER TYPE type_record DROP ATTRIBUTE first;
125+
ALTER TYPE type_record DROP ATTRIBUTE second;
126+
ALTER TYPE type_record ADD ATTRIBUTE first text;
127+
ALTER TYPE type_record ADD ATTRIBUTE second int4;
128+
129+
SELECT * FROM test_type_record_as('obj', 'one', 1, false);
115130

116131
-- errors cases
117132

0 commit comments

Comments
 (0)