+/*
+ * ExecPrepareTuplestoreResult
+ *
+ * Subroutine for ExecMakeFunctionResult: prepare to extract rows from a
+ * tuplestore function result. We must set up a funcResultSlot (unless
+ * already done in a previous call cycle) and verify that the function
+ * returned the expected tuple descriptor.
+ */
+static void
+ExecPrepareTuplestoreResult(FuncExprState *fcache,
+ ExprContext *econtext,
+ Tuplestorestate *resultStore,
+ TupleDesc resultDesc)
+{
+ fcache->funcResultStore = resultStore;
+
+ if (fcache->funcResultSlot == NULL)
+ {
+ /* Create a slot so we can read data out of the tuplestore */
+ MemoryContext oldcontext;
+
+ /* We must have been able to determine the result rowtype */
+ if (fcache->funcResultDesc == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("function returning setof record called in "
+ "context that cannot accept type record")));
+
+ oldcontext = MemoryContextSwitchTo(fcache->func.fn_mcxt);
+ fcache->funcResultSlot =
+ MakeSingleTupleTableSlot(fcache->funcResultDesc);
+ MemoryContextSwitchTo(oldcontext);
+ }
+
+ /*
+ * If function provided a tupdesc, cross-check it. We only really
+ * need to do this for functions returning RECORD, but might as well
+ * do it always.
+ */
+ if (resultDesc)
+ {
+ if (fcache->funcResultDesc)
+ tupledesc_match(fcache->funcResultDesc, resultDesc);
+
+ /*
+ * If it is a dynamically-allocated TupleDesc, free it: it is
+ * typically allocated in a per-query context, so we must avoid
+ * leaking it across multiple usages.
+ */
+ if (resultDesc->tdrefcount == -1)
+ FreeTupleDesc(resultDesc);
+ }
+
+ /* Register cleanup callback if we didn't already */
+ if (!fcache->shutdown_reg)
+ {
+ RegisterExprContextCallback(econtext,
+ ShutdownFuncExpr,
+ PointerGetDatum(fcache));
+ fcache->shutdown_reg = true;
+ }
+}
+
+/*
+ * Check that function result tuple type (src_tupdesc) matches or can
+ * be considered to match what the query expects (dst_tupdesc). If
+ * they don't match, ereport.
+ *
+ * We really only care about number of attributes and data type.
+ * Also, we can ignore type mismatch on columns that are dropped in the
+ * destination type, so long as the physical storage matches. This is
+ * helpful in some cases involving out-of-date cached plans.
+ */
+static void
+tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc)
+{
+ int i;
+
+ if (dst_tupdesc->natts != src_tupdesc->natts)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("function return row and query-specified return row do not match"),
+ errdetail_plural("Returned row contains %d attribute, but query expects %d.",
+ "Returned row contains %d attributes, but query expects %d.",
+ src_tupdesc->natts,
+ src_tupdesc->natts, dst_tupdesc->natts)));
+
+ for (i = 0; i < dst_tupdesc->natts; i++)
+ {
+ Form_pg_attribute dattr = dst_tupdesc->attrs[i];
+ Form_pg_attribute sattr = src_tupdesc->attrs[i];
+
+ if (dattr->atttypid == sattr->atttypid)
+ continue; /* no worries */
+ if (!dattr->attisdropped)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("function return row and query-specified return row do not match"),
+ errdetail("Returned type %s at ordinal position %d, but query expects %s.",
+ format_type_be(sattr->atttypid),
+ i + 1,
+ format_type_be(dattr->atttypid))));
+
+ if (dattr->attlen != sattr->attlen ||
+ dattr->attalign != sattr->attalign)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("function return row and query-specified return row do not match"),
+ errdetail("Physical storage mismatch on dropped attribute at ordinal position %d.",
+ i + 1)));
+ }
+}
+