auto-update-views.patch

application/octet-stream

Filename: auto-update-views.patch
Type: application/octet-stream
Part: 0
Message: Proof of concept: auto updatable views

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: unified
File+
src/backend/rewrite/rewriteHandler.c 0 0
src/backend/rewrite/rewriteManip.c 0 0
src/include/rewrite/rewriteManip.h 0 0
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
new file mode 100644
index 8f75948..f942767
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
*************** fireRules(Query *parsetree,
*** 1829,1834 ****
--- 1829,2218 ----
  
  
  /*
+  * get_view_query - get the query from a view's _RETURN rule.
+  */
+ static Query *
+ get_view_query(Relation view)
+ {
+ 	int		i;
+ 
+ 	Assert(view->rd_rel->relkind == RELKIND_VIEW);
+ 
+ 	for (i = 0; i < view->rd_rules->numLocks; i++)
+ 	{
+ 		RewriteRule *rule = view->rd_rules->rules[i];
+ 
+ 		if (rule->event == CMD_SELECT)
+ 		{
+ 			/* A _RETURN rule should have only one action */
+ 			if (list_length(rule->actions) != 1)
+ 				elog(ERROR, "invalid _RETURN rule action specification");
+ 
+ 			return linitial(rule->actions);
+ 		}
+ 	}
+ 
+ 	elog(ERROR, "failed to find _RETURN rule for view");
+ 	return NULL;	/* keep compiler quiet */
+ }
+ 
+ 
+ /*
+  * test_auto_update_view -
+  *	Test if the specified view can be automatically updated. This will either
+  *	return NULL (if the view can be updated) or the reason that it cannot.
+  *
+  * Note that the checks performed here are local to this view.  It does not
+  * check that the view's underlying base relation is updatable.
+  */
+ static const char *
+ test_auto_update_view(Relation view)
+ {
+ 	Query	   *viewquery;
+ 	RangeTblRef *rtr;
+ 	RangeTblEntry *base_rte;
+ 	Bitmapset  *bms;
+ 	ListCell   *cell;
+ 
+ 	/*
+ 	 * Check if the view is simply updatable.  According to SQL-92 this means:
+ 	 *	- No DISTINCT clauses.
+ 	 *	- Every TLE is a column reference, and appears at most once.
+ 	 *	- Refers to a single base relation.
+ 	 *	- No GROUP BY or HAVING clauses.
+ 	 *	- No set operations (UNION, INTERSECT or EXCEPT).
+ 	 *	- No sub-queries in the WHERE clause.
+ 	 *
+ 	 * We relax this last restriction since it would actually be more work to
+ 	 * enforce it than to simply allow it.  In addition (for now) we impose
+ 	 * the following additional constraints, based on features that are not
+ 	 * part of SQL-92:
+ 	 *	- No DISTINCT ON clauses.
+ 	 *	- No window functions.
+ 	 *	- No CTEs (WITH or WITH RECURSIVE).
+ 	 *	- No OFFSET or LIMIT clauses.
+ 	 *	- No system columns.
+ 	 *
+ 	 * Note that we do these checks without recursively expanding the view.
+ 	 * The base relation may be a view, and it might have INSTEAD OF triggers
+ 	 * or rules, in which case it need not satisfy these constraints.
+ 	 */
+ 	viewquery = get_view_query(view);
+ 
+ 	if (viewquery->distinctClause != NIL ||
+ 		viewquery->hasDistinctOn)
+ 		return "Views containing DISTINCT are not updatable";
+ 
+ 	if (viewquery->groupClause != NIL)
+ 		return "Views containing GROUP BY are not updatable";
+ 
+ 	if (viewquery->havingQual != NULL)
+ 		return "Views containing HAVING are not updatable";
+ 
+ 	if (viewquery->hasAggs)
+ 		return "Views containing aggregates are not updatable";
+ 
+ 	if (viewquery->hasWindowFuncs)
+ 		return "Views containing window functions are not updatable";
+ 
+ 	if (viewquery->setOperations != NULL)
+ 		return "Views containing UNION, INTERSECT or EXCEPT are not updatable";
+ 
+ 	if (viewquery->hasRecursive ||
+ 		viewquery->cteList != NIL)
+ 		return "Views containing WITH [RECURSIVE] are not updatable";
+ 
+ 	if (viewquery->limitOffset != NULL ||
+ 		viewquery->limitCount != NULL)
+ 		return "Views containing OFFSET or LIMIT are not updatable";
+ 
+ 	/*
+ 	 * The view query should select from a single base relation, which must be
+ 	 * a table or another view.
+ 	 */
+ 	if (list_length(viewquery->jointree->fromlist) == 0)
+ 		return "Views with no base relations are not updatable";
+ 
+ 	if (list_length(viewquery->jointree->fromlist) > 1)
+ 		return "Views with multiple base relations are not updatable";
+ 
+ 	rtr = (RangeTblRef *) linitial(viewquery->jointree->fromlist);
+ 	if (!IsA(rtr, RangeTblRef))
+ 		return "Views that are not based on tables or views are not updatable";
+ 
+ 	base_rte = rt_fetch(rtr->rtindex, viewquery->rtable);
+ 	if (base_rte->rtekind != RTE_RELATION)
+ 		return "Views that are not based on tables or views are not updatable";
+ 
+ 	/*
+ 	 * The view's targetlist entries should all be Vars referring to columns
+ 	 * in the base relation, and no 2 should refer to the same base column.
+ 	 *
+ 	 * Note that we don't bother freeing resources allocated here if we
+ 	 * return early, since that is an error condition.
+ 	 */
+ 	bms = NULL;
+ 	foreach(cell, viewquery->targetList)
+ 	{
+ 		TargetEntry *tle = (TargetEntry *) lfirst(cell);
+ 		Var		   *var;
+ 
+ 		if (!IsA(tle->expr, Var))
+ 			return "Views with columns that are not simple references to columns in the base relation are not updatable";
+ 
+ 		var = (Var *) tle->expr;
+ 		if (var->varattno < 0)
+ 			return "Views that refer to system columns are not updatable";
+ 
+ 		if (var->varattno == 0)
+ 			return "Views that refer to whole rows from the base relation are not updatable";
+ 
+ 		if (bms_is_member(var->varattno, bms))
+ 			return "Views that refer to the same column more than once are not updatable";
+ 
+ 		bms = bms_add_member(bms, var->varattno);
+ 	}
+ 
+ 	bms_free(bms);
+ 	return NULL;	/* the view is simply updatable */
+ }
+ 
+ 
+ /*
+  * rewriteTargetView -
+  *	attempt to rewrite a query where the target relation is a view, so that
+  *	the view's base relation becomes the target relation.
+  *
+  * This only handles the case where there are no INSTEAD OF triggers to update
+  * the view.  If there are INSTEAD OF triggers the view is expanded as part
+  * of fireRIRrules, which has dedicated code to keep both the original view
+  * target and the expanded rule actions to act as a source of data.
+  *
+  * Note that the base relation here may itself be a view, which may or may not
+  * have INSTEAD OF triggers or rules to handle the update.  That case is
+  * handled by recursion in RewriteQuery.
+  */
+ static Query *
+ rewriteTargetView(Query *parsetree, Relation view)
+ {
+ 	TriggerDesc *trigDesc = view->trigdesc;
+ 	const char *auto_update_detail;
+ 	Query	   *viewquery;
+ 	RangeTblRef *rtr;
+ 	RangeTblEntry *base_rte;
+ 	int			natt;
+ 	AttrNumber *base_attno;
+ 	bool		reorder_atts;
+ 	int			attno;
+ 	ListCell   *cell;
+ 	RangeTblEntry *view_rte;
+ 	int			rt_index;
+ 	List	   *new_rtable;
+ 
+ 	/* The view must have INSTEAD OF triggers or be simply updatable */
+ 	switch (parsetree->commandType)
+ 	{
+ 		case CMD_INSERT:
+ 			if (trigDesc && trigDesc->trig_insert_instead_row)
+ 				return NULL;	/* INSTEAD OF INSERT trigger will be used */
+ 
+ 			auto_update_detail = test_auto_update_view(view);
+ 			if (auto_update_detail)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ 						 errmsg("cannot insert into view \"%s\"",
+ 								RelationGetRelationName(view)),
+ 						 errdetail("%s.", auto_update_detail),
+ 						 errhint("You need an unconditional ON INSERT DO INSTEAD rule or an INSTEAD OF INSERT trigger.")));
+ 			break;
+ 		case CMD_UPDATE:
+ 			if (trigDesc && trigDesc->trig_update_instead_row)
+ 				return NULL;	/* INSTEAD OF UPDATE trigger will be used */
+ 
+ 			auto_update_detail = test_auto_update_view(view);
+ 			if (auto_update_detail)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ 						 errmsg("cannot update view \"%s\"",
+ 								RelationGetRelationName(view)),
+ 						 errdetail("%s.", auto_update_detail),
+ 						 errhint("You need an unconditional ON UPDATE DO INSTEAD rule or an INSTEAD OF UPDATE trigger.")));
+ 			break;
+ 		case CMD_DELETE:
+ 			if (trigDesc && trigDesc->trig_delete_instead_row)
+ 				return NULL;	/* INSTEAD OF DELETE trigger will be used */
+ 
+ 			auto_update_detail = test_auto_update_view(view);
+ 			if (auto_update_detail)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ 						 errmsg("cannot delete from view \"%s\"",
+ 								RelationGetRelationName(view)),
+ 						 errdetail("%s.", auto_update_detail),
+ 						 errhint("You need an unconditional ON DELETE DO INSTEAD rule or an INSTEAD OF DELETE trigger.")));
+ 			break;
+ 		default:
+ 			elog(ERROR, "unrecognized CmdType: %d", (int) parsetree->commandType);
+ 			break;
+ 	}
+ 
+ 	/*
+ 	 * The view should have a single base relation.
+ 	 */
+ 	viewquery = get_view_query(view);
+ 	Assert(list_length(viewquery->jointree->fromlist) == 1);
+ 
+ 	rtr = (RangeTblRef *) linitial(viewquery->jointree->fromlist);
+ 	Assert(IsA(rtr, RangeTblRef));
+ 
+ 	base_rte = rt_fetch(rtr->rtindex, viewquery->rtable);
+ 	Assert(base_rte->rtekind == RTE_RELATION);
+ 
+ 	/*
+ 	 * The view's targetlist entries should all be Vars referring to columns
+ 	 * in the base relation, and no 2 should refer to the same base column.
+ 	 * Build a mapping from view column number to base relation column number,
+ 	 * and catch the case where they are in the same order.
+ 	 */
+ 	natt = list_length(viewquery->targetList);
+ 	base_attno = (AttrNumber *) palloc(sizeof(AttrNumber) * natt);
+ 	reorder_atts = false;
+ 
+ 	attno = 1;
+ 	foreach(cell, viewquery->targetList)
+ 	{
+ 		TargetEntry *tle = (TargetEntry *) lfirst(cell);
+ 		Var		   *var = (Var *) tle->expr;
+ 
+ 		Assert(var->varattno > 0);
+ 		base_attno[attno - 1] = var->varattno;
+ 
+ 		if (var->varattno != attno)
+ 			reorder_atts = true;	/* view columns in different order */
+ 
+ 		attno++;
+ 	}
+ 
+ 	/*
+ 	 * The view is simply updatable, provided that its base relation is
+ 	 * updatable, which will be checked later.  To avoid having to adjust
+ 	 * varnos, we replace the old target RTE with the base relation RTE.
+ 	 *
+ 	 * Note that we also need to keep the original view RTE even though it
+ 	 * will not be used so that the correct permissions checks are done
+ 	 * against it.
+ 	 */
+ 	rt_index = 1;
+ 	new_rtable = NIL;
+ 	foreach(cell, parsetree->rtable)
+ 	{
+ 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(cell);
+ 
+ 		if (rt_index == parsetree->resultRelation)
+ 		{
+ 			/*
+ 			 * Create the new target RTE using the base relation.  Copy any
+ 			 * permission checks from the original view target RTE, adjusting
+ 			 * the column numbers if necessary, and performing the checks as
+ 			 * the view's owner.
+ 			 */
+ 			RangeTblEntry *newrte = (RangeTblEntry *) copyObject(base_rte);
+ 			newrte->requiredPerms = rte->requiredPerms;
+ 			newrte->checkAsUser = view->rd_rel->relowner;
+ 			newrte->selectedCols = bms_copy(rte->selectedCols);
+ 			newrte->modifiedCols = bms_copy(rte->modifiedCols);
+ 
+ 			if (reorder_atts)
+ 			{
+ 				newrte->selectedCols = adjust_col_privs(newrte->selectedCols,
+ 														natt, base_attno);
+ 				newrte->modifiedCols = adjust_col_privs(newrte->modifiedCols,
+ 														natt, base_attno);
+ 			}
+ 
+ 			/*
+ 			 * Keep the original view RTE so that executor also checks that
+ 			 * the current user has the required permissions on the view
+ 			 */
+ 			view_rte = rte;
+ 			rte = newrte;
+ 		}
+ 		new_rtable = lappend(new_rtable, rte);
+ 		rt_index++;
+ 	}
+ 	parsetree->rtable = lappend(new_rtable, view_rte);
+ 
+ 	/*
+ 	 * For UPDATE/DELETE, rewriteTargetListUD will have added a wholerow junk
+ 	 * TLE for the view to the end of the targetlist which we no longer need.
+ 	 * We remove this now to avoid unnecessary work when we process the
+ 	 * targetlist.  Note that when we recurse through rewriteQuery a new junk
+ 	 * RTE will be added to allow the executor to find the row in the base
+ 	 * relation.
+ 	 */
+ 	if (parsetree->commandType != CMD_INSERT)
+ 	{
+ 		TargetEntry *tle = (TargetEntry *)
+ 			lfirst(list_tail(parsetree->targetList));
+ 
+ 		Assert(IsA(tle->expr, Var) && ((Var *) tle->expr)->varattno == 0);
+ 		parsetree->targetList = list_delete_ptr(parsetree->targetList, tle);
+ 	}
+ 
+ 	/*
+ 	 * If the view's columns are in a different order, we need to update the
+ 	 * target list to point to the new columns in the base relation, and
+ 	 * similarly adjust any Vars in the query that refer to the result
+ 	 * relation.
+ 	 */
+ 	if (reorder_atts)
+ 	{
+ 		/* Update target list entries */
+ 		foreach(cell, parsetree->targetList)
+ 		{
+ 			TargetEntry *tle = (TargetEntry *) lfirst(cell);
+ 
+ 			if (!tle->resjunk)
+ 			{
+ 				if (tle->resno > 0 && tle->resno <= natt)
+ 					tle->resno = base_attno[tle->resno - 1];
+ 			}
+ 		}
+ 
+ 		/* Update any Var attribute numbers for the result relation */
+ 		parsetree =
+ 			(Query *) ChangeVarAttnos((Node *) parsetree,
+ 									  parsetree->resultRelation,
+ 									  natt, base_attno,
+ 									  viewquery->targetList,
+ 									  view_rte->eref->colnames, 0);
+ 	}
+ 
+ 	/*
+ 	 * For UPDATE/DELETE, add any quals from the view's jointree to the main
+ 	 * jointree to filter rows.  We know that there is just one relation in
+ 	 * the view, so any of its quals that refer to it need to be adjusted to
+ 	 * refer to it's new location in the main query.
+ 	 *
+ 	 * For INSERT the view's quals are not needed for now.  When we implement
+ 	 * WITH CHECK OPTION, this might be a good time to collect them.
+ 	 */
+ 	if (parsetree->commandType != CMD_INSERT)
+ 	{
+ 		Node	   *viewqual = copyObject(viewquery->jointree->quals);
+ 
+ 		ChangeVarNodes(viewqual, rtr->rtindex, parsetree->resultRelation, 0);
+ 		AddQual(parsetree, (Node *) viewqual);
+ 	}
+ 
+ 	/* Tidy up */
+ 	pfree(base_attno);
+ 
+ 	return parsetree;
+ }
+ 
+ 
+ /*
   * RewriteQuery -
   *	  rewrites the query and apply the rules again on the queries rewritten
   *
*************** RewriteQuery(Query *parsetree, List *rew
*** 1842,1847 ****
--- 2226,2232 ----
  	bool		instead = false;
  	bool		returning = false;
  	Query	   *qual_product = NULL;
+ 	List	   *product_queries = NIL;
  	List	   *rewritten = NIL;
  	ListCell   *lc1;
  
*************** RewriteQuery(Query *parsetree, List *rew
*** 1993,2001 ****
  						   result_relation, parsetree);
  
  		if (locks != NIL)
- 		{
- 			List	   *product_queries;
- 
  			product_queries = fireRules(parsetree,
  										result_relation,
  										event,
--- 2378,2383 ----
*************** RewriteQuery(Query *parsetree, List *rew
*** 2004,2009 ****
--- 2386,2416 ----
  										&returning,
  										&qual_product);
  
+ 		/*
+ 		 * If there are no unqualified INSTEAD rules, and the target relation
+ 		 * is a view without any INSTEAD OF triggers, then we have a problem
+ 		 * unless it can be automatically updated.
+ 		 *
+ 		 * If we can automatically update the view, then we do so here and add
+ 		 * the resulting query to the product queries list, so that it gets
+ 		 * recursively rewritten if necessary.  We mark this as an unqualified
+ 		 * INSTEAD to prevent the original query from being executed.  Note
+ 		 * also that if this succeeds it will have rewritten any returning
+ 		 * list too.
+ 		 */
+ 		if (!instead && rt_entry_relation->rd_rel->relkind == RELKIND_VIEW)
+ 		{
+ 			Query  *query = qual_product ? qual_product : parsetree;
+ 			Query  *newquery = rewriteTargetView(query, rt_entry_relation);
+ 
+ 			if (newquery != NULL)
+ 			{
+ 				product_queries = lappend(product_queries, newquery);
+ 				instead = true;
+ 				returning = true;
+ 			}
+ 		}
+ 
  			/*
  			 * If we got any product queries, recursively rewrite them --- but
  			 * first check for recursion!
*************** RewriteQuery(Query *parsetree, List *rew
*** 2040,2046 ****
  
  				rewrite_events = list_delete_first(rewrite_events);
  			}
- 		}
  
  		/*
  		 * If there is an INSTEAD, and the original query has a RETURNING, we
--- 2447,2452 ----
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
new file mode 100644
index 9c778ef..0ed5666
*** a/src/backend/rewrite/rewriteManip.c
--- b/src/backend/rewrite/rewriteManip.c
***************
*** 13,18 ****
--- 13,19 ----
   */
  #include "postgres.h"
  
