diff options
Diffstat (limited to 'src/backend')
-rw-r--r-- | src/backend/commands/tablecmds.c | 57 | ||||
-rw-r--r-- | src/backend/executor/execQual.c | 277 | ||||
-rw-r--r-- | src/backend/executor/execScan.c | 9 |
3 files changed, 262 insertions, 81 deletions
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index cf665a6eb4f..ea62f7db454 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.142.4.6 2006/07/10 22:10:57 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.142.4.7 2007/02/02 00:08:01 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1812,22 +1812,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 @@ -2714,6 +2739,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); } @@ -2745,6 +2776,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); } @@ -3568,6 +3605,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 */ diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 91271423b0f..be3e1c12188 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.171.4.2 2006/11/06 18:21:47 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.171.4.3 2007/02/02 00:08:01 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -63,6 +63,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, @@ -433,6 +435,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 @@ -440,17 +446,15 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone) { Var *variable = (Var *) exprstate->expr; - Datum result; TupleTableSlot *slot; AttrNumber attnum; - HeapTuple heapTuple; - TupleDesc tuple_type; + TupleDesc slot_tupdesc; if (isDone) *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 @@ -480,63 +484,194 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext, /* * extract tuple information from the slot */ - heapTuple = slot->val; - tuple_type = slot->ttc_tupleDescriptor; + slot_tupdesc = slot->ttc_tupleDescriptor; - /* - * Some checks that are only applied for user attribute numbers (bogus - * system attnums will be caught inside heap_getattr). - */ - if (attnum > 0) + if (attnum != InvalidAttrNumber) { /* - * This assert checks that the attnum is valid. - */ - Assert(attnum <= tuple_type->natts && - tuple_type->attrs[attnum - 1] != NULL); - - /* - * If the attribute's column has been dropped, we force a NULL - * result. This case should not happen in normal use, but it could - * happen if we are executing a plan cached before the column was - * dropped. + * Scalar variable case. + * + * If it's a user attribute, check validity (bogus system attnums will + * be caught inside heap_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, and force a + * NULL result. This path is not fast. + * + * Note: we check typmod, but allow the case that the Var has + * unspecified typmod while the column has a specific typmod. */ - if (tuple_type->attrs[attnum - 1]->attisdropped) + if (attnum > 0) { - *isNull = true; - return (Datum) 0; + 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]; + + /* + * If the attribute's column has been dropped, we force a NULL + * result. This case should not happen in normal use, but it could + * happen if we are executing a plan cached before the column was + * dropped. + * + * Can't check type if dropped, since atttypid is probably 0 + */ + if (attr->attisdropped) + { + *isNull = true; + return (Datum) 0; + } + + 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 */ + return ExecEvalScalarVar(exprstate, econtext, isNull, isDone); + } + 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); + 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))); + } + } + + /* Skip the checking on future executions of node */ + exprstate->evalfunc = ExecEvalWholeRowVar; + + /* Fetch the value */ + return ExecEvalWholeRowVar(exprstate, econtext, isNull, isDone); } +} - result = heap_getattr(heapTuple, /* tuple containing attribute */ - attnum, /* attribute number of desired - * attribute */ - tuple_type, /* tuple descriptor of tuple */ - isNull); /* return: is attribute null? */ +/* ---------------------------------------------------------------- + * 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; + HeapTuple heapTuple; + TupleDesc slot_tupdesc; - return result; + 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; + + /* extract tuple information from the slot */ + heapTuple = slot->val; + slot_tupdesc = slot->ttc_tupleDescriptor; + + /* Fetch the value from the tuple */ + return heap_getattr(heapTuple, /* tuple containing attribute */ + attnum, /* attribute number of desired + * attribute */ + slot_tupdesc, /* tuple descriptor of tuple */ + isNull); /* return: is attribute null? */ } /* ---------------------------------------------------------------- * 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 @@ -544,7 +679,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; @@ -553,16 +688,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 = slot->val; tupleDesc = slot->ttc_tupleDescriptor; @@ -578,10 +703,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) { @@ -590,9 +711,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); } @@ -2686,12 +2804,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); @@ -2723,6 +2843,28 @@ ExecEvalFieldSelect(FieldSelectState *fstate, MemoryContextSwitchTo(oldcontext); } + /* 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 @@ -2734,7 +2876,7 @@ ExecEvalFieldSelect(FieldSelectState *fstate, tmptup.t_data = tuple; result = heap_getattr(&tmptup, - fselect->fieldnum, + fieldnum, tupDesc, isNull); return result; @@ -2936,15 +3078,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); diff --git a/src/backend/executor/execScan.c b/src/backend/executor/execScan.c index 538b0faf370..b3053f06460 100644 --- a/src/backend/executor/execScan.c +++ b/src/backend/executor/execScan.c @@ -12,7 +12,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/execScan.c,v 1.34.4.1 2007/01/24 01:26:02 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execScan.c,v 1.34.4.2 2007/02/02 00:08:01 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -208,6 +208,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) @@ -224,8 +225,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); } |