@@ -1489,6 +1489,44 @@ PLy_function_delete_args(PLyProcedure *proc)
14891489PyDict_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)
15051543ItemPointerEquals (& proc -> fn_tid ,& procTup -> t_self )))
15061544return false;
15071545
1546+ /* Else check the input argument datatypes */
15081547valid = true;
1509- /* If there are composite input arguments, they might have changed */
15101548for (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 */
15161553if (!valid )
15171554break ;
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+
15411561return 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 */
17091728if (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{
19711990HeapTuple relTup ;
19721991
@@ -1976,7 +1995,7 @@ PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
19761995if (!HeapTupleIsValid (relTup ))
19771996elog (ERROR ,"cache lookup failed for relation %u" ,arg -> typ_relid );
19781997
1979- /*Extract the XMINvalue to lateruse it in PLy_procedure_valid */
1998+ /*Remember XMINand TID for latervalidation if cache is still OK */
19801999arg -> typrel_xmin = HeapTupleHeaderGetXmin (relTup -> t_data );
19812000arg -> typrel_tid = relTup -> t_self ;
19822001
@@ -2050,6 +2069,29 @@ PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
20502069arg -> 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+
20532095for (i = 0 ;i < desc -> natts ;i ++ )
20542096{
20552097HeapTuple typeTup ;
@@ -2670,7 +2712,11 @@ PLyMapping_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping)
26702712PLyObToDatum * att ;
26712713
26722714if (desc -> attrs [i ]-> attisdropped )
2715+ {
2716+ values [i ]= (Datum )0 ;
2717+ nulls [i ]= true;
26732718continue ;
2719+ }
26742720
26752721key = NameStr (desc -> attrs [i ]-> attname );
26762722value = NULL ;
@@ -2756,7 +2802,11 @@ PLySequence_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence)
27562802PLyObToDatum * att ;
27572803
27582804if (desc -> attrs [i ]-> attisdropped )
2805+ {
2806+ values [i ]= (Datum )0 ;
2807+ nulls [i ]= true;
27592808continue ;
2809+ }
27602810
27612811value = NULL ;
27622812att = & info -> out .r .atts [i ];
@@ -2819,7 +2869,11 @@ PLyGenericObject_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *object)
28192869PLyObToDatum * att ;
28202870
28212871if (desc -> attrs [i ]-> attisdropped )
2872+ {
2873+ values [i ]= (Datum )0 ;
2874+ nulls [i ]= true;
28222875continue ;
2876+ }
28232877
28242878key = NameStr (desc -> attrs [i ]-> attname );
28252879value = NULL ;