I wrote: > After some reflection I think that the best fix is to redefine > AcquireRewriteLocks' processing of dropped columns so that it puts an > actual null pointer, not a consed-up Const, into the joinaliasvars list > item.
Here's a complete patch along that line. Possibly worthy of note is that I chose to keep expandRTE's API the same as before, ie, it returns a NULL Const for a dropped column (when include_dropped is true). I had intended to change it to return a null pointer to match the change in the underlying data structure, but found that most of the callers passing include_dropped = true need the Consts, because they're going to construct a RowExpr that has to have null constants for the dropped columns. In HEAD, I'm a bit tempted to move strip_implicit_coercions into nodes/nodeFuncs.c, so that we don't have the ugliness of the parser calling an optimizer subroutine. But I propose applying this to back branches as-is. regards, tom lane
diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c index 7eaf8d27bf08bf5dd1776d203876adb8396c73b3..5f736ad6c4060ff4d7dabb3844a04185e01fa3ef 100644 *** a/src/backend/optimizer/util/var.c --- b/src/backend/optimizer/util/var.c *************** flatten_join_alias_vars_mutator(Node *no *** 654,660 **** newvar = (Node *) lfirst(lv); attnum++; /* Ignore dropped columns */ ! if (IsA(newvar, Const)) continue; newvar = copyObject(newvar); --- 654,660 ---- newvar = (Node *) lfirst(lv); attnum++; /* Ignore dropped columns */ ! if (newvar == NULL) continue; newvar = copyObject(newvar); *************** flatten_join_alias_vars_mutator(Node *no *** 687,692 **** --- 687,693 ---- /* Expand join alias reference */ Assert(var->varattno > 0); newvar = (Node *) list_nth(rte->joinaliasvars, var->varattno - 1); + Assert(newvar != NULL); newvar = copyObject(newvar); /* diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index a9254c8c3a2e33b7c293ef51c53c78a797b1d4f1..42de89f510190877b1f6fa357efb08c81eb7acc9 100644 *** a/src/backend/parser/parse_relation.c --- b/src/backend/parser/parse_relation.c *************** *** 24,29 **** --- 24,30 ---- #include "funcapi.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" + #include "optimizer/clauses.h" #include "parser/parsetree.h" #include "parser/parse_relation.h" #include "parser/parse_type.h" *************** markRTEForSelectPriv(ParseState *pstate, *** 749,762 **** * The aliasvar could be either a Var or a COALESCE expression, * but in the latter case we should already have marked the two * referent variables as being selected, due to their use in the ! * JOIN clause. So we need only be concerned with the simple Var ! * case. */ Var *aliasvar; Assert(col > 0 && col <= list_length(rte->joinaliasvars)); aliasvar = (Var *) list_nth(rte->joinaliasvars, col - 1); ! if (IsA(aliasvar, Var)) markVarForSelectPriv(pstate, aliasvar, NULL); } } --- 750,764 ---- * The aliasvar could be either a Var or a COALESCE expression, * but in the latter case we should already have marked the two * referent variables as being selected, due to their use in the ! * JOIN clause. So we need only be concerned with the Var case. ! * But we do need to drill down through implicit coercions. */ Var *aliasvar; Assert(col > 0 && col <= list_length(rte->joinaliasvars)); aliasvar = (Var *) list_nth(rte->joinaliasvars, col - 1); ! aliasvar = (Var *) strip_implicit_coercions((Node *) aliasvar); ! if (aliasvar && IsA(aliasvar, Var)) markVarForSelectPriv(pstate, aliasvar, NULL); } } *************** expandRTE(RangeTblEntry *rte, int rtinde *** 1841,1850 **** * deleted columns in the join; but we have to check since * this routine is also used by the rewriter, and joins * found in stored rules might have join columns for ! * since-deleted columns. This will be signaled by a NULL ! * Const in the alias-vars list. */ ! if (IsA(avar, Const)) { if (include_dropped) { --- 1843,1852 ---- * deleted columns in the join; but we have to check since * this routine is also used by the rewriter, and joins * found in stored rules might have join columns for ! * since-deleted columns. This will be signaled by a null ! * pointer in the alias-vars list. */ ! if (avar == NULL) { if (include_dropped) { *************** expandRTE(RangeTblEntry *rte, int rtinde *** 1852,1859 **** *colnames = lappend(*colnames, makeString(pstrdup(""))); if (colvars) *colvars = lappend(*colvars, ! copyObject(avar)); } continue; } --- 1854,1869 ---- *colnames = lappend(*colnames, makeString(pstrdup(""))); if (colvars) + { + /* + * Can't use join's column type here (it might + * be dropped!); but it doesn't really matter + * what type the Const claims to be. + */ *colvars = lappend(*colvars, ! makeNullConst(INT4OID, -1, ! InvalidOid)); ! } } continue; } *************** get_rte_attribute_type(RangeTblEntry *rt *** 2242,2247 **** --- 2252,2258 ---- Assert(attnum > 0 && attnum <= list_length(rte->joinaliasvars)); aliasvar = (Node *) list_nth(rte->joinaliasvars, attnum - 1); + Assert(aliasvar != NULL); *vartype = exprType(aliasvar); *vartypmod = exprTypmod(aliasvar); *varcollid = exprCollation(aliasvar); *************** get_rte_attribute_is_dropped(RangeTblEnt *** 2304,2310 **** * but one in a stored rule might contain columns that were * dropped from the underlying tables, if said columns are * nowhere explicitly referenced in the rule. This will be ! * signaled to us by a NULL Const in the joinaliasvars list. */ Var *aliasvar; --- 2315,2321 ---- * but one in a stored rule might contain columns that were * dropped from the underlying tables, if said columns are * nowhere explicitly referenced in the rule. This will be ! * signaled to us by a null pointer in the joinaliasvars list. */ Var *aliasvar; *************** get_rte_attribute_is_dropped(RangeTblEnt *** 2313,2319 **** elog(ERROR, "invalid varattno %d", attnum); aliasvar = (Var *) list_nth(rte->joinaliasvars, attnum - 1); ! result = IsA(aliasvar, Const); } break; case RTE_FUNCTION: --- 2324,2330 ---- elog(ERROR, "invalid varattno %d", attnum); aliasvar = (Var *) list_nth(rte->joinaliasvars, attnum - 1); ! result = (aliasvar == NULL); } break; case RTE_FUNCTION: diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index ca20e77ce6d1b09ff0a2012f7d3084d33c40543d..9c6c202c8e6ef16a981fdafa8945741ca70cd709 100644 *** a/src/backend/parser/parse_target.c --- b/src/backend/parser/parse_target.c *************** markTargetListOrigin(ParseState *pstate, *** 311,316 **** --- 311,317 ---- Assert(attnum > 0 && attnum <= list_length(rte->joinaliasvars)); aliasvar = (Var *) list_nth(rte->joinaliasvars, attnum - 1); + /* We intentionally don't strip implicit coercions here */ markTargetListOrigin(pstate, tle, aliasvar, netlevelsup); } break; *************** expandRecordVariable(ParseState *pstate, *** 1461,1466 **** --- 1462,1469 ---- /* Join RTE --- recursively inspect the alias variable */ Assert(attnum > 0 && attnum <= list_length(rte->joinaliasvars)); expr = (Node *) list_nth(rte->joinaliasvars, attnum - 1); + Assert(expr != NULL); + /* We intentionally don't strip implicit coercions here */ if (IsA(expr, Var)) return expandRecordVariable(pstate, (Var *) expr, netlevelsup); /* else fall through to inspect the expression */ diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index 7f527bd74a281247616ac84870940157c82d76ad..3c7974adc72152ba4640baa95c4aed1ed15c3d9a 100644 *** a/src/backend/rewrite/rewriteHandler.c --- b/src/backend/rewrite/rewriteHandler.c *************** static Query *fireRIRrules(Query *parset *** 91,99 **** * such a list in a stored rule to include references to dropped columns. * (If the column is not explicitly referenced anywhere else in the query, * the dependency mechanism won't consider it used by the rule and so won't ! * prevent the column drop.) To support get_rte_attribute_is_dropped(), ! * we replace join alias vars that reference dropped columns with NULL Const ! * nodes. * * (In PostgreSQL 8.0, we did not do this processing but instead had * get_rte_attribute_is_dropped() recurse to detect dropped columns in joins. --- 91,98 ---- * such a list in a stored rule to include references to dropped columns. * (If the column is not explicitly referenced anywhere else in the query, * the dependency mechanism won't consider it used by the rule and so won't ! * prevent the column drop.) To support get_rte_attribute_is_dropped(), we ! * replace join alias vars that reference dropped columns with null pointers. * * (In PostgreSQL 8.0, we did not do this processing but instead had * get_rte_attribute_is_dropped() recurse to detect dropped columns in joins. *************** AcquireRewriteLocks(Query *parsetree, bo *** 160,167 **** /* * Scan the join's alias var list to see if any columns have ! * been dropped, and if so replace those Vars with NULL ! * Consts. * * Since a join has only two inputs, we can expect to see * multiple references to the same input RTE; optimize away --- 159,166 ---- /* * Scan the join's alias var list to see if any columns have ! * been dropped, and if so replace those Vars with null ! * pointers. * * Since a join has only two inputs, we can expect to see * multiple references to the same input RTE; optimize away *************** AcquireRewriteLocks(Query *parsetree, bo *** 172,187 **** curinputrte = NULL; foreach(ll, rte->joinaliasvars) { ! Var *aliasvar = (Var *) lfirst(ll); /* * If the list item isn't a simple Var, then it must * represent a merged column, ie a USING column, and so it * couldn't possibly be dropped, since it's referenced in ! * the join clause. (Conceivably it could also be a NULL ! * constant already? But that's OK too.) */ ! if (IsA(aliasvar, Var)) { /* * The elements of an alias list have to refer to --- 171,190 ---- curinputrte = NULL; foreach(ll, rte->joinaliasvars) { ! Var *aliasitem = (Var *) lfirst(ll); ! Var *aliasvar = aliasitem; ! ! /* Look through any implicit coercion */ ! aliasvar = (Var *) strip_implicit_coercions((Node *) aliasvar); /* * If the list item isn't a simple Var, then it must * represent a merged column, ie a USING column, and so it * couldn't possibly be dropped, since it's referenced in ! * the join clause. (Conceivably it could also be a null ! * pointer already? But that's OK too.) */ ! if (aliasvar && IsA(aliasvar, Var)) { /* * The elements of an alias list have to refer to *************** AcquireRewriteLocks(Query *parsetree, bo *** 205,219 **** if (get_rte_attribute_is_dropped(curinputrte, aliasvar->varattno)) { ! /* ! * can't use vartype here, since that might be a ! * now-dropped type OID, but it doesn't really ! * matter what type the Const claims to be. ! */ ! aliasvar = (Var *) makeNullConst(INT4OID, -1, InvalidOid); } } ! newaliasvars = lappend(newaliasvars, aliasvar); } rte->joinaliasvars = newaliasvars; break; --- 208,218 ---- if (get_rte_attribute_is_dropped(curinputrte, aliasvar->varattno)) { ! /* Replace the join alias item with a NULL */ ! aliasitem = NULL; } } ! newaliasvars = lappend(newaliasvars, aliasitem); } rte->joinaliasvars = newaliasvars; break; diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 40b565a11d6ddf40b3cd8e692e625efe1f6dc3a7..60afd8f745ff8d26a261def03ee9df063387fe30 100644 *** a/src/backend/utils/adt/ruleutils.c --- b/src/backend/utils/adt/ruleutils.c *************** typedef struct *** 235,240 **** --- 235,241 ---- * child RTE's attno and rightattnos[i] is zero; and conversely for a * column of the right child. But for merged columns produced by JOIN * USING/NATURAL JOIN, both leftattnos[i] and rightattnos[i] are nonzero. + * Also, if the column has been dropped, both are zero. * * If it's a JOIN USING, usingNames holds the alias names selected for the * merged columns (these might be different from the original USING list, *************** set_join_column_names(deparse_namespace *** 3053,3058 **** --- 3054,3066 ---- char *colname = colinfo->colnames[i]; char *real_colname; + /* Ignore dropped column (only possible for non-merged column) */ + if (colinfo->leftattnos[i] == 0 && colinfo->rightattnos[i] == 0) + { + Assert(colname == NULL); + continue; + } + /* Get the child column name */ if (colinfo->leftattnos[i] > 0) real_colname = leftcolinfo->colnames[colinfo->leftattnos[i] - 1]; *************** set_join_column_names(deparse_namespace *** 3061,3075 **** else { /* We're joining system columns --- use eref name */ ! real_colname = (char *) list_nth(rte->eref->colnames, i); ! } ! ! /* Ignore dropped columns (only possible for non-merged column) */ ! if (real_colname == NULL) ! { ! Assert(colname == NULL); ! continue; } /* In an unnamed join, just report child column names as-is */ if (rte->alias == NULL) --- 3069,3077 ---- else { /* We're joining system columns --- use eref name */ ! real_colname = strVal(list_nth(rte->eref->colnames, i)); } + Assert(real_colname != NULL); /* In an unnamed join, just report child column names as-is */ if (rte->alias == NULL) *************** identify_join_columns(JoinExpr *j, Range *** 3402,3408 **** { Var *aliasvar = (Var *) lfirst(lc); ! if (IsA(aliasvar, Var)) { Assert(aliasvar->varlevelsup == 0); Assert(aliasvar->varattno != 0); --- 3404,3417 ---- { Var *aliasvar = (Var *) lfirst(lc); ! /* get rid of any implicit coercion above the Var */ ! aliasvar = (Var *) strip_implicit_coercions((Node *) aliasvar); ! ! if (aliasvar == NULL) ! { ! /* It's a dropped column; nothing to do here */ ! } ! else if (IsA(aliasvar, Var)) { Assert(aliasvar->varlevelsup == 0); Assert(aliasvar->varattno != 0); *************** identify_join_columns(JoinExpr *j, Range *** 3422,3436 **** */ } else - { - /* - * Although NULL constants can appear in joinaliasvars lists - * during planning, we shouldn't see any here, since the Query - * tree hasn't been through AcquireRewriteLocks(). - */ elog(ERROR, "unrecognized node type in join alias vars: %d", (int) nodeTag(aliasvar)); - } i++; } --- 3431,3438 ---- *************** get_variable(Var *var, int levelsup, boo *** 5359,5365 **** Var *aliasvar; aliasvar = (Var *) list_nth(rte->joinaliasvars, attnum - 1); ! if (IsA(aliasvar, Var)) { return get_variable(aliasvar, var->varlevelsup + levelsup, istoplevel, context); --- 5361,5368 ---- Var *aliasvar; aliasvar = (Var *) list_nth(rte->joinaliasvars, attnum - 1); ! aliasvar = (Var *) strip_implicit_coercions((Node *) aliasvar); ! if (aliasvar && IsA(aliasvar, Var)) { return get_variable(aliasvar, var->varlevelsup + levelsup, istoplevel, context); *************** get_name_for_var_field(Var *var, int fie *** 5670,5675 **** --- 5673,5680 ---- elog(ERROR, "cannot decompile join alias var in plan tree"); Assert(attnum > 0 && attnum <= list_length(rte->joinaliasvars)); expr = (Node *) list_nth(rte->joinaliasvars, attnum - 1); + Assert(expr != NULL); + /* we intentionally don't strip implicit coercions here */ if (IsA(expr, Var)) return get_name_for_var_field((Var *) expr, fieldno, var->varlevelsup + levelsup, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 9415e2c636eab741e2216e6c23dc929e7f4f9494..b4013e893dcf2dd11f86459a5a658410baadfae7 100644 *** a/src/include/nodes/parsenodes.h --- b/src/include/nodes/parsenodes.h *************** typedef struct XmlSerialize *** 660,666 **** * a stored rule might contain entries for columns dropped since the rule * was created. (This is only possible for columns not actually referenced * in the rule.) When loading a stored rule, we replace the joinaliasvars ! * items for any such columns with NULL Consts. (We can't simply delete * them from the joinaliasvars list, because that would affect the attnums * of Vars referencing the rest of the list.) * --- 660,666 ---- * a stored rule might contain entries for columns dropped since the rule * was created. (This is only possible for columns not actually referenced * in the rule.) When loading a stored rule, we replace the joinaliasvars ! * items for any such columns with null pointers. (We can't simply delete * them from the joinaliasvars list, because that would affect the attnums * of Vars referencing the rest of the list.) * *************** typedef struct RangeTblEntry *** 731,743 **** /* * Fields valid for a join RTE (else NULL/zero): * ! * joinaliasvars is a list of Vars or COALESCE expressions corresponding ! * to the columns of the join result. An alias Var referencing column K ! * of the join result can be replaced by the K'th element of joinaliasvars ! * --- but to simplify the task of reverse-listing aliases correctly, we ! * do not do that until planning time. In a Query loaded from a stored ! * rule, it is also possible for joinaliasvars items to be NULL Consts, ! * denoting columns dropped since the rule was made. */ JoinType jointype; /* type of join */ List *joinaliasvars; /* list of alias-var expansions */ --- 731,749 ---- /* * Fields valid for a join RTE (else NULL/zero): * ! * joinaliasvars is a list of (usually) Vars corresponding to the columns ! * of the join result. An alias Var referencing column K of the join ! * result can be replaced by the K'th element of joinaliasvars --- but to ! * simplify the task of reverse-listing aliases correctly, we do not do ! * that until planning time. In detail: an element of joinaliasvars can ! * be a Var of one of the join's input relations, or such a Var with an ! * implicit coercion to the join's output column type, or a COALESCE ! * expression containing the two input column Vars (possibly coerced). ! * Within a Query loaded from a stored rule, it is also possible for ! * joinaliasvars items to be null pointers, which are placeholders for ! * (necessarily unreferenced) columns dropped since the rule was made. ! * Also, once planning begins, joinaliasvars items can be almost anything, ! * as a result of subquery-flattening substitutions. */ JoinType jointype; /* type of join */ List *joinaliasvars; /* list of alias-var expansions */ diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out index 4fa774950345808d9455abf06fddbed15933dfaf..8b451429674a6153dcf914ab2753787365cd6cb8 100644 *** a/src/test/regress/expected/create_view.out --- b/src/test/regress/expected/create_view.out *************** select pg_get_viewdef('vv4', true); *** 1243,1248 **** --- 1243,1275 ---- FULL JOIN tt8 tt8y(x_1, z, z2) USING (x_1); (1 row) + -- + -- Also check dropping a column that existed when the view was made + -- + create table tt9 (x int, xx int, y int); + create table tt10 (x int, z int); + create view vv5 as select x,y,z from tt9 join tt10 using(x); + select pg_get_viewdef('vv5', true); + pg_get_viewdef + ------------------------- + SELECT tt9.x, + + tt9.y, + + tt10.z + + FROM tt9 + + JOIN tt10 USING (x); + (1 row) + + alter table tt9 drop column xx; + select pg_get_viewdef('vv5', true); + pg_get_viewdef + ------------------------- + SELECT tt9.x, + + tt9.y, + + tt10.z + + FROM tt9 + + JOIN tt10 USING (x); + (1 row) + -- clean up all the random objects we made above set client_min_messages = warning; DROP SCHEMA temp_view_test CASCADE; diff --git a/src/test/regress/sql/create_view.sql b/src/test/regress/sql/create_view.sql index 3d85d9cfdc50b454fb38d8a742770eda1e119a41..4fbd5a5e6f8baa3d41f1a201fbb99ad4bc3bf3da 100644 *** a/src/test/regress/sql/create_view.sql --- b/src/test/regress/sql/create_view.sql *************** select pg_get_viewdef('vv2', true); *** 389,394 **** --- 389,409 ---- select pg_get_viewdef('vv3', true); select pg_get_viewdef('vv4', true); + -- + -- Also check dropping a column that existed when the view was made + -- + + create table tt9 (x int, xx int, y int); + create table tt10 (x int, z int); + + create view vv5 as select x,y,z from tt9 join tt10 using(x); + + select pg_get_viewdef('vv5', true); + + alter table tt9 drop column xx; + + select pg_get_viewdef('vv5', true); + -- clean up all the random objects we made above set client_min_messages = warning; DROP SCHEMA temp_view_test CASCADE;
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers