Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 30fd8ec

Browse files
committed
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.
1 parent af95d7a commit 30fd8ec

File tree

1 file changed

+88
-1
lines changed

1 file changed

+88
-1
lines changed

src/backend/executor/execMain.c

+88-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
*
2727
*
2828
* IDENTIFICATION
29-
* $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.311 2008/07/26 19:15:35 tgl Exp $
29+
* $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.312 2008/08/08 17:01:11 tgl Exp $
3030
*
3131
*-------------------------------------------------------------------------
3232
*/
@@ -47,11 +47,13 @@
4747
#include "miscadmin.h"
4848
#include "optimizer/clauses.h"
4949
#include "parser/parse_clause.h"
50+
#include "parser/parse_expr.h"
5051
#include "parser/parsetree.h"
5152
#include "storage/bufmgr.h"
5253
#include "storage/lmgr.h"
5354
#include "storage/smgr.h"
5455
#include "utils/acl.h"
56+
#include "utils/builtins.h"
5557
#include "utils/lsyscache.h"
5658
#include "utils/memutils.h"
5759
#include "utils/snapmgr.h"
@@ -72,6 +74,7 @@ typedef struct evalPlanQual
7274

7375
/* decls for local routines only used within this module */
7476
static void InitPlan(QueryDesc *queryDesc, int eflags);
77+
static void ExecCheckPlanOutput(Relation resultRel, List *targetList);
7578
static void ExecEndPlan(PlanState *planstate, EState *estate);
7679
static TupleTableSlot *ExecutePlan(EState *estate, PlanState *planstate,
7780
CmdType operation,
@@ -697,6 +700,9 @@ InitPlan(QueryDesc *queryDesc, int eflags)
697700
* filter if there are any junk attrs in the tlist. UPDATE and
698701
* DELETE always need a filter, since there's always a junk 'ctid'
699702
* attribute present --- no need to look first.
703+
*
704+
* This section of code is also a convenient place to verify that the
705+
* output of an INSERT or UPDATE matches the target table(s).
700706
*/
701707
{
702708
bool junk_filter_needed = false;
@@ -751,6 +757,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
751757
PlanState *subplan = appendplans[i];
752758
JunkFilter *j;
753759

760+
if (operation == CMD_UPDATE)
761+
ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
762+
subplan->plan->targetlist);
763+
754764
j = ExecInitJunkFilter(subplan->plan->targetlist,
755765
resultRelInfo->ri_RelationDesc->rd_att->tdhasoid,
756766
ExecAllocTableSlot(estate->es_tupleTable));
@@ -791,6 +801,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
791801
/* Normal case with just one JunkFilter */
792802
JunkFilter *j;
793803

804+
if (operation == CMD_INSERT || operation == CMD_UPDATE)
805+
ExecCheckPlanOutput(estate->es_result_relation_info->ri_RelationDesc,
806+
planstate->plan->targetlist);
807+
794808
j = ExecInitJunkFilter(planstate->plan->targetlist,
795809
tupType->tdhasoid,
796810
ExecAllocTableSlot(estate->es_tupleTable));
@@ -827,6 +841,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
827841
}
828842
else
829843
{
844+
if (operation == CMD_INSERT)
845+
ExecCheckPlanOutput(estate->es_result_relation_info->ri_RelationDesc,
846+
planstate->plan->targetlist);
847+
830848
estate->es_junkFilter = NULL;
831849
if (estate->es_rowMarks)
832850
elog(ERROR, "SELECT FOR UPDATE/SHARE, but no junk columns");
@@ -974,6 +992,75 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
974992
ExecOpenIndices(resultRelInfo);
975993
}
976994

995+
/*
996+
* Verify that the tuples to be produced by INSERT or UPDATE match the
997+
* target relation's rowtype
998+
*
999+
* We do this to guard against stale plans. If plan invalidation is
1000+
* functioning properly then we should never get a failure here, but better
1001+
* safe than sorry. Note that this is called after we have obtained lock
1002+
* on the target rel, so the rowtype can't change underneath us.
1003+
*
1004+
* The plan output is represented by its targetlist, because that makes
1005+
* handling the dropped-column case easier.
1006+
*/
1007+
static void
1008+
ExecCheckPlanOutput(Relation resultRel, List *targetList)
1009+
{
1010+
TupleDesc resultDesc = RelationGetDescr(resultRel);
1011+
int attno = 0;
1012+
ListCell *lc;
1013+
1014+
foreach(lc, targetList)
1015+
{
1016+
TargetEntry *tle = (TargetEntry *) lfirst(lc);
1017+
Form_pg_attribute attr;
1018+
1019+
if (tle->resjunk)
1020+
continue; /* ignore junk tlist items */
1021+
1022+
if (attno >= resultDesc->natts)
1023+
ereport(ERROR,
1024+
(errcode(ERRCODE_DATATYPE_MISMATCH),
1025+
errmsg("table row type and query-specified row type do not match"),
1026+
errdetail("Query has too many columns.")));
1027+
attr = resultDesc->attrs[attno++];
1028+
1029+
if (!attr->attisdropped)
1030+
{
1031+
/* Normal case: demand type match */
1032+
if (exprType((Node *) tle->expr) != attr->atttypid)
1033+
ereport(ERROR,
1034+
(errcode(ERRCODE_DATATYPE_MISMATCH),
1035+
errmsg("table row type and query-specified row type do not match"),
1036+
errdetail("Table has type %s at ordinal position %d, but query expects %s.",
1037+
format_type_be(attr->atttypid),
1038+
attno,
1039+
format_type_be(exprType((Node *) tle->expr)))));
1040+
}
1041+
else
1042+
{
1043+
/*
1044+
* For a dropped column, we can't check atttypid (it's likely 0).
1045+
* In any case the planner has most likely inserted an INT4 null.
1046+
* What we insist on is just *some* NULL constant.
1047+
*/
1048+
if (!IsA(tle->expr, Const) ||
1049+
!((Const *) tle->expr)->constisnull)
1050+
ereport(ERROR,
1051+
(errcode(ERRCODE_DATATYPE_MISMATCH),
1052+
errmsg("table row type and query-specified row type do not match"),
1053+
errdetail("Query provides a value for a dropped column at ordinal position %d.",
1054+
attno)));
1055+
}
1056+
}
1057+
if (attno != resultDesc->natts)
1058+
ereport(ERROR,
1059+
(errcode(ERRCODE_DATATYPE_MISMATCH),
1060+
errmsg("table row type and query-specified row type do not match"),
1061+
errdetail("Query has too few columns.")));
1062+
}
1063+
9771064
/*
9781065
* ExecGetTriggerResultRel
9791066
*

0 commit comments

Comments
 (0)