diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 6ac61d3c5beb12389149bdea69e9a33ef7607fdf..7394b3d32a91075376eccd58764dae02a4be5615 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.165 2004/08/02 01:30:41 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.166 2004/08/17 18:47:08 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -44,6 +44,7 @@ #include "executor/nodeSubplan.h" #include "funcapi.h" #include "miscadmin.h" +#include "nodes/makefuncs.h" #include "optimizer/planmain.h" #include "parser/parse_expr.h" #include "utils/acl.h" @@ -2096,7 +2097,7 @@ ExecEvalRow(RowExprState *rstate, HeapTuple tuple; Datum *values; char *nulls; - int nargs; + int natts; ListCell *arg; int i; @@ -2106,9 +2107,12 @@ ExecEvalRow(RowExprState *rstate, *isDone = ExprSingleResult; /* Allocate workspace */ - nargs = list_length(rstate->args); - values = (Datum *) palloc(nargs * sizeof(Datum)); - nulls = (char *) palloc(nargs * sizeof(char)); + natts = rstate->tupdesc->natts; + values = (Datum *) palloc0(natts * sizeof(Datum)); + nulls = (char *) palloc(natts * sizeof(char)); + + /* preset to nulls in case rowtype has some later-added columns */ + memset(nulls, 'n', natts * sizeof(char)); /* Evaluate field values */ i = 0; @@ -2979,19 +2983,12 @@ ExecInitExpr(Expr *node, PlanState *parent) { RowExpr *rowexpr = (RowExpr *) node; RowExprState *rstate = makeNode(RowExprState); + Form_pg_attribute *attrs; List *outlist = NIL; ListCell *l; + int i; rstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalRow; - foreach(l, rowexpr->args) - { - Expr *e = (Expr *) lfirst(l); - ExprState *estate; - - estate = ExecInitExpr(e, parent); - outlist = lappend(outlist, estate); - } - rstate->args = outlist; /* Build tupdesc to describe result tuples */ if (rowexpr->row_typeid == RECORDOID) { @@ -3003,7 +3000,46 @@ ExecInitExpr(Expr *node, PlanState *parent) { /* it's been cast to a named type, use that */ rstate->tupdesc = lookup_rowtype_tupdesc(rowexpr->row_typeid, -1); + rstate->tupdesc = CreateTupleDescCopy(rstate->tupdesc); } + /* Set up evaluation, skipping any deleted columns */ + Assert(list_length(rowexpr->args) <= rstate->tupdesc->natts); + attrs = rstate->tupdesc->attrs; + i = 0; + foreach(l, rowexpr->args) + { + Expr *e = (Expr *) lfirst(l); + ExprState *estate; + + if (!attrs[i]->attisdropped) + { + /* + * Guard against ALTER COLUMN TYPE on rowtype + * since the RowExpr was created. XXX should we + * check typmod too? Not sure we can be sure it'll + * be the same. + */ + if (exprType((Node *) e) != attrs[i]->atttypid) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("ROW() column has type %s instead of type %s", + format_type_be(exprType((Node *) e)), + format_type_be(attrs[i]->atttypid)))); + } + else + { + /* + * Ignore original expression and insert a NULL. + * We don't really care what type of NULL it is, + * so always make an int4 NULL. + */ + e = (Expr *) makeNullConst(INT4OID); + } + estate = ExecInitExpr(e, parent); + outlist = lappend(outlist, estate); + i++; + } + rstate->args = outlist; state = (ExprState *) rstate; } break; diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 68d2529889e86d7c884acb63c4ed8fdcb1a3a0e7..f941375127c4cd4397019b9c064193b375a5ae66 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.177 2004/08/02 01:30:43 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.178 2004/08/17 18:47:08 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -40,6 +40,7 @@ #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/syscache.h" +#include "utils/typcache.h" typedef struct @@ -1054,6 +1055,33 @@ set_coercionform_dontcare_walker(Node *node, void *context) context); } +/* + * Helper for eval_const_expressions: check that datatype of an attribute + * is still what it was when the expression was parsed. This is needed to + * guard against improper simplification after ALTER COLUMN TYPE. (XXX we + * may well need to make similar checks elsewhere?) + */ +static bool +rowtype_field_matches(Oid rowtypeid, int fieldnum, + Oid expectedtype, int32 expectedtypmod) +{ + TupleDesc tupdesc; + Form_pg_attribute attr; + + /* No issue for RECORD, since there is no way to ALTER such a type */ + if (rowtypeid == RECORDOID) + return true; + tupdesc = lookup_rowtype_tupdesc(rowtypeid, -1); + if (fieldnum <= 0 || fieldnum > tupdesc->natts) + return false; + attr = tupdesc->attrs[fieldnum - 1]; + if (attr->attisdropped || + attr->atttypid != expectedtype || + attr->atttypmod != expectedtypmod) + return false; + return true; +} + /*-------------------- * eval_const_expressions @@ -1630,6 +1658,10 @@ eval_const_expressions_mutator(Node *node, * parser, because ParseComplexProjection short-circuits it. But * it can arise while simplifying functions.) Also, we can * optimize field selection from a RowExpr construct. + * + * We must however check that the declared type of the field is + * still the same as when the FieldSelect was created --- this + * can change if someone did ALTER COLUMN TYPE on the rowtype. */ FieldSelect *fselect = (FieldSelect *) node; FieldSelect *newfselect; @@ -1640,11 +1672,15 @@ eval_const_expressions_mutator(Node *node, if (arg && IsA(arg, Var) && ((Var *) arg)->varattno == InvalidAttrNumber) { - return (Node *) makeVar(((Var *) arg)->varno, - fselect->fieldnum, - fselect->resulttype, - fselect->resulttypmod, - ((Var *) arg)->varlevelsup); + if (rowtype_field_matches(((Var *) arg)->vartype, + fselect->fieldnum, + fselect->resulttype, + fselect->resulttypmod)) + return (Node *) makeVar(((Var *) arg)->varno, + fselect->fieldnum, + fselect->resulttype, + fselect->resulttypmod, + ((Var *) arg)->varlevelsup); } if (arg && IsA(arg, RowExpr)) { @@ -1652,7 +1688,18 @@ eval_const_expressions_mutator(Node *node, if (fselect->fieldnum > 0 && fselect->fieldnum <= list_length(rowexpr->args)) - return (Node *) list_nth(rowexpr->args, fselect->fieldnum - 1); + { + Node *fld = (Node *) list_nth(rowexpr->args, + fselect->fieldnum - 1); + + if (rowtype_field_matches(rowexpr->row_typeid, + fselect->fieldnum, + fselect->resulttype, + fselect->resulttypmod) && + fselect->resulttype == exprType(fld) && + fselect->resulttypmod == exprTypmod(fld)) + return fld; + } } newfselect = makeNode(FieldSelect); newfselect->arg = (Expr *) arg; diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c index 3878d07d5a189f8be32b41bb0892b06676359e45..8065b261beb1935c7592d81c6c40843e31470b3c 100644 --- a/src/backend/parser/parse_coerce.c +++ b/src/backend/parser/parse_coerce.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_coerce.c,v 2.119 2004/06/16 01:26:44 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_coerce.c,v 2.120 2004/08/17 18:47:08 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -648,10 +648,15 @@ coerce_record_to_complex(ParseState *pstate, Node *node, List *args = NIL; List *newargs; int i; + int ucolno; ListCell *arg; if (node && IsA(node, RowExpr)) { + /* + * Since the RowExpr must be of type RECORD, we needn't worry + * about it containing any dropped columns. + */ args = ((RowExpr *) node)->args; } else if (node && IsA(node, Var) && @@ -670,6 +675,8 @@ coerce_record_to_complex(ParseState *pstate, Node *node, Oid vartype; int32 vartypmod; + if (get_rte_attribute_is_dropped(rte, nf)) + continue; get_rte_attribute_type(rte, nf, &vartype, &vartypmod); args = lappend(args, makeVar(((Var *) node)->varno, @@ -687,19 +694,34 @@ coerce_record_to_complex(ParseState *pstate, Node *node, format_type_be(targetTypeId)))); tupdesc = lookup_rowtype_tupdesc(targetTypeId, -1); - if (list_length(args) != tupdesc->natts) - ereport(ERROR, - (errcode(ERRCODE_CANNOT_COERCE), - errmsg("cannot cast type %s to %s", - format_type_be(RECORDOID), - format_type_be(targetTypeId)), - errdetail("Input has wrong number of columns."))); newargs = NIL; - i = 0; - foreach(arg, args) + ucolno = 1; + arg = list_head(args); + for (i = 0; i < tupdesc->natts; i++) { - Node *expr = (Node *) lfirst(arg); - Oid exprtype = exprType(expr); + Node *expr; + Oid exprtype; + + /* Fill in NULLs for dropped columns in rowtype */ + if (tupdesc->attrs[i]->attisdropped) + { + /* + * can't use atttypid here, but it doesn't really matter + * what type the Const claims to be. + */ + newargs = lappend(newargs, makeNullConst(INT4OID)); + continue; + } + + if (arg == NULL) + ereport(ERROR, + (errcode(ERRCODE_CANNOT_COERCE), + errmsg("cannot cast type %s to %s", + format_type_be(RECORDOID), + format_type_be(targetTypeId)), + errdetail("Input has too few columns."))); + expr = (Node *) lfirst(arg); + exprtype = exprType(expr); expr = coerce_to_target_type(pstate, expr, exprtype, @@ -716,10 +738,18 @@ coerce_record_to_complex(ParseState *pstate, Node *node, errdetail("Cannot cast type %s to %s in column %d.", format_type_be(exprtype), format_type_be(tupdesc->attrs[i]->atttypid), - i + 1))); + ucolno))); newargs = lappend(newargs, expr); - i++; + ucolno++; + arg = lnext(arg); } + if (arg != NULL) + ereport(ERROR, + (errcode(ERRCODE_CANNOT_COERCE), + errmsg("cannot cast type %s to %s", + format_type_be(RECORDOID), + format_type_be(targetTypeId)), + errdetail("Input has too many columns."))); rowexpr = makeNode(RowExpr); rowexpr->args = newargs; diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index fcec2cb39dd3de58274e3ea38afc4845a486fb2f..3f32f8c80f5340f7aac15270e9b05e96a142f560 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_relation.c,v 1.96 2004/05/30 23:40:35 neilc Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_relation.c,v 1.97 2004/08/17 18:47:08 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -42,8 +42,6 @@ static Node *scanNameSpaceForRelid(ParseState *pstate, Node *nsnode, static void scanNameSpaceForConflict(ParseState *pstate, Node *nsnode, RangeTblEntry *rte1, const char *aliasname1); static bool isForUpdate(ParseState *pstate, char *refname); -static bool get_rte_attribute_is_dropped(RangeTblEntry *rte, - AttrNumber attnum); static int specialAttNum(const char *attname); static void warnAutoRange(ParseState *pstate, RangeVar *relation); @@ -1699,7 +1697,7 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum, * get_rte_attribute_is_dropped * Check whether attempted attribute ref is to a dropped column */ -static bool +bool get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum) { bool result; diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c index d8cd7de5efa7ca4ffb0a39298eb46f74212227af..86412e90634adf34a0c47e82c09c23d0d1d39320 100644 --- a/src/backend/rewrite/rewriteManip.c +++ b/src/backend/rewrite/rewriteManip.c @@ -7,12 +7,13 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/rewrite/rewriteManip.c,v 1.84 2004/05/30 23:40:35 neilc Exp $ + * $PostgreSQL: pgsql/src/backend/rewrite/rewriteManip.c,v 1.85 2004/08/17 18:47:09 tgl Exp $ * *------------------------------------------------------------------------- */ #include "postgres.h" +#include "catalog/pg_type.h" #include "nodes/makefuncs.h" #include "optimizer/clauses.h" #include "optimizer/tlist.h" @@ -938,18 +939,30 @@ ResolveNew_mutator(Node *node, ResolveNew_context *context) for (nf = 1; nf <= nfields; nf++) { - Oid vartype; - int32 vartypmod; - Var *newvar; - - get_rte_attribute_type(rte, nf, &vartype, &vartypmod); - newvar = makeVar(this_varno, - nf, - vartype, - vartypmod, - this_varlevelsup); - fields = lappend(fields, - resolve_one_var(newvar, context)); + if (get_rte_attribute_is_dropped(rte, nf)) + { + /* + * can't determine att type here, but it doesn't + * really matter what type the Const claims to be. + */ + fields = lappend(fields, + makeNullConst(INT4OID)); + } + else + { + Oid vartype; + int32 vartypmod; + Var *newvar; + + get_rte_attribute_type(rte, nf, &vartype, &vartypmod); + newvar = makeVar(this_varno, + nf, + vartype, + vartypmod, + this_varlevelsup); + fields = lappend(fields, + resolve_one_var(newvar, context)); + } } rowexpr = makeNode(RowExpr); diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index e3b9a4e068a9f62f127d93131e166bbc256e4094..8e1420d9267b037212034c5d83a08a96f32be277 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -3,7 +3,7 @@ * back to source text * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.176 2004/08/02 04:27:15 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.177 2004/08/17 18:47:09 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -3283,22 +3283,54 @@ get_rule_expr(Node *node, deparse_context *context, case T_RowExpr: { RowExpr *rowexpr = (RowExpr *) node; + TupleDesc tupdesc = NULL; ListCell *arg; + int i; char *sep; /* - * SQL99 allows "ROW" to be omitted when list_length(args) > 1, - * but for simplicity we always print it. + * If it's a named type and not RECORD, we may have to skip + * dropped columns and/or claim there are NULLs for added + * columns. + */ + if (rowexpr->row_typeid != RECORDOID) + { + tupdesc = lookup_rowtype_tupdesc(rowexpr->row_typeid, -1); + Assert(list_length(rowexpr->args) <= tupdesc->natts); + } + + /* + * SQL99 allows "ROW" to be omitted when there is more than + * one column, but for simplicity we always print it. */ appendStringInfo(buf, "ROW("); sep = ""; + i = 0; foreach(arg, rowexpr->args) { Node *e = (Node *) lfirst(arg); - appendStringInfo(buf, sep); - get_rule_expr(e, context, true); - sep = ", "; + if (tupdesc == NULL || + !tupdesc->attrs[i]->attisdropped) + { + appendStringInfo(buf, sep); + get_rule_expr(e, context, true); + sep = ", "; + } + i++; + } + if (tupdesc != NULL) + { + while (i < tupdesc->natts) + { + if (!tupdesc->attrs[i]->attisdropped) + { + appendStringInfo(buf, sep); + appendStringInfo(buf, "NULL"); + sep = ", "; + } + i++; + } } appendStringInfo(buf, ")"); if (rowexpr->row_format == COERCE_EXPLICIT_CAST) diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 3ed4d74ee3546cb3d270650f23320805bb5e0c89..5abdbd548fd3fc40e80e8e06fdbe6e5e573e2ccf 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -10,7 +10,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.100 2004/06/09 19:08:18 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.101 2004/08/17 18:47:09 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -97,13 +97,16 @@ typedef struct Resdom * Alias - * specifies an alias for a range variable; the alias might also * specify renaming of columns within the table. + * + * Note: colnames is a list of Value nodes (always strings). In an RTE's + * eref Alias, the colnames list includes dropped columns, so that the + * colname list position matches the physical attribute number. */ typedef struct Alias { NodeTag type; char *aliasname; /* aliased rel name (never qualified) */ List *colnames; /* optional list of column aliases */ - /* Note: colnames is a list of Value nodes (always strings) */ } Alias; typedef enum InhOption @@ -663,6 +666,16 @@ typedef struct ArrayExpr /* * RowExpr - a ROW() expression + * + * Note: the list of fields must have a one-for-one correspondence with + * physical fields of the associated rowtype, although it is okay for it + * to be shorter than the rowtype. That is, the N'th list element must + * match up with the N'th physical field. When the N'th physical field + * is a dropped column (attisdropped) then the N'th list element can just + * be a NULL constant. (This case can only occur for named composite types, + * not RECORD types, since those are built from the RowExpr itself rather + * than vice versa.) It is important not to assume that length(args) is + * the same as the number of columns logically present in the rowtype. */ typedef struct RowExpr { diff --git a/src/include/parser/parsetree.h b/src/include/parser/parsetree.h index 9b488c397ac7c625e9583de2b110de73b04e023a..e7f401ac315c896d4a12d4b17b31c019898c442b 100644 --- a/src/include/parser/parsetree.h +++ b/src/include/parser/parsetree.h @@ -8,7 +8,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/parser/parsetree.h,v 1.24 2004/05/26 04:41:46 neilc Exp $ + * $PostgreSQL: pgsql/src/include/parser/parsetree.h,v 1.25 2004/08/17 18:47:09 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -55,6 +55,13 @@ extern char *get_rte_attribute_name(RangeTblEntry *rte, AttrNumber attnum); extern void get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum, Oid *vartype, int32 *vartypmod); +/* + * Check whether an attribute of an RTE has been dropped (note that + * get_rte_attribute_type will fail on such an attr) + */ +extern bool get_rte_attribute_is_dropped(RangeTblEntry *rte, + AttrNumber attnum); + /* ---------------- * target list operations