Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 2dada0c

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 68c903a commit 2dada0c

File tree

3 files changed

+120
-30
lines changed

3 files changed

+120
-30
lines changed

src/pl/plpython/expected/plpython_record.out

+21
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

+84-30
Original file line numberDiff line numberDiff line change
@@ -1489,6 +1489,44 @@ PLy_function_delete_args(PLyProcedure *proc)
14891489
PyDict_DelItemString(proc->globals, proc->argnames[i]);
14901490
}
14911491

1492+
/*
1493+
* Check if our cached information about a datatype is still valid
1494+
*/
1495+
static bool
1496+
PLy_procedure_argument_valid(PLyTypeInfo *arg)
1497+
{
1498+
HeapTuple relTup;
1499+
bool valid;
1500+
1501+
/* Nothing to cache unless type is composite */
1502+
if (arg->is_rowtype != 1)
1503+
return true;
1504+
1505+
/*
1506+
* Zero typ_relid means that we got called on an output argument of a
1507+
* function returning a unnamed record type; the info for it can't change.
1508+
*/
1509+
if (!OidIsValid(arg->typ_relid))
1510+
return true;
1511+
1512+
/* Else we should have some cached data */
1513+
Assert(TransactionIdIsValid(arg->typrel_xmin));
1514+
Assert(ItemPointerIsValid(&arg->typrel_tid));
1515+
1516+
/* Get the pg_class tuple for the data type */
1517+
relTup = SearchSysCache1(RELOID, ObjectIdGetDatum(arg->typ_relid));
1518+
if (!HeapTupleIsValid(relTup))
1519+
elog(ERROR, "cache lookup failed for relation %u", arg->typ_relid);
1520+
1521+
/* If it has changed, the cached data is not valid */
1522+
valid = (arg->typrel_xmin == HeapTupleHeaderGetXmin(relTup->t_data) &&
1523+
ItemPointerEquals(&arg->typrel_tid, &relTup->t_self));
1524+
1525+
ReleaseSysCache(relTup);
1526+
1527+
return valid;
1528+
}
1529+
14921530
/*
14931531
* Decide whether a cached PLyProcedure struct is still valid
14941532
*/
@@ -1505,39 +1543,21 @@ PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup)
15051543
ItemPointerEquals(&proc->fn_tid, &procTup->t_self)))
15061544
return false;
15071545

1546+
/* Else check the input argument datatypes */
15081547
valid = true;
1509-
/* If there are composite input arguments, they might have changed */
15101548
for (i = 0; i < proc->nargs; i++)
15111549
{
1512-
Oid relid;
1513-
HeapTuple relTup;
1550+
valid = PLy_procedure_argument_valid(&proc->args[i]);
15141551

15151552
/* Short-circuit on first changed argument */
15161553
if (!valid)
15171554
break;
1518-
1519-
/* Only check input arguments that are composite */
1520-
if (proc->args[i].is_rowtype != 1)
1521-
continue;
1522-
1523-
Assert(OidIsValid(proc->args[i].typ_relid));
1524-
Assert(TransactionIdIsValid(proc->args[i].typrel_xmin));
1525-
Assert(ItemPointerIsValid(&proc->args[i].typrel_tid));
1526-
1527-
/* Get the pg_class tuple for the argument type */
1528-
relid = proc->args[i].typ_relid;
1529-
relTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
1530-
if (!HeapTupleIsValid(relTup))
1531-
elog(ERROR, "cache lookup failed for relation %u", relid);
1532-
1533-
/* If it has changed, the function is not valid */
1534-
if (!(proc->args[i].typrel_xmin == HeapTupleHeaderGetXmin(relTup->t_data) &&
1535-
ItemPointerEquals(&proc->args[i].typrel_tid, &relTup->t_self)))
1536-
valid = false;
1537-
1538-
ReleaseSysCache(relTup);
15391555
}
15401556

1557+
/* if the output type is composite, it might have changed */
1558+
if (valid)
1559+
valid = PLy_procedure_argument_valid(&proc->result);
1560+
15411561
return valid;
15421562
}
15431563

@@ -1701,10 +1721,9 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
17011721

17021722
/*
17031723
* Now get information required for input conversion of the
1704-
* procedure's arguments. Note that we ignore output arguments here
1705-
* --- since we don't support returning record, and that was already
1706-
* checked above, there's no need to worry about multiple output
1707-
* arguments.
1724+
* procedure's arguments. Note that we ignore output arguments here.
1725+
* If the function returns record, those I/O functions will be set up
1726+
* when the function is first called.
17081727
*/
17091728
if (procStruct->pronargs)
17101729
{
@@ -1966,7 +1985,7 @@ PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
19661985
* RECORDOID means we got called to create input functions for a tuple
19671986
* fetched by plpy.execute or for an anonymous record type
19681987
*/
1969-
if (desc->tdtypeid != RECORDOID && !TransactionIdIsValid(arg->typrel_xmin))
1988+
if (desc->tdtypeid != RECORDOID)
19701989
{
19711990
HeapTuple relTup;
19721991

@@ -1976,7 +1995,7 @@ PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
19761995
if (!HeapTupleIsValid(relTup))
19771996
elog(ERROR, "cache lookup failed for relation %u", arg->typ_relid);
19781997

1979-
/* Extract the XMIN value to later use it in PLy_procedure_valid */
1998+
/* Remember XMIN and TID for later validation if cache is still OK */
19801999
arg->typrel_xmin = HeapTupleHeaderGetXmin(relTup->t_data);
19812000
arg->typrel_tid = relTup->t_self;
19822001

@@ -2050,6 +2069,29 @@ PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
20502069
arg->out.r.atts = PLy_malloc0(desc->natts * sizeof(PLyDatumToOb));
20512070
}
20522071

2072+
Assert(OidIsValid(desc->tdtypeid));
2073+
2074+
/*
2075+
* RECORDOID means we got called to create output functions for an
2076+
* anonymous record type
2077+
*/
2078+
if (desc->tdtypeid != RECORDOID)
2079+
{
2080+
HeapTuple relTup;
2081+
2082+
/* Get the pg_class tuple corresponding to the type of the output */
2083+
arg->typ_relid = typeidTypeRelid(desc->tdtypeid);
2084+
relTup = SearchSysCache1(RELOID, ObjectIdGetDatum(arg->typ_relid));
2085+
if (!HeapTupleIsValid(relTup))
2086+
elog(ERROR, "cache lookup failed for relation %u", arg->typ_relid);
2087+
2088+
/* Remember XMIN and TID for later validation if cache is still OK */
2089+
arg->typrel_xmin = HeapTupleHeaderGetXmin(relTup->t_data);
2090+
arg->typrel_tid = relTup->t_self;
2091+
2092+
ReleaseSysCache(relTup);
2093+
}
2094+
20532095
for (i = 0; i < desc->natts; i++)
20542096
{
20552097
HeapTuple typeTup;
@@ -2670,7 +2712,11 @@ PLyMapping_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping)
26702712
PLyObToDatum *att;
26712713

26722714
if (desc->attrs[i]->attisdropped)
2715+
{
2716+
values[i] = (Datum) 0;
2717+
nulls[i] = true;
26732718
continue;
2719+
}
26742720

26752721
key = NameStr(desc->attrs[i]->attname);
26762722
value = NULL;
@@ -2756,7 +2802,11 @@ PLySequence_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence)
27562802
PLyObToDatum *att;
27572803

27582804
if (desc->attrs[i]->attisdropped)
2805+
{
2806+
values[i] = (Datum) 0;
2807+
nulls[i] = true;
27592808
continue;
2809+
}
27602810

27612811
value = NULL;
27622812
att = &info->out.r.atts[i];
@@ -2819,7 +2869,11 @@ PLyGenericObject_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *object)
28192869
PLyObToDatum *att;
28202870

28212871
if (desc->attrs[i]->attisdropped)
2872+
{
2873+
values[i] = (Datum) 0;
2874+
nulls[i] = true;
28222875
continue;
2876+
}
28232877

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

src/pl/plpython/sql/plpython_record.sql

+15
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)