]> granicus.if.org Git - postgresql/commitdiff
Repair failure to check that a table is still compatible with a previously
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 2 Feb 2007 00:07:03 +0000 (00:07 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 2 Feb 2007 00:07:03 +0000 (00:07 +0000)
made query plan.  Use of ALTER COLUMN TYPE creates a hazard for cached
query plans: they could contain Vars that claim a column has a different
type than it now has.  Fix this by checking during plan startup that Vars
at relation scan level match the current relation tuple descriptor.  Since
at that point we already have at least AccessShareLock, we can be sure the
column type will not change underneath us later in the query.  However,
since a backend's locks do not conflict against itself, there is still a
hole for an attacker to exploit: he could try to execute ALTER COLUMN TYPE
while a query is in progress in the current backend.  Seal that hole by
rejecting ALTER TABLE whenever the target relation is already open in
the current backend.

This is a significant security hole: not only can one trivially crash the
backend, but with appropriate misuse of pass-by-reference datatypes it is
possible to read out arbitrary locations in the server process's memory,
which could allow retrieving database content the user should not be able
to see.  Our thanks to Jeff Trout for the initial report.

Security: CVE-2007-0556

13 files changed:
src/backend/commands/tablecmds.c
src/backend/executor/execMain.c
src/backend/executor/execQual.c
src/backend/executor/execScan.c
src/backend/executor/execUtils.c
src/backend/executor/nodeAgg.c
src/backend/executor/nodeGroup.c
src/backend/executor/nodeHashjoin.c
src/backend/executor/nodeMergejoin.c
src/backend/executor/nodeNestloop.c
src/backend/executor/nodeResult.c
src/backend/executor/nodeSubplan.c
src/include/executor/executor.h

index a11cde3647ef8880d2a05545b7cb45bc19f5ab30..001bc5715484b6a5da663da859ddbcd81dec49ae 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.212 2007/01/25 04:35:10 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.213 2007/02/02 00:07:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1964,22 +1964,47 @@ update_ri_trigger_args(Oid relid,
 void
 AlterTable(AlterTableStmt *stmt)
 {
-       ATController(relation_openrv(stmt->relation, AccessExclusiveLock),
-                                stmt->cmds,
-                                interpretInhOption(stmt->relation->inhOpt));
+       Relation rel = relation_openrv(stmt->relation, AccessExclusiveLock);
+       int                     expected_refcnt;
+
+       /*
+        * Disallow ALTER TABLE when the current backend has any open reference
+        * to it besides the one we just got (such as an open cursor or active
+        * plan); our AccessExclusiveLock doesn't protect us against stomping on
+        * our own foot, only other people's feet!
+        *
+        * Note: the only case known to cause serious trouble is ALTER COLUMN TYPE,
+        * and some changes are obviously pretty benign, so this could possibly
+        * be relaxed to only error out for certain types of alterations.  But
+        * the use-case for allowing any of these things is not obvious, so we
+        * won't work hard at it for now.
+        */
+       expected_refcnt = rel->rd_isnailed ? 2 : 1;
+       if (rel->rd_refcnt != expected_refcnt)
+               ereport(ERROR,
+                               (errcode(ERRCODE_OBJECT_IN_USE),
+                                errmsg("relation \"%s\" is being used by active queries in this session",
+                                               RelationGetRelationName(rel))));
+
+       ATController(rel, stmt->cmds, interpretInhOption(stmt->relation->inhOpt));
 }
 
 /*
  * AlterTableInternal
  *
  * ALTER TABLE with target specified by OID
+ *
+ * We do not reject if the relation is already open, because it's quite
+ * likely that one or more layers of caller have it open.  That means it
+ * is unsafe to use this entry point for alterations that could break
+ * existing query plans.
  */
 void
 AlterTableInternal(Oid relid, List *cmds, bool recurse)
 {
-       ATController(relation_open(relid, AccessExclusiveLock),
-                                cmds,
-                                recurse);
+       Relation rel = relation_open(relid, AccessExclusiveLock);
+
+       ATController(rel, cmds, recurse);
 }
 
 static void
@@ -2929,6 +2954,12 @@ ATSimpleRecursion(List **wqueue, Relation rel,
                        if (childrelid == relid)
                                continue;
                        childrel = relation_open(childrelid, AccessExclusiveLock);
+                       /* check for child relation in use in this session */
+                       if (childrel->rd_refcnt != 1)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_OBJECT_IN_USE),
+                                                errmsg("relation \"%s\" is being used by active queries in this session",
+                                                               RelationGetRelationName(childrel))));
                        ATPrepCmd(wqueue, childrel, cmd, false, true);
                        relation_close(childrel, NoLock);
                }
