From 1a3970f7fafe9e60a34b3fef88b8a3c7c1fb58e6 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 8 Aug 2008 17:01:26 +0000 Subject: [PATCH] Install checks in executor startup to ensure that the tuples produced by an INSERT or UPDATE will match the target table's current rowtype. In pre-8.3 releases inconsistency can arise with stale cached plans, as reported by Merlin Moncure. (We patched the equivalent hazard on the SELECT side in Feb 2007; I'm not sure why we thought there was no risk on the insertion side.) In 8.3 and HEAD this problem should be impossible due to plan cache invalidation management, but it seems prudent to make the check anyway. Back-patch as far as 8.0. 7.x versions lack ALTER COLUMN TYPE, so there seems no way to abuse a stale plan comparably. --- src/backend/executor/execMain.c | 92 ++++++++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 804ba96aac..e1ae67f10d 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.280.2.2 2007/02/02 00:07:27 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.280.2.3 2008/08/08 17:01:26 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, List *rangeTable, CmdType operation, bool doInstrument); +static void ExecCheckPlanOutput(Relation resultRel, List *targetList); static TupleTableSlot *ExecutePlan(EState *estate, PlanState *planstate, CmdType operation, long numberTuples, @@ -641,6 +644,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; @@ -700,6 +706,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)); @@ -719,6 +729,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)); @@ -732,7 +746,13 @@ 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; + } } /* @@ -939,6 +959,76 @@ ExecContextForcesOids(PlanState *planstate, bool *hasoids) 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->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 * -- 2.50.1