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

Reply via email to