@@ -2960,6 +2991,12 @@ ATOneLevelRecursion(List **wqueue, Relation rel,
                Relation        childrel;
 
                childrel = relation_open(childrelid, AccessExclusiveLock);
+               /* check for child relation in use in this session */
+               if (childrel->rd_refcnt != 1)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_OBJECT_IN_USE),
+                                        errmsg("relation \"%s\" is being used by active queries in this session",
+                                                       RelationGetRelationName(childrel))));
                ATPrepCmd(wqueue, childrel, cmd, true, true);
                relation_close(childrel, NoLock);
        }
@@ -3765,6 +3802,12 @@ ATExecDropColumn(Relation rel, const char *colName,
                        Form_pg_attribute childatt;
 
                        childrel = heap_open(childrelid, AccessExclusiveLock);
+                       /* check for child relation in use in this session */
+                       if (childrel->rd_refcnt != 1)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_OBJECT_IN_USE),
+                                                errmsg("relation \"%s\" is being used by active queries in this session",
+                                                               RelationGetRelationName(childrel))));
 
                        tuple = SearchSysCacheCopyAttName(childrelid, colName);
                        if (!HeapTupleIsValid(tuple))           /* shouldn't happen */
index 29916550af47dfdb0209ee0ea6bc67357702f296..91a76bb0779b15c8c8e084e4693254f8290ed1a7 100644 (file)
@@ -26,7 +26,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.285 2007/01/25 04:35:10 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.286 2007/02/02 00:07:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -804,7 +804,8 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 
                        rliststate = (List *) ExecInitExpr((Expr *) rlist, planstate);
                        resultRelInfo->ri_projectReturning =
-                               ExecBuildProjectionInfo(rliststate, econtext, slot);
+                               ExecBuildProjectionInfo(rliststate, econtext, slot,
+                                                                          resultRelInfo->ri_RelationDesc->rd_att);
                        resultRelInfo++;
                }
 
