diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 69a63dc59fba2b39ee8729a9e65e5f65352f902d..d6cf42c0f39812c876161d27d547a9beacf3d2c7 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -26,7 +26,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.303.2.1 2008/04/21 03:49:51 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.303.2.2 2008/08/08 17:01:18 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -47,9 +47,11 @@ #include "miscadmin.h" #include "optimizer/clauses.h" #include "parser/parse_clause.h" +#include "parser/parse_expr.h" #include "parser/parsetree.h" #include "storage/smgr.h" #include "utils/acl.h" +#include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -70,6 +72,7 @@ static void initResultRelInfo(ResultRelInfo *resultRelInfo, Index resultRelationIndex, CmdType operation, bool doInstrument); +static void ExecCheckPlanOutput(Relation resultRel, List *targetList); static void ExecEndPlan(PlanState *planstate, EState *estate); static TupleTableSlot *ExecutePlan(EState *estate, PlanState *planstate, CmdType operation, @@ -676,6 +679,9 @@ InitPlan(QueryDesc *queryDesc, int eflags) * 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; @@ -734,6 +740,10 @@ InitPlan(QueryDesc *queryDesc, int eflags) 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)); @@ -774,6 +784,10 @@ InitPlan(QueryDesc *queryDesc, int eflags) /* 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)); @@ -810,6 +824,10 @@ InitPlan(QueryDesc *queryDesc, int eflags) } else { + if (operation == CMD_INSERT) + ExecCheckPlanOutput(estate->es_result_relation_info->ri_RelationDesc, + planstate->plan->targetlist); + estate->es_junkFilter = NULL; if (estate->es_rowMarks) elog(ERROR, "SELECT FOR UPDATE/SHARE, but no junk columns"); @@ -957,6 +975,75 @@ initResultRelInfo(ResultRelInfo *resultRelInfo, ExecOpenIndices(resultRelInfo); } +/* + * 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->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."))); +} + /* * ExecGetTriggerResultRel *