*
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.241.4.3 2006/04/26 23:01:13 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.241.4.4 2008/08/08 17:01:41 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "miscadmin.h"
 #include "optimizer/clauses.h"
 #include "optimizer/var.h"
+#include "parser/parse_expr.h"
 #include "parser/parsetree.h"
 #include "utils/acl.h"
+#include "utils/builtins.h"
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
 
                                  Index resultRelationIndex,
                                  List *rangeTable,
                                  CmdType operation);
+static void ExecCheckPlanOutput(Relation resultRel, List *targetList);
 static TupleTableSlot *ExecutePlan(EState *estate, PlanState *planstate,
                        CmdType operation,
                        long numberTuples,
         * tuples (else heap_insert will be scribbling on the source
         * relation!). UPDATE and DELETE always need a filter, since there's
         * always a junk 'ctid' attribute present --- no need to look first.
+        *
+        * This section of code is also a convenient place to verify that the
+        * output of an INSERT or UPDATE matches the target table(s).
         */
        {
                bool            junk_filter_needed = false;
                                        PlanState  *subplan = appendplans[i];
                                        JunkFilter *j;
 
+                                       if (operation == CMD_UPDATE)
+                                               ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+                                                                                       subplan->plan->targetlist);
+
                                        j = ExecInitJunkFilter(subplan->plan->targetlist,
                                                                                   resultRelInfo->ri_RelationDesc->rd_att->tdhasoid,
                                                                                   ExecAllocTableSlot(estate->es_tupleTable));
                                /* Normal case with just one JunkFilter */
                                JunkFilter *j;
 
+                               if (operation == CMD_INSERT || operation == CMD_UPDATE)
+                                       ExecCheckPlanOutput(estate->es_result_relation_info->ri_RelationDesc,
+                                                                               planstate->plan->targetlist);
+
                                j = ExecInitJunkFilter(planstate->plan->targetlist,
                                                                           tupType->tdhasoid,
                                                          ExecAllocTableSlot(estate->es_tupleTable));
                        }
                }
                else
+               {
+                       if (operation == CMD_INSERT)
+                               ExecCheckPlanOutput(estate->es_result_relation_info->ri_RelationDesc,
+                                                                       planstate->plan->targetlist);
+
                        estate->es_junkFilter = NULL;
+               }
        }
 
        /*
        return false;
 }
 
+/*
+ * Verify that the tuples to be produced by INSERT or UPDATE match the
+ * target relation's rowtype
+ *
+ * We do this to guard against stale plans.  If plan invalidation is
+ * functioning properly then we should never get a failure here, but better
+ * safe than sorry.  Note that this is called after we have obtained lock
+ * on the target rel, so the rowtype can't change underneath us.
+ *
+ * The plan output is represented by its targetlist, because that makes
+ * handling the dropped-column case easier.
+ */
+static void
+ExecCheckPlanOutput(Relation resultRel, List *targetList)
+{
+       TupleDesc       resultDesc = RelationGetDescr(resultRel);
+       int                     attno = 0;
+       ListCell   *lc;
+
+       foreach(lc, targetList)
+       {
+               TargetEntry *tle = (TargetEntry *) lfirst(lc);
+               Form_pg_attribute attr;
+
+               if (tle->resdom->resjunk)
+                       continue;                       /* ignore junk tlist items */
+
+               if (attno >= resultDesc->natts)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                        errmsg("table row type and query-specified row type do not match"),
+                                        errdetail("Query has too many columns.")));
+               attr = resultDesc->attrs[attno++];
+
+               if (!attr->attisdropped)
+               {
+                       /* Normal case: demand type match */
+                       if (exprType((Node *) tle->expr) != attr->atttypid)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                                errmsg("table row type and query-specified row type do not match"),
+                                                errdetail("Table has type %s at ordinal position %d, but query expects %s.",
+                                                                  format_type_be(attr->atttypid),
+                                                                  attno,
+                                                                  format_type_be(exprType((Node *) tle->expr)))));
+               }
+               else
+               {
+                       /*
+                        * For a dropped column, we can't check atttypid (it's likely 0).
+                        * In any case the planner has most likely inserted an INT4 null.
+                        * What we insist on is just *some* NULL constant.
+                        */
+                       if (!IsA(tle->expr, Const) ||
+                               !((Const *) tle->expr)->constisnull)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                                errmsg("table row type and query-specified row type do not match"),
+                                                errdetail("Query provides a value for a dropped column at ordinal position %d.",
+                                                                  attno)));
+               }
+       }
+       if (attno != resultDesc->natts)
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                errmsg("table row type and query-specified row type do not match"),
+                                errdetail("Query has too few columns.")));
+}
+
+
 /* ----------------------------------------------------------------
  *             ExecEndPlan
  *