index 732c1ab849f6bfa525ff754104b666a61729f9b2..6b72c02781f5dbe1c29f34c04712faf125eba2e9 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.210 2007/02/01 19:10:26 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.211 2007/02/02 00:07:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -64,6 +64,8 @@ static Datum ExecEvalAggref(AggrefExprState *aggref,
                           bool *isNull, ExprDoneCond *isDone);
 static Datum ExecEvalVar(ExprState *exprstate, ExprContext *econtext,
                        bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext,
+                       bool *isNull, ExprDoneCond *isDone);
 static Datum ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext,
                                        bool *isNull, ExprDoneCond *isDone);
 static Datum ExecEvalConst(ExprState *exprstate, ExprContext *econtext,
@@ -425,6 +427,10 @@ ExecEvalAggref(AggrefExprState *aggref, ExprContext *econtext,
  *
  *             Returns a Datum whose value is the value of a range
  *             variable with respect to given expression context.
+ *
+ * Note: ExecEvalVar is executed only the first time through in a given plan;
+ * it changes the ExprState's function pointer to pass control directly to
+ * ExecEvalScalarVar or ExecEvalWholeRowVar after making one-time checks.
  * ----------------------------------------------------------------
  */
 static Datum
@@ -439,7 +445,7 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext,
                *isDone = ExprSingleResult;
 
        /*
-        * Get the slot and attribute number we want
+        * Get the input slot and attribute number we want
         *
         * The asserts check that references to system attributes only appear at
         * the level of a relation scan; at higher levels, system attributes must
@@ -466,35 +472,170 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext,
                        break;
        }
 
-#ifdef USE_ASSERT_CHECKING
-
-       /*
-        * Some checks that are only applied for user attribute numbers (bogus
-        * system attnums will be caught inside slot_getattr).
-        */
-       if (attnum > 0)
+       if (attnum != InvalidAttrNumber)
        {
-               TupleDesc       tuple_type = slot->tts_tupleDescriptor;
-
                /*
-                * This assert checks that the attnum is valid.
+                * Scalar variable case.
+                *
+                * If it's a user attribute, check validity (bogus system attnums will
+                * be caught inside slot_getattr).  What we have to check for here
+                * is the possibility of an attribute having been changed in type
+                * since the plan tree was created.  Ideally the plan would get
+                * invalidated and not re-used, but until that day arrives, we need
+                * defenses.  Fortunately it's sufficient to check once on the first
+                * time through.
+                *
+                * Note: we allow a reference to a dropped attribute.  slot_getattr
+                * will force a NULL result in such cases.
+                *
+                * Note: we check typmod, but allow the case that the Var has
+                * unspecified typmod while the column has a specific typmod.
                 */
-               Assert(attnum <= tuple_type->natts);
+               if (attnum > 0)
+               {
+                       TupleDesc       slot_tupdesc = slot->tts_tupleDescriptor;
+                       Form_pg_attribute attr;
+
+                       if (attnum > slot_tupdesc->natts)       /* should never happen */
+                               elog(ERROR, "attribute number %d exceeds number of columns %d",
+                                        attnum, slot_tupdesc->natts);
+
+                       attr = slot_tupdesc->attrs[attnum - 1];
 
+                       /* can't check type if dropped, since atttypid is probably 0 */
+                       if (!attr->attisdropped)
+                       {
+                               if (variable->vartype != attr->atttypid ||
+                                       (variable->vartypmod != attr->atttypmod &&
+                                        variable->vartypmod != -1))
+                                       ereport(ERROR,
+                                                       (errmsg("attribute %d has wrong type", attnum),
+                                                        errdetail("Table has type %s, but query expects %s.",
+                                                                          format_type_be(attr->atttypid),
+                                                                          format_type_be(variable->vartype))));
+                       }
+               }
+
+               /* Skip the checking on future executions of node */
+               exprstate->evalfunc = ExecEvalScalarVar;
+
+               /* Fetch the value from the slot */
+               return slot_getattr(slot, attnum, isNull);
+       }
+       else
+       {
                /*
-                * This assert checks that the datatype the plan expects to get (as
-                * told by our "variable" argument) is in fact the datatype of the
-                * attribute being fetched (as seen in the current context, identified
-                * by our "econtext" argument).  Otherwise crashes are likely.
+                * Whole-row variable.
                 *
-                * Note that we can't check dropped columns, since their atttypid has
-                * been zeroed.
+                * If it's a RECORD Var, we'll use the slot's type ID info.  It's
+                * likely that the slot's type is also RECORD; if so, make sure it's
+                * been "blessed", so that the Datum can be interpreted later.
+                *
+                * If the Var identifies a named composite type, we must check that
+                * the actual tuple type is compatible with it.
                 */
-               Assert(variable->vartype == tuple_type->attrs[attnum - 1]->atttypid ||
-                          tuple_type->attrs[attnum - 1]->attisdropped);
+               TupleDesc       slot_tupdesc = slot->tts_tupleDescriptor;
+
+               if (variable->vartype == RECORDOID)
+               {
+                       if (slot_tupdesc->tdtypeid == RECORDOID &&
+                               slot_tupdesc->tdtypmod < 0)
+                               assign_record_type_typmod(slot_tupdesc);
+               }
+               else
+               {
+                       TupleDesc       var_tupdesc;
+                       int                     i;
+
+                       /*
+                        * 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.
+                        */
+                       var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1);
+
+                       if (var_tupdesc->natts != slot_tupdesc->natts)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                                errmsg("table row type and query-specified row type do not match"),
+                                                errdetail("Table row contains %d attributes, but query expects %d.",
+                                                                  slot_tupdesc->natts, var_tupdesc->natts)));
+
+                       for (i = 0; i < var_tupdesc->natts; i++)
+                       {
+                               Form_pg_attribute vattr = var_tupdesc->attrs[i];
+                               Form_pg_attribute sattr = slot_tupdesc->attrs[i];
+
+                               if (vattr->atttypid == sattr->atttypid)
+                                       continue;                       /* no worries */
+                               if (!vattr->attisdropped)
+                                       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(sattr->atttypid),
+                                                                          i + 1,
+                                                                          format_type_be(vattr->atttypid))));
+
+                               if (vattr->attlen != sattr->attlen ||
+                                       vattr->attalign != sattr->attalign)
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                                        errmsg("table row type and query-specified row type do not match"),
+                                                        errdetail("Physical storage mismatch on dropped attribute at ordinal position %d.",
+                                                                          i + 1)));
+                       }
+
+                       ReleaseTupleDesc(var_tupdesc);
+               }
+
+               /* Skip the checking on future executions of node */
+               exprstate->evalfunc = ExecEvalWholeRowVar;
+
+               /* Fetch the value */
+               return ExecEvalWholeRowVar(exprstate, econtext, isNull, isDone);
        }