+ #include "access/sysattr.h"
  #include "catalog/pg_type.h"
  #include "nodes/makefuncs.h"
  #include "nodes/nodeFuncs.h"
*************** ResolveNew(Node *node, int target_varno,
*** 1451,1453 ****
--- 1452,1599 ----
  								 (void *) &context,
  								 outer_hasSubLinks);
  }
+ 
+ 
+ /*
+  * Adjust the columns in a set needing particular privileges, using the
+  * specified attribute number mappings.  This is used for simply updatable
+  * views to map permissions checks on the view columns onto the matching
+  * columns in the underlying base relation.
+  */
+ Bitmapset *
+ adjust_col_privs(Bitmapset *cols, int natt, AttrNumber *new_attno)
+ {
+ 	Bitmapset  *result = NULL;
+ 	Bitmapset  *tmpcols;
+ 	AttrNumber	col;
+ 
+ 	tmpcols = bms_copy(cols);
+ 	while ((col = bms_first_member(tmpcols)) >= 0)
+ 	{
+ 		AttrNumber	attno = col + FirstLowInvalidHeapAttributeNumber;
+ 
+ 		if (attno <= 0)
+ 			result = bms_add_member(result, col);		/* system column */
+ 		else if (attno <= natt)
+ 			result = bms_add_member(result,
+ 				new_attno[attno - 1] - FirstLowInvalidHeapAttributeNumber);
+ 	}
+ 	bms_free(tmpcols);
+ 
+ 	return result;
+ }
+ 
+ 
+ /*
+  * ChangeVarAttnos - Adjust the attribute numbers in Var nodes
+  *
+  * Find all Var nodes in the given tree belonging to a specific relation
+  * (identified by sublevels_up and rt_index), and change their varattno
+  * fields using the new attribute numbers specified.  This is used for simply
+  * updatable views to map references to view columns onto references to
+  * columns in the underlying base relation.
+  *
+  * Any whole-row references to the view are mapped onto ROW expressions
+  * constructed from the view's targetlist.
+  */
+ 
+ typedef struct
+ {
+ 	int			rt_index;
+ 	int			natt;
+ 	AttrNumber *new_attno;
+ 	List	   *view_tlist;
+ 	List	   *view_colnames;
+ 	int			sublevels_up;
+ } ChangeVarAttnos_context;
+ 
+ static Node *
+ ChangeVarAttnos_mutator(Node *node, ChangeVarAttnos_context *context)
+ {
+ 	if (node == NULL)
+ 		return NULL;
+ 	if (IsA(node, Var))
+ 	{
+ 		Var		   *var = (Var *) node;
+ 
+ 		if (var->varlevelsup == context->sublevels_up &&
+ 			var->varno == context->rt_index)
+ 		{
+ 			if (var->varattno > 0)
+ 			{
+ 				/*
+ 				 * A regular column in the view is mapped to the corresponding
+ 				 * column in the base relation
+ 				 */
+ 				Var	   *newvar;
+ 
+ 				if (var->varattno > context->natt)
+ 					elog(ERROR, "varattno %d too large for view",
+ 						 var->varattno);
+ 
+ 				newvar = (Var *) copyObject(var);
+ 				newvar->varattno = context->new_attno[var->varattno - 1];
+ 				return (Node *) newvar;
+ 			}
+ 			else if (var->varattno == 0)
+ 			{
+ 				/*
+ 				 * For a wholerow variable in the view, we construct a RowExpr
+ 				 * from the view's targetlist
+ 				 */
+ 				RowExpr	   *row = makeNode(RowExpr);
+ 				ListCell   *cell;
+ 
+ 				row->args = NIL;
+ 				foreach(cell, context->view_tlist)
+ 				{
+ 					TargetEntry *tle = (TargetEntry *) lfirst(cell);
+ 					Var		   *view_var = (Var *) copyObject(tle->expr);
+ 
+ 					view_var->varno = var->varno;
+ 					row->args = lappend(row->args, view_var);
+ 				}
+ 
+ 				row->row_typeid = var->vartype;
+ 				row->row_format = COERCE_IMPLICIT_CAST;
+ 				row->colnames = copyObject(context->view_colnames);
+ 				row->location = -1;
+ 				return (Node *) row;
+ 			}
+ 		}
+ 	}
+ 	if (IsA(node, Query))
+ 	{
+ 		/* Recurse into subselects */
+ 		Query	   *result;
+ 
+ 		context->sublevels_up++;
+ 		result = query_tree_mutator((Query *) node, ChangeVarAttnos_mutator,
+ 									(void *) context, 0);
+ 		context->sublevels_up--;
+ 		return (Node *) result;
+ 	}
+ 	return expression_tree_mutator(node, ChangeVarAttnos_mutator,
+ 								   (void *) context);
+ }
+ 
+ Node *
+ ChangeVarAttnos(Node *node, int rt_index, int natt, AttrNumber *new_attno,
+ 				List *view_tlist, List *view_colnames, int sublevels_up)
+ {
+ 	ChangeVarAttnos_context context;
+ 
+ 	context.rt_index = rt_index;
+ 	context.natt = natt;
+ 	context.new_attno = new_attno;
+ 	context.view_tlist = view_tlist;
+ 	context.view_colnames = view_colnames;
+ 	context.sublevels_up = sublevels_up;
+ 
+ 	/*
+ 	 * Must be prepared to start with a Query or a bare expression tree; if
+ 	 * it's a Query, we don't want to increment sublevels_up.
+ 	 */
+ 	return query_or_expression_tree_mutator(node, ChangeVarAttnos_mutator,
+ 											(void *) &context, 0);
+ }
diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h
new file mode 100644
index 6f57b37..560305e
*** a/src/include/rewrite/rewriteManip.h
--- b/src/include/rewrite/rewriteManip.h
*************** extern Node *ResolveNew(Node *node, int
*** 75,78 ****
--- 75,85 ----
  		   List *targetlist, int event, int update_varno,
  		   bool *outer_hasSubLinks);
  
+ extern Bitmapset *adjust_col_privs(Bitmapset *cols, int natt,
+ 				 AttrNumber *new_attno);
+ 
+ extern Node *ChangeVarAttnos(Node *node, int rt_index,
+ 				int natt, AttrNumber *new_attno,
+ 				List *view_tlist, List *view_colnames, int sublevels_up);
+ 
  #endif   /* REWRITEMANIP_H */