-#endif   /* USE_ASSERT_CHECKING */
+}
 
+/* ----------------------------------------------------------------
+ *             ExecEvalScalarVar
+ *
+ *             Returns a Datum for a scalar variable.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext,
+                                 bool *isNull, ExprDoneCond *isDone)
+{
+       Var                *variable = (Var *) exprstate->expr;
+       TupleTableSlot *slot;
+       AttrNumber      attnum;
+
+       if (isDone)
+               *isDone = ExprSingleResult;
+
+       /* Get the input slot and attribute number we want */
+       switch (variable->varno)
+       {
+               case INNER:                             /* get the tuple from the inner node */
+                       slot = econtext->ecxt_innertuple;
+                       break;
+
+               case OUTER:                             /* get the tuple from the outer node */
+                       slot = econtext->ecxt_outertuple;
+                       break;
+
+               default:                                /* get the tuple from the relation being
+                                                                * scanned */
+                       slot = econtext->ecxt_scantuple;
+                       break;
+       }
+
+       attnum = variable->varattno;
+
+       /* Fetch the value from the slot */
        return slot_getattr(slot, attnum, isNull);
 }
 
@@ -502,10 +643,6 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext,
  *             ExecEvalWholeRowVar
  *
  *             Returns a Datum for a whole-row variable.
- *
- *             This could be folded into ExecEvalVar, but we make it a separate
- *             routine so as not to slow down ExecEvalVar with tests for this
- *             uncommon case.
  * ----------------------------------------------------------------
  */
 static Datum
@@ -513,7 +650,7 @@ ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext,
                                        bool *isNull, ExprDoneCond *isDone)
 {
        Var                *variable = (Var *) exprstate->expr;
-       TupleTableSlot *slot;
+       TupleTableSlot *slot = econtext->ecxt_scantuple;
        HeapTuple       tuple;
        TupleDesc       tupleDesc;
        HeapTupleHeader dtuple;
@@ -522,16 +659,6 @@ ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext,
                *isDone = ExprSingleResult;
        *isNull = false;
 
-       Assert(variable->varattno == InvalidAttrNumber);
-
-       /*
-        * Whole-row Vars can only appear at the level of a relation scan, never
-        * in a join.
-        */
-       Assert(variable->varno != INNER);
-       Assert(variable->varno != OUTER);
-       slot = econtext->ecxt_scantuple;
-
        tuple = ExecFetchSlotTuple(slot);
        tupleDesc = slot->tts_tupleDescriptor;
 
@@ -547,9 +674,6 @@ ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext,
        /*
         * If the Var identifies a named composite type, label the tuple with that
         * type; otherwise use what is in the tupleDesc.
-        *
-        * It's likely that the slot's tupleDesc is a record type; if so, make
-        * sure it's been "blessed", so that the Datum can be interpreted later.
         */
        if (variable->vartype != RECORDOID)
        {
@@ -558,9 +682,6 @@ ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext,
        }
        else
        {
-               if (tupleDesc->tdtypeid == RECORDOID &&
-                       tupleDesc->tdtypmod < 0)
-                       assign_record_type_typmod(tupleDesc);
                HeapTupleHeaderSetTypeId(dtuple, tupleDesc->tdtypeid);
                HeapTupleHeaderSetTypMod(dtuple, tupleDesc->tdtypmod);
        }
@@ -3192,12 +3313,14 @@ ExecEvalFieldSelect(FieldSelectState *fstate,
                                        ExprDoneCond *isDone)
 {
        FieldSelect *fselect = (FieldSelect *) fstate->xprstate.expr;
+       AttrNumber      fieldnum = fselect->fieldnum;
        Datum           result;
        Datum           tupDatum;
        HeapTupleHeader tuple;
        Oid                     tupType;
        int32           tupTypmod;
        TupleDesc       tupDesc;
+       Form_pg_attribute attr;
        HeapTupleData tmptup;
 
        tupDatum = ExecEvalExpr(fstate->arg, econtext, isNull, isDone);
@@ -3215,6 +3338,28 @@ ExecEvalFieldSelect(FieldSelectState *fstate,
        tupDesc = get_cached_rowtype(tupType, tupTypmod,
                                                                 &fstate->argdesc, econtext);
 
+       /* Check for dropped column, and force a NULL result if so */
+       if (fieldnum <= 0 ||
+               fieldnum > tupDesc->natts)      /* should never happen */
+                               elog(ERROR, "attribute number %d exceeds number of columns %d",
+                                        fieldnum, tupDesc->natts);
+       attr = tupDesc->attrs[fieldnum - 1];
+       if (attr->attisdropped)
+       {
+               *isNull = true;
+               return (Datum) 0;
+       }
+
+       /* Check for type mismatch --- possible after ALTER COLUMN TYPE? */
+       if (fselect->resulttype != attr->atttypid ||
+               (fselect->resulttypmod != attr->atttypmod &&
+                fselect->resulttypmod != -1))
+               ereport(ERROR,
+                               (errmsg("attribute %d has wrong type", fieldnum),
+                                errdetail("Table has type %s, but query expects %s.",
+                                                  format_type_be(attr->atttypid),
+                                                  format_type_be(fselect->resulttype))));
+
        /*
         * heap_getattr needs a HeapTuple not a bare HeapTupleHeader.  We set all
         * the fields in the struct just in case user tries to inspect system
@@ -3226,7 +3371,7 @@ ExecEvalFieldSelect(FieldSelectState *fstate,
        tmptup.t_data = tuple;
 
        result = heap_getattr(&tmptup,
-                                                 fselect->fieldnum,
+                                                 fieldnum,
                                                  tupDesc,
                                                  isNull);
        return result;
@@ -3413,15 +3558,8 @@ ExecInitExpr(Expr *node, PlanState *parent)
        switch (nodeTag(node))
        {
                case T_Var:
-                       {
-                               Var                *var = (Var *) node;
-
-                               state = (ExprState *) makeNode(ExprState);
-                               if (var->varattno != InvalidAttrNumber)
-                                       state->evalfunc = ExecEvalVar;
-                               else
-                                       state->evalfunc = ExecEvalWholeRowVar;
-                       }
+                       state = (ExprState *) makeNode(ExprState);
+                       state->evalfunc = ExecEvalVar;
                        break;
                case T_Const:
                        state = (ExprState *) makeNode(ExprState);
index b3789d8efcfafeea815b8bfd29cf519e92adc4fb..7ee4dc3841c98e9daf93ff458963665e13076476 100644 (file)
@@ -12,7 +12,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/executor/execScan.c,v 1.40 2007/01/24 01:25:47 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/executor/execScan.c,v 1.41 2007/02/02 00:07:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -187,7 +187,8 @@ ExecAssignScanProjectionInfo(ScanState *node)
                                                          node->ss_ScanTupleSlot->tts_tupleDescriptor))
                node->ps.ps_ProjInfo = NULL;
        else
-               ExecAssignProjectionInfo(&node->ps);
+               ExecAssignProjectionInfo(&node->ps,
+                                                                node->ss_ScanTupleSlot->tts_tupleDescriptor);
 }
 
 static bool
@@ -209,6 +210,7 @@ tlist_matches_tupdesc(PlanState *ps, List *tlist, Index varno, TupleDesc tupdesc
                var = (Var *) ((TargetEntry *) lfirst(tlist_item))->expr;
                if (!var || !IsA(var, Var))
                        return false;           /* tlist item not a Var */
+               /* if these Asserts fail, planner messed up */
                Assert(var->varno == varno);
                Assert(var->varlevelsup == 0);
                if (var->varattno != attrno)
@@ -225,8 +227,10 @@ tlist_matches_tupdesc(PlanState *ps, List *tlist, Index varno, TupleDesc tupdesc
                 * projection steps just to convert from specific typmod to typmod -1,
                 * which is pretty silly.
                 */
-               Assert(var->vartype == att_tup->atttypid);
-               Assert(var->vartypmod == att_tup->atttypmod || var->vartypmod == -1);
+               if (var->vartype != att_tup->atttypid ||
+                       (var->vartypmod != att_tup->atttypmod &&
+                        var->vartypmod != -1))
+                       return false;           /* type mismatch */
 
                tlist_item = lnext(tlist_item);
        }
index 7b15e153d22b5247efaf0cd95fed051c8b7dd3b1..8285b306276e61d70f6ac42eadc31941aa2508d8 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/executor/execUtils.c,v 1.142 2007/01/05 22:19:27 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/executor/execUtils.c,v 1.143 2007/02/02 00:07:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -578,12 +578,19 @@ ExecGetResultType(PlanState *planstate)
  * econtext, and storing the result into the tuple slot.  (Caller must have
  * ensured that tuple slot has a descriptor matching the tlist!)  Note that
  * the given tlist should be a list of ExprState nodes, not Expr nodes.
+ *
+ * inputDesc can be NULL, but if it is not, we check to see whether simple
+ * Vars in the tlist match the descriptor.  It is important to provide
+ * inputDesc for relation-scan plan nodes, as a cross check that the relation
+ * hasn't been changed since the plan was made.  At higher levels of a plan,
+ * there is no need to recheck.
  * ----------------
  */
 ProjectionInfo *
 ExecBuildProjectionInfo(List *targetList,
                                                ExprContext *econtext,
-                                               TupleTableSlot *slot)
+                                               TupleTableSlot *slot,
+                                               TupleDesc inputDesc)
 {
        ProjectionInfo *projInfo = makeNode(ProjectionInfo);
        int                     len;
@@ -598,14 +605,17 @@ ExecBuildProjectionInfo(List *targetList,
 
        /*
         * Determine whether the target list consists entirely of simple Var
-        * references (ie, references to non-system attributes).  If so, we can
-        * use the simpler ExecVariableList instead of ExecTargetList.
+        * references (ie, references to non-system attributes) that match the
+        * input.  If so, we can use the simpler ExecVariableList instead of
+        * ExecTargetList.  (Note: if there is a type mismatch then ExecEvalVar
+        * will probably throw an error at runtime, but we leave that to it.)
         */
        isVarList = true;
        foreach(tl, targetList)
        {
                GenericExprState *gstate = (GenericExprState *) lfirst(tl);
                Var                *variable = (Var *) gstate->arg->expr;
+               Form_pg_attribute attr;
 
                if (variable == NULL ||
                        !IsA(variable, Var) ||
@@ -614,6 +624,22 @@ ExecBuildProjectionInfo(List *targetList,
                        isVarList = false;
                        break;
                }
+               if (!inputDesc)
+                       continue;                       /* can't check type, assume OK */
+               if (variable->varattno > inputDesc->natts)
+               {
+                       isVarList = false;
+                       break;
+               }
+               attr = inputDesc->attrs[variable->varattno - 1];
+               if (attr->attisdropped ||
+                       variable->vartype != attr->atttypid ||
+                       (variable->vartypmod != attr->atttypmod &&
+                        variable->vartypmod != -1))
+               {
+                       isVarList = false;
+                       break;
+               }
        }
        projInfo->pi_isVarList = isVarList;
 
@@ -689,15 +715,20 @@ ExecBuildProjectionInfo(List *targetList,
  *             ExecAssignProjectionInfo
  *
  * forms the projection information from the node's targetlist
+ *
+ * Notes for inputDesc are same as for ExecBuildProjectionInfo: supply it
+ * for a relation-scan node, can pass NULL for upper-level nodes
  * ----------------
  */
 void
-ExecAssignProjectionInfo(PlanState *planstate)
+ExecAssignProjectionInfo(PlanState *planstate,
+                                                TupleDesc inputDesc)
 {
        planstate->ps_ProjInfo =
                ExecBuildProjectionInfo(planstate->targetlist,
                                                                planstate->ps_ExprContext,
-                                                               planstate->ps_ResultTupleSlot);
+                                                               planstate->ps_ResultTupleSlot,
+                                                               inputDesc);
 }
 
 
index 00fb3b86e7172554d989ae3ae53331761e47bc47..f6ee9b275e716e3fb2fa9574973e8531f502ec2a 100644 (file)
@@ -61,7 +61,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/executor/nodeAgg.c,v 1.149 2007/01/10 18:06:02 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/executor/nodeAgg.c,v 1.150 2007/02/02 00:07:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1243,7 +1243,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
         * Initialize result tuple type and projection info.
         */
        ExecAssignResultTypeFromTL(&aggstate->ss.ps);
-       ExecAssignProjectionInfo(&aggstate->ss.ps);
+       ExecAssignProjectionInfo(&aggstate->ss.ps, NULL);
 
        /*
         * get the count of aggregates in targetlist and quals
index da4b2bcdb4972e92174c634277a991fedecbdf92..0b636aa01e4e7298c3650b1546f68754e665f6d3 100644 (file)
@@ -15,7 +15,7 @@
  *       locate group boundaries.
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/executor/nodeGroup.c,v 1.67 2007/01/10 18:06:02 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/executor/nodeGroup.c,v 1.68 2007/02/02 00:07:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -205,7 +205,7 @@ ExecInitGroup(Group *node, EState *estate, int eflags)
         * Initialize result tuple type and projection info.
         */
        ExecAssignResultTypeFromTL(&grpstate->ss.ps);
-       ExecAssignProjectionInfo(&grpstate->ss.ps);
+       ExecAssignProjectionInfo(&grpstate->ss.ps, NULL);
 
        /*
         * Precompute fmgr lookup data for inner loop
index 4960e2d8c697e5a7f86b69dec5e42aec76c9342b..f75a09e717fccbeae5625949f7cced05f3414e85 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/executor/nodeHashjoin.c,v 1.88 2007/01/30 01:33:36 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/executor/nodeHashjoin.c,v 1.89 2007/02/02 00:07:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -431,7 +431,7 @@ ExecInitHashJoin(HashJoin *node, EState *estate, int eflags)
         * initialize tuple type and projection info
         */
        ExecAssignResultTypeFromTL(&hjstate->js.ps);
-       ExecAssignProjectionInfo(&hjstate->js.ps);
+       ExecAssignProjectionInfo(&hjstate->js.ps, NULL);
 
        ExecSetSlotDescriptor(hjstate->hj_OuterTupleSlot,
                                                  ExecGetResultType(outerPlanState(hjstate)));
index 6e820d7ad2a1d5b9d034cf94599028847d2b064a..ad4aace6536363a1228c94263781877e070cd325 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/executor/nodeMergejoin.c,v 1.86 2007/01/11 17:19:13 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/executor/nodeMergejoin.c,v 1.87 2007/02/02 00:07:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1493,7 +1493,7 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags)
         * initialize tuple type and projection info
         */
        ExecAssignResultTypeFromTL(&mergestate->js.ps);
-       ExecAssignProjectionInfo(&mergestate->js.ps);
+       ExecAssignProjectionInfo(&mergestate->js.ps, NULL);
 
        /*
         * preprocess the merge clauses
index dd5ef957d057163161bc3a91a68c5419acf5f7dc..8398cc5724caf67dd0bd4bdf335f7837279d1d5d 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/executor/nodeNestloop.c,v 1.44 2007/01/05 22:19:28 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/executor/nodeNestloop.c,v 1.45 2007/02/02 00:07:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -349,7 +349,7 @@ ExecInitNestLoop(NestLoop *node, EState *estate, int eflags)
         * initialize tuple type and projection info
         */
        ExecAssignResultTypeFromTL(&nlstate->js.ps);
-       ExecAssignProjectionInfo(&nlstate->js.ps);
+       ExecAssignProjectionInfo(&nlstate->js.ps, NULL);
 
        /*
         * finally, wipe the current outer tuple clean.
index 32b7a7a521a18c6271701ed7603d04f900ac45f7..9765103cbe2df36d6c5e7df2c3856047027a4ad5 100644 (file)
@@ -38,7 +38,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/executor/nodeResult.c,v 1.36 2007/01/05 22:19:28 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/executor/nodeResult.c,v 1.37 2007/02/02 00:07:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -235,7 +235,7 @@ ExecInitResult(Result *node, EState *estate, int eflags)
         * initialize tuple type and projection info
         */
        ExecAssignResultTypeFromTL(&resstate->ps);
-       ExecAssignProjectionInfo(&resstate->ps);
+       ExecAssignProjectionInfo(&resstate->ps, NULL);
 
        return resstate;
 }
index c5c35697c0b08ab6a6dc22d78e116c38e8a72e6c..0e840802eb0fcd138692e5b31e1c2923f1f7a1af 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/executor/nodeSubplan.c,v 1.83 2007/01/30 01:33:36 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/executor/nodeSubplan.c,v 1.84 2007/02/02 00:07:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -865,14 +865,16 @@ ExecInitSubPlan(SubPlanState *node, EState *estate, int eflags)
                ExecSetSlotDescriptor(slot, tupDesc);
                node->projLeft = ExecBuildProjectionInfo(lefttlist,
                                                                                                 NULL,
-                                                                                                slot);
+                                                                                                slot,
+                                                                                                NULL);
 
                tupDesc = ExecTypeFromTL(rightptlist, false);
                slot = ExecAllocTableSlot(tupTable);
                ExecSetSlotDescriptor(slot, tupDesc);
                node->projRight = ExecBuildProjectionInfo(righttlist,
                                                                                                  node->innerecontext,
-                                                                                                 slot);
+                                                                                                 slot,
+                                                                                                 NULL);
        }
 }
 
index 6e099374cff9d84a552f491ae92b861b67a45977..1800f4cb039999c22a32683e86a5d740d028301b 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.134 2007/01/10 18:06:04 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.135 2007/02/02 00:07:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -257,8 +257,10 @@ extern void ExecAssignResultTypeFromTL(PlanState *planstate);
 extern TupleDesc ExecGetResultType(PlanState *planstate);
 extern ProjectionInfo *ExecBuildProjectionInfo(List *targetList,
                                                ExprContext *econtext,
-                                               TupleTableSlot *slot);
-extern void ExecAssignProjectionInfo(PlanState *planstate);
+                                               TupleTableSlot *slot,
+                                               TupleDesc inputDesc);
+extern void ExecAssignProjectionInfo(PlanState *planstate,
+                                                                        TupleDesc inputDesc);
 extern void ExecFreeExprContext(PlanState *planstate);
 extern TupleDesc ExecGetScanType(ScanState *scanstate);
 extern void ExecAssignScanType(ScanState *scanstate, TupleDesc tupDesc);