*
* rewriteManip.c
*
- * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
+ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteManip.c,v 1.48 2000/09/12 21:07:04 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/rewrite/rewriteManip.c,v 1.114 2008/10/04 21:56:54 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
+#include "catalog/pg_type.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
-#include "optimizer/tlist.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_relation.h"
#include "parser/parsetree.h"
-#include "parser/parse_clause.h"
#include "rewrite/rewriteManip.h"
-#include "utils/lsyscache.h"
-/* macros borrowed from expression_tree_mutator */
-
-#define FLATCOPY(newnode, node, nodetype) \
- ( (newnode) = makeNode(nodetype), \
- memcpy((newnode), (node), sizeof(nodetype)) )
+typedef struct
+{
+ int sublevels_up;
+} contain_aggs_of_level_context;
-#define MUTATE(newfield, oldfield, fieldtype, mutator, context) \
- ( (newfield) = (fieldtype) mutator((Node *) (oldfield), (context)) )
+typedef struct
+{
+ int agg_location;
+ int sublevels_up;
+} locate_agg_of_level_context;
-static bool checkExprHasAggs_walker(Node *node, void *context);
+static bool contain_aggs_of_level_walker(Node *node,
+ contain_aggs_of_level_context *context);
+static bool locate_agg_of_level_walker(Node *node,
+ locate_agg_of_level_context *context);
static bool checkExprHasSubLink_walker(Node *node, void *context);
+static Relids offset_relid_set(Relids relids, int offset);
+static Relids adjust_relid_set(Relids relids, int oldrelid, int newrelid);
/*
* checkExprHasAggs -
- * Queries marked hasAggs might not have them any longer after
- * rewriting. Check it.
+ * Check if an expression contains an aggregate function call of the
+ * current query level.
*/
bool
checkExprHasAggs(Node *node)
{
+ return contain_aggs_of_level(node, 0);
+}
+
+/*
+ * contain_aggs_of_level -
+ * Check if an expression contains an aggregate function call of a
+ * specified query level.
+ *
+ * The objective of this routine is to detect whether there are aggregates
+ * belonging to the given query level. Aggregates belonging to subqueries
+ * or outer queries do NOT cause a true result. We must recurse into
+ * subqueries to detect outer-reference aggregates that logically belong to
+ * the specified query level.
+ */
+bool
+contain_aggs_of_level(Node *node, int levelsup)
+{
+ contain_aggs_of_level_context context;
+
+ context.sublevels_up = levelsup;
+
/*
- * If a Query is passed, examine it --- but we will not recurse
- * into sub-Queries.
+ * 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.
*/
- if (node && IsA(node, Query))
- return query_tree_walker((Query *) node, checkExprHasAggs_walker,
- NULL);
- else
- return checkExprHasAggs_walker(node, NULL);
+ return query_or_expression_tree_walker(node,
+ contain_aggs_of_level_walker,
+ (void *) &context,
+ 0);
+}
+
+static bool
+contain_aggs_of_level_walker(Node *node,
+ contain_aggs_of_level_context *context)
+{
+ if (node == NULL)
+ return false;
+ if (IsA(node, Aggref))
+ {
+ if (((Aggref *) node)->agglevelsup == context->sublevels_up)
+ return true; /* abort the tree traversal and return true */
+ /* else fall through to examine argument */
+ }
+ if (IsA(node, Query))
+ {
+ /* Recurse into subselects */
+ bool result;
+
+ context->sublevels_up++;
+ result = query_tree_walker((Query *) node,
+ contain_aggs_of_level_walker,
+ (void *) context, 0);
+ context->sublevels_up--;
+ return result;
+ }
+ return expression_tree_walker(node, contain_aggs_of_level_walker,
+ (void *) context);
+}
+
+/*
+ * locate_agg_of_level -
+ * Find the parse location of any aggregate of the specified query level.
+ *
+ * Returns -1 if no such agg is in the querytree, or if they all have
+ * unknown parse location. (The former case is probably caller error,
+ * but we don't bother to distinguish it from the latter case.)
+ *
+ * Note: it might seem appropriate to merge this functionality into
+ * contain_aggs_of_level, but that would complicate that function's API.
+ * Currently, the only uses of this function are for error reporting,
+ * and so shaving cycles probably isn't very important.
+ */
+int
+locate_agg_of_level(Node *node, int levelsup)
+{
+ locate_agg_of_level_context context;
+
+ context.agg_location = -1; /* in case we find nothing */
+ context.sublevels_up = levelsup;
+
+ /*
+ * 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.
+ */
+ (void) query_or_expression_tree_walker(node,
+ locate_agg_of_level_walker,
+ (void *) &context,
+ 0);
+
+ return context.agg_location;
}
static bool
-checkExprHasAggs_walker(Node *node, void *context)
+locate_agg_of_level_walker(Node *node,
+ locate_agg_of_level_context *context)
{
if (node == NULL)
return false;
if (IsA(node, Aggref))
- return true; /* abort the tree traversal and return
- * true */
- return expression_tree_walker(node, checkExprHasAggs_walker, context);
+ {
+ if (((Aggref *) node)->agglevelsup == context->sublevels_up &&
+ ((Aggref *) node)->location >= 0)
+ {
+ context->agg_location = ((Aggref *) node)->location;
+ return true; /* abort the tree traversal and return true */
+ }
+ /* else fall through to examine argument */
+ }
+ if (IsA(node, Query))
+ {
+ /* Recurse into subselects */
+ bool result;
+
+ context->sublevels_up++;
+ result = query_tree_walker((Query *) node,
+ locate_agg_of_level_walker,
+ (void *) context, 0);
+ context->sublevels_up--;
+ return result;
+ }
+ return expression_tree_walker(node, locate_agg_of_level_walker,
+ (void *) context);
}
/*
* checkExprHasSubLink -
- * Queries marked hasSubLinks might not have them any longer after
- * rewriting. Check it.
+ * Check if an expression contains a SubLink.
*/
bool
checkExprHasSubLink(Node *node)
{
/*
- * If a Query is passed, examine it --- but we will not recurse
- * into sub-Queries.
+ * If a Query is passed, examine it --- but we should not recurse into
+ * sub-Queries that are in its rangetable or CTE list.
*/
- if (node && IsA(node, Query))
- return query_tree_walker((Query *) node, checkExprHasSubLink_walker,
- NULL);
- else
- return checkExprHasSubLink_walker(node, NULL);
+ return query_or_expression_tree_walker(node,
+ checkExprHasSubLink_walker,
+ NULL,
+ QTW_IGNORE_RC_SUBQUERIES);
}
static bool
if (node == NULL)
return false;
if (IsA(node, SubLink))
- return true; /* abort the tree traversal and return
- * true */
+ return true; /* abort the tree traversal and return true */
return expression_tree_walker(node, checkExprHasSubLink_walker, context);
}
*
* Find all Var nodes in the given tree with varlevelsup == sublevels_up,
* and increment their varno fields (rangetable indexes) by 'offset'.
- * The varnoold fields are adjusted similarly. Also, RangeTblRef nodes
- * in join trees are adjusted.
+ * The varnoold fields are adjusted similarly. Also, adjust other nodes
+ * that contain rangetable indexes, such as RangeTblRef and JoinExpr.
*
* NOTE: although this has the form of a walker, we cheat and modify the
* nodes in-place. The given expression tree should have been copied
}
return false;
}
+ if (IsA(node, CurrentOfExpr))
+ {
+ CurrentOfExpr *cexpr = (CurrentOfExpr *) node;
+
+ if (context->sublevels_up == 0)
+ cexpr->cvarno += context->offset;
+ return false;
+ }
if (IsA(node, RangeTblRef))
{
- RangeTblRef *rtr = (RangeTblRef *) node;
+ RangeTblRef *rtr = (RangeTblRef *) node;
if (context->sublevels_up == 0)
rtr->rtindex += context->offset;
+ /* the subquery itself is visited separately */
return false;
}
+ if (IsA(node, JoinExpr))
+ {
+ JoinExpr *j = (JoinExpr *) node;
+
+ if (context->sublevels_up == 0)
+ j->rtindex += context->offset;
+ /* fall through to examine children */
+ }
+ if (IsA(node, FlattenedSubLink))
+ {
+ FlattenedSubLink *fslink = (FlattenedSubLink *) node;
+
+ if (context->sublevels_up == 0)
+ {
+ fslink->lefthand = offset_relid_set(fslink->lefthand,
+ context->offset);
+ fslink->righthand = offset_relid_set(fslink->righthand,
+ context->offset);
+ }
+ /* fall through to examine children */
+ }
+ if (IsA(node, AppendRelInfo))
+ {
+ AppendRelInfo *appinfo = (AppendRelInfo *) node;
+
+ if (context->sublevels_up == 0)
+ {
+ appinfo->parent_relid += context->offset;
+ appinfo->child_relid += context->offset;
+ }
+ /* fall through to examine children */
+ }
if (IsA(node, Query))
{
/* Recurse into subselects */
context->sublevels_up++;
result = query_tree_walker((Query *) node, OffsetVarNodes_walker,
- (void *) context);
+ (void *) context, 0);
context->sublevels_up--;
return result;
}
context.sublevels_up = sublevels_up;
/*
- * Must be prepared to start with a Query or a bare expression tree;
- * if it's a Query, go straight to query_tree_walker to make sure that
+ * Must be prepared to start with a Query or a bare expression tree; if
+ * it's a Query, go straight to query_tree_walker to make sure that
* sublevels_up doesn't get incremented prematurely.
*/
if (node && IsA(node, Query))
- query_tree_walker((Query *) node, OffsetVarNodes_walker,
- (void *) &context);
+ {
+ Query *qry = (Query *) node;
+
+ /*
+ * If we are starting at a Query, and sublevels_up is zero, then we
+ * must also fix rangetable indexes in the Query itself --- namely
+ * resultRelation and rowMarks entries. sublevels_up cannot be zero
+ * when recursing into a subquery, so there's no need to have the same
+ * logic inside OffsetVarNodes_walker.
+ */
+ if (sublevels_up == 0)
+ {
+ ListCell *l;
+
+ if (qry->resultRelation)
+ qry->resultRelation += offset;
+ foreach(l, qry->rowMarks)
+ {
+ RowMarkClause *rc = (RowMarkClause *) lfirst(l);
+
+ rc->rti += offset;
+ }
+ }
+ query_tree_walker(qry, OffsetVarNodes_walker,
+ (void *) &context, 0);
+ }
else
OffsetVarNodes_walker(node, &context);
}
+static Relids
+offset_relid_set(Relids relids, int offset)
+{
+ Relids result = NULL;
+ Relids tmprelids;
+ int rtindex;
+
+ tmprelids = bms_copy(relids);
+ while ((rtindex = bms_first_member(tmprelids)) >= 0)
+ result = bms_add_member(result, rtindex + offset);
+ bms_free(tmprelids);
+ return result;
+}
+
/*
* ChangeVarNodes - adjust Var nodes for a specific change of RT index
*
* Find all Var nodes in the given tree belonging to a specific relation
* (identified by sublevels_up and rt_index), and change their varno fields
- * to 'new_index'. The varnoold fields are changed too. Also, RangeTblRef
- * nodes in join trees are adjusted.
+ * to 'new_index'. The varnoold fields are changed too. Also, adjust other
+ * nodes that contain rangetable indexes, such as RangeTblRef and JoinExpr.
*
* NOTE: although this has the form of a walker, we cheat and modify the
* nodes in-place. The given expression tree should have been copied
}
return false;
}
+ if (IsA(node, CurrentOfExpr))
+ {
+ CurrentOfExpr *cexpr = (CurrentOfExpr *) node;
+
+ if (context->sublevels_up == 0 &&
+ cexpr->cvarno == context->rt_index)
+ cexpr->cvarno = context->new_index;
+ return false;
+ }
if (IsA(node, RangeTblRef))
{
- RangeTblRef *rtr = (RangeTblRef *) node;
+ RangeTblRef *rtr = (RangeTblRef *) node;
if (context->sublevels_up == 0 &&
rtr->rtindex == context->rt_index)
rtr->rtindex = context->new_index;
+ /* the subquery itself is visited separately */
return false;
}
+ if (IsA(node, JoinExpr))
+ {
+ JoinExpr *j = (JoinExpr *) node;
+
+ if (context->sublevels_up == 0 &&
+ j->rtindex == context->rt_index)
+ j->rtindex = context->new_index;
+ /* fall through to examine children */
+ }
+ if (IsA(node, FlattenedSubLink))
+ {
+ FlattenedSubLink *fslink = (FlattenedSubLink *) node;
+
+ if (context->sublevels_up == 0)
+ {
+ fslink->lefthand = adjust_relid_set(fslink->lefthand,
+ context->rt_index,
+ context->new_index);
+ fslink->righthand = adjust_relid_set(fslink->righthand,
+ context->rt_index,
+ context->new_index);
+ }
+ /* fall through to examine children */
+ }
+ if (IsA(node, AppendRelInfo))
+ {
+ AppendRelInfo *appinfo = (AppendRelInfo *) node;
+
+ if (context->sublevels_up == 0)
+ {
+ if (appinfo->parent_relid == context->rt_index)
+ appinfo->parent_relid = context->new_index;
+ if (appinfo->child_relid == context->rt_index)
+ appinfo->child_relid = context->new_index;
+ }
+ /* fall through to examine children */
+ }
if (IsA(node, Query))
{
/* Recurse into subselects */
context->sublevels_up++;
result = query_tree_walker((Query *) node, ChangeVarNodes_walker,
- (void *) context);
+ (void *) context, 0);
context->sublevels_up--;
return result;
}
context.sublevels_up = sublevels_up;
/*
- * Must be prepared to start with a Query or a bare expression tree;
- * if it's a Query, go straight to query_tree_walker to make sure that
+ * Must be prepared to start with a Query or a bare expression tree; if
+ * it's a Query, go straight to query_tree_walker to make sure that
* sublevels_up doesn't get incremented prematurely.
*/
if (node && IsA(node, Query))
- query_tree_walker((Query *) node, ChangeVarNodes_walker,
- (void *) &context);
+ {
+ Query *qry = (Query *) node;
+
+ /*
+ * If we are starting at a Query, and sublevels_up is zero, then we
+ * must also fix rangetable indexes in the Query itself --- namely
+ * resultRelation and rowMarks entries. sublevels_up cannot be zero
+ * when recursing into a subquery, so there's no need to have the same
+ * logic inside ChangeVarNodes_walker.
+ */
+ if (sublevels_up == 0)
+ {
+ ListCell *l;
+
+ if (qry->resultRelation == rt_index)
+ qry->resultRelation = new_index;
+ foreach(l, qry->rowMarks)
+ {
+ RowMarkClause *rc = (RowMarkClause *) lfirst(l);
+
+ if (rc->rti == rt_index)
+ rc->rti = new_index;
+ }
+ }
+ query_tree_walker(qry, ChangeVarNodes_walker,
+ (void *) &context, 0);
+ }
else
ChangeVarNodes_walker(node, &context);
}
+/*
+ * Substitute newrelid for oldrelid in a Relid set
+ */
+static Relids
+adjust_relid_set(Relids relids, int oldrelid, int newrelid)
+{
+ if (bms_is_member(oldrelid, relids))
+ {
+ /* Ensure we have a modifiable copy */
+ relids = bms_copy(relids);
+ /* Remove old, add new */
+ relids = bms_del_member(relids, oldrelid);
+ relids = bms_add_member(relids, newrelid);
+ }
+ return relids;
+}
+
/*
* IncrementVarSublevelsUp - adjust Var nodes when pushing them down in tree
*
* that sublink are not affected, only outer references to vars that belong
* to the expression's original query level or parents thereof.
*
+ * Likewise for other nodes containing levelsup fields, such as Aggref.
+ *
* NOTE: although this has the form of a walker, we cheat and modify the
* Var nodes in-place. The given expression tree should have been copied
* earlier to ensure that no unwanted side-effects occur!
if (var->varlevelsup >= context->min_sublevels_up)
var->varlevelsup += context->delta_sublevels_up;
+ return false; /* done here */
+ }
+ if (IsA(node, CurrentOfExpr))
+ {
+ /* this should not happen */
+ if (context->min_sublevels_up == 0)
+ elog(ERROR, "cannot push down CurrentOfExpr");
return false;
}
+ if (IsA(node, Aggref))
+ {
+ Aggref *agg = (Aggref *) node;
+
+ if (agg->agglevelsup >= context->min_sublevels_up)
+ agg->agglevelsup += context->delta_sublevels_up;
+ /* fall through to recurse into argument */
+ }
+ if (IsA(node, RangeTblEntry))
+ {
+ RangeTblEntry *rte = (RangeTblEntry *) node;
+
+ if (rte->rtekind == RTE_CTE)
+ {
+ if (rte->ctelevelsup >= context->min_sublevels_up)
+ rte->ctelevelsup += context->delta_sublevels_up;
+ }
+ return false; /* allow range_table_walker to continue */
+ }
if (IsA(node, Query))
{
/* Recurse into subselects */
context->min_sublevels_up++;
result = query_tree_walker((Query *) node,
IncrementVarSublevelsUp_walker,
- (void *) context);
+ (void *) context,
+ QTW_EXAMINE_RTES);
context->min_sublevels_up--;
return result;
}
context.min_sublevels_up = min_sublevels_up;
/*
- * Must be prepared to start with a Query or a bare expression tree;
- * if it's a Query, go straight to query_tree_walker to make sure that
- * sublevels_up doesn't get incremented prematurely.
+ * 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.
*/
- if (node && IsA(node, Query))
- query_tree_walker((Query *) node, IncrementVarSublevelsUp_walker,
- (void *) &context);
- else
- IncrementVarSublevelsUp_walker(node, &context);
+ query_or_expression_tree_walker(node,
+ IncrementVarSublevelsUp_walker,
+ (void *) &context,
+ QTW_EXAMINE_RTES);
+}
+
+/*
+ * IncrementVarSublevelsUp_rtable -
+ * Same as IncrementVarSublevelsUp, but to be invoked on a range table.
+ */
+void
+IncrementVarSublevelsUp_rtable(List *rtable, int delta_sublevels_up,
+ int min_sublevels_up)
+{
+ IncrementVarSublevelsUp_context context;
+
+ context.delta_sublevels_up = delta_sublevels_up;
+ context.min_sublevels_up = min_sublevels_up;
+
+ range_table_walker(rtable,
+ IncrementVarSublevelsUp_walker,
+ (void *) &context,
+ QTW_EXAMINE_RTES);
}
/*
* rangeTableEntry_used - detect whether an RTE is referenced somewhere
- * in var nodes or jointree nodes of a query or expression.
+ * in var nodes or join or setOp trees of a query or expression.
*/
typedef struct
return true;
return false;
}
+ if (IsA(node, CurrentOfExpr))
+ {
+ CurrentOfExpr *cexpr = (CurrentOfExpr *) node;
+
+ if (context->sublevels_up == 0 &&
+ cexpr->cvarno == context->rt_index)
+ return true;
+ return false;
+ }
if (IsA(node, RangeTblRef))
{
RangeTblRef *rtr = (RangeTblRef *) node;
if (rtr->rtindex == context->rt_index &&
context->sublevels_up == 0)
return true;
+ /* the subquery itself is visited separately */
return false;
}
+ if (IsA(node, JoinExpr))
+ {
+ JoinExpr *j = (JoinExpr *) node;
+
+ if (j->rtindex == context->rt_index &&
+ context->sublevels_up == 0)
+ return true;
+ /* fall through to examine children */
+ }
+ /* Shouldn't need to handle planner auxiliary nodes here */
+ Assert(!IsA(node, FlattenedSubLink));
+ Assert(!IsA(node, SpecialJoinInfo));
+ Assert(!IsA(node, AppendRelInfo));
+
if (IsA(node, Query))
{
/* Recurse into subselects */
context->sublevels_up++;
result = query_tree_walker((Query *) node, rangeTableEntry_used_walker,
- (void *) context);
+ (void *) context, 0);
context->sublevels_up--;
return result;
}
context.sublevels_up = sublevels_up;
/*
- * Must be prepared to start with a Query or a bare expression tree;
- * if it's a Query, go straight to query_tree_walker to make sure that
- * sublevels_up doesn't get incremented prematurely.
+ * 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.
*/
- if (node && IsA(node, Query))
- return query_tree_walker((Query *) node, rangeTableEntry_used_walker,
- (void *) &context);
- else
- return rangeTableEntry_used_walker(node, &context);
+ return query_or_expression_tree_walker(node,
+ rangeTableEntry_used_walker,
+ (void *) &context,
+ 0);
}
context->sublevels_up++;
result = query_tree_walker((Query *) node, attribute_used_walker,
- (void *) context);
+ (void *) context, 0);
context->sublevels_up--;
return result;
}
context.sublevels_up = sublevels_up;
/*
- * Must be prepared to start with a Query or a bare expression tree;
- * if it's a Query, go straight to query_tree_walker to make sure that
- * sublevels_up doesn't get incremented prematurely.
+ * 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.
*/
- if (node && IsA(node, Query))
- return query_tree_walker((Query *) node, attribute_used_walker,
- (void *) &context);
- else
- return attribute_used_walker(node, &context);
+ return query_or_expression_tree_walker(node,
+ attribute_used_walker,
+ (void *) &context,
+ 0);
}
/*
- * Add the given qualifier condition to the query's WHERE clause
+ * If the given Query is an INSERT ... SELECT construct, extract and
+ * return the sub-Query node that represents the SELECT part. Otherwise
+ * return the given Query.
+ *
+ * If subquery_ptr is not NULL, then *subquery_ptr is set to the location
+ * of the link to the SELECT subquery inside parsetree, or NULL if not an
+ * INSERT ... SELECT.
+ *
+ * This is a hack needed because transformations on INSERT ... SELECTs that
+ * appear in rule actions should be applied to the source SELECT, not to the
+ * INSERT part. Perhaps this can be cleaned up with redesigned querytrees.
*/
-void
-AddQual(Query *parsetree, Node *qual)
+Query *
+getInsertSelectQuery(Query *parsetree, Query ***subquery_ptr)
{
- Node *copy,
- *old;
+ Query *selectquery;
+ RangeTblEntry *selectrte;
+ RangeTblRef *rtr;
- if (qual == NULL)
- return;
+ if (subquery_ptr)
+ *subquery_ptr = NULL;
- /* INTERSECT want's the original, but we need to copy - Jan */
- copy = copyObject(qual);
-
- old = parsetree->qual;
- if (old == NULL)
- parsetree->qual = copy;
- else
- parsetree->qual = (Node *) make_andclause(makeList(old, copy, -1));
+ if (parsetree == NULL)
+ return parsetree;
+ if (parsetree->commandType != CMD_INSERT)
+ return parsetree;
/*
- * Make sure query is marked correctly if added qual has sublinks or
- * aggregates (not sure it can ever have aggs, but sublinks
- * definitely).
+ * Currently, this is ONLY applied to rule-action queries, and so we
+ * expect to find the *OLD* and *NEW* placeholder entries in the given
+ * query. If they're not there, it must be an INSERT/SELECT in which
+ * they've been pushed down to the SELECT.
*/
- parsetree->hasAggs |= checkExprHasAggs(copy);
- parsetree->hasSubLinks |= checkExprHasSubLink(copy);
+ if (list_length(parsetree->rtable) >= 2 &&
+ strcmp(rt_fetch(PRS2_OLD_VARNO, parsetree->rtable)->eref->aliasname,
+ "*OLD*") == 0 &&
+ strcmp(rt_fetch(PRS2_NEW_VARNO, parsetree->rtable)->eref->aliasname,
+ "*NEW*") == 0)
+ return parsetree;
+ Assert(parsetree->jointree && IsA(parsetree->jointree, FromExpr));
+ if (list_length(parsetree->jointree->fromlist) != 1)
+ elog(ERROR, "expected to find SELECT subquery");
+ rtr = (RangeTblRef *) linitial(parsetree->jointree->fromlist);
+ Assert(IsA(rtr, RangeTblRef));
+ selectrte = rt_fetch(rtr->rtindex, parsetree->rtable);
+ selectquery = selectrte->subquery;
+ if (!(selectquery && IsA(selectquery, Query) &&
+ selectquery->commandType == CMD_SELECT))
+ elog(ERROR, "expected to find SELECT subquery");
+ if (list_length(selectquery->rtable) >= 2 &&
+ strcmp(rt_fetch(PRS2_OLD_VARNO, selectquery->rtable)->eref->aliasname,
+ "*OLD*") == 0 &&
+ strcmp(rt_fetch(PRS2_NEW_VARNO, selectquery->rtable)->eref->aliasname,
+ "*NEW*") == 0)
+ {
+ if (subquery_ptr)
+ *subquery_ptr = &(selectrte->subquery);
+ return selectquery;
+ }
+ elog(ERROR, "could not find rule placeholders");
+ return NULL; /* not reached */
}
+
/*
- * Add the given havingQual to the one already contained in the parsetree
- * just as AddQual does for the normal 'where' qual
+ * Add the given qualifier condition to the query's WHERE clause
*/
void
-AddHavingQual(Query *parsetree, Node *havingQual)
+AddQual(Query *parsetree, Node *qual)
{
- Node *copy,
- *old;
+ Node *copy;
- if (havingQual == NULL)
+ if (qual == NULL)
return;
+ if (parsetree->commandType == CMD_UTILITY)
+ {
+ /*
+ * There's noplace to put the qual on a utility statement.
+ *
+ * If it's a NOTIFY, silently ignore the qual; this means that the
+ * NOTIFY will execute, whether or not there are any qualifying rows.
+ * While clearly wrong, this is much more useful than refusing to
+ * execute the rule at all, and extra NOTIFY events are harmless for
+ * typical uses of NOTIFY.
+ *
+ * If it isn't a NOTIFY, error out, since unconditional execution of
+ * other utility stmts is unlikely to be wanted. (This case is not
+ * currently allowed anyway, but keep the test for safety.)
+ */
+ if (parsetree->utilityStmt && IsA(parsetree->utilityStmt, NotifyStmt))
+ return;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("conditional utility statements are not implemented")));
+ }
+
+ if (parsetree->setOperations != NULL)
+ {
+ /*
+ * There's noplace to put the qual on a setop statement, either. (This
+ * could be fixed, but right now the planner simply ignores any qual
+ * condition on a setop query.)
+ */
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("conditional UNION/INTERSECT/EXCEPT statements are not implemented")));
+ }
+
/* INTERSECT want's the original, but we need to copy - Jan */
- copy = copyObject(havingQual);
+ copy = copyObject(qual);
- old = parsetree->havingQual;
- if (old == NULL)
- parsetree->havingQual = copy;
- else
- parsetree->havingQual = (Node *) make_andclause(makeList(old, copy, -1));
+ parsetree->jointree->quals = make_and_qual(parsetree->jointree->quals,
+ copy);
/*
- * Make sure query is marked correctly if added qual has sublinks or
- * aggregates (not sure it can ever have aggs, but sublinks
- * definitely).
+ * We had better not have stuck an aggregate into the WHERE clause.
*/
- parsetree->hasAggs |= checkExprHasAggs(copy);
- parsetree->hasSubLinks |= checkExprHasSubLink(copy);
-}
-
-#ifdef NOT_USED
-void
-AddNotHavingQual(Query *parsetree, Node *havingQual)
-{
- Node *notqual;
+ Assert(!checkExprHasAggs(copy));
- if (havingQual == NULL)
- return;
-
- /* Need not copy input qual, because AddHavingQual will... */
- notqual = (Node *) make_notclause((Expr *) havingQual);
-
- AddHavingQual(parsetree, notqual);
+ /*
+ * Make sure query is marked correctly if added qual has sublinks. Need
+ * not search qual when query is already marked.
+ */
+ if (!parsetree->hasSubLinks)
+ parsetree->hasSubLinks = checkExprHasSubLink(copy);
}
-#endif
+/*
+ * Invert the given clause and add it to the WHERE qualifications of the
+ * given querytree. Inversion means "x IS NOT TRUE", not just "NOT x",
+ * else we will do the wrong thing when x evaluates to NULL.
+ */
void
-AddNotQual(Query *parsetree, Node *qual)
+AddInvertedQual(Query *parsetree, Node *qual)
{
- Node *notqual;
+ BooleanTest *invqual;
if (qual == NULL)
return;
/* Need not copy input qual, because AddQual will... */
- notqual = (Node *) make_notclause((Expr *) qual);
+ invqual = makeNode(BooleanTest);
+ invqual->arg = (Expr *) qual;
+ invqual->booltesttype = IS_NOT_TRUE;
- AddQual(parsetree, notqual);
+ AddQual(parsetree, (Node *) invqual);
}
/*
- * Add all expressions used by the given GroupClause list to the
- * parsetree's targetlist and groupclause list.
+ * ResolveNew - replace Vars with corresponding items from a targetlist
+ *
+ * Vars matching target_varno and sublevels_up are replaced by the
+ * entry with matching resno from targetlist, if there is one.
+ * If not, we either change the unmatched Var's varno to update_varno
+ * (when event == CMD_UPDATE) or replace it with a constant NULL.
*
- * tlist is the old targetlist associated with the input groupclauses.
+ * The caller must also provide target_rte, the RTE describing the target
+ * relation. This is needed to handle whole-row Vars referencing the target.
+ * We expand such Vars into RowExpr constructs.
*
- * XXX shouldn't we be checking to see if there are already matching
- * entries in parsetree->targetlist?
+ * Note: the business with inserted_sublink is needed to update hasSubLinks
+ * in subqueries when the replacement adds a subquery inside a subquery.
+ * Messy, isn't it? We do not need to do similar pushups for hasAggs,
+ * because it isn't possible for this transformation to insert a level-zero
+ * aggregate reference into a subquery --- it could only insert outer aggs.
*/
-void
-AddGroupClause(Query *parsetree, List *group_by, List *tlist)
-{
- List *l;
- foreach(l, group_by)
- {
- GroupClause *groupclause = (GroupClause *) copyObject(lfirst(l));
- TargetEntry *tle = get_sortgroupclause_tle(groupclause, tlist);
-
- /* copy the groupclause's TLE from the old tlist */
- tle = (TargetEntry *) copyObject(tle);
-
- /*
- * The ressortgroupref number in the old tlist might be already
- * taken in the new tlist, so force assignment of a new number.
- */
- tle->resdom->ressortgroupref = 0;
- groupclause->tleSortGroupRef =
- assignSortGroupRef(tle, parsetree->targetList);
-
- /* Also need to set the resno and mark it resjunk. */
- tle->resdom->resno = length(parsetree->targetList) + 1;
- tle->resdom->resjunk = true;
-
- parsetree->targetList = lappend(parsetree->targetList, tle);
- parsetree->groupClause = lappend(parsetree->groupClause, groupclause);
- }
-}
+typedef struct
+{
+ int target_varno;
+ int sublevels_up;
+ RangeTblEntry *target_rte;
+ List *targetlist;
+ int event;
+ int update_varno;
+ bool inserted_sublink;
+} ResolveNew_context;
static Node *
-make_null(Oid type)
+resolve_one_var(Var *var, ResolveNew_context *context)
{
- Const *c = makeNode(Const);
+ TargetEntry *tle;
- c->consttype = type;
- c->constlen = get_typlen(type);
- c->constvalue = PointerGetDatum(NULL);
- c->constisnull = true;
- c->constbyval = get_typbyval(type);
- return (Node *) c;
-}
+ tle = get_tle_by_resno(context->targetlist, var->varattno);
-#ifdef NOT_USED
-void
-FixResdomTypes(List *tlist)
-{
- List *i;
-
- foreach(i, tlist)
+ if (tle == NULL)
{
- TargetEntry *tle = lfirst(i);
-
- if (nodeTag(tle->expr) == T_Var)
+ /* Failed to find column in insert/update tlist */
+ if (context->event == CMD_UPDATE)
{
- Var *var = (Var *) tle->expr;
-
- tle->resdom->restype = var->vartype;
- tle->resdom->restypmod = var->vartypmod;
+ /* For update, just change unmatched var's varno */
+ var = (Var *) copyObject(var);
+ var->varno = context->update_varno;
+ var->varnoold = context->update_varno;
+ return (Node *) var;
+ }
+ else
+ {
+ /* Otherwise replace unmatched var with a null */
+ /* need coerce_to_domain in case of NOT NULL domain constraint */
+ return coerce_to_domain((Node *) makeNullConst(var->vartype,
+ var->vartypmod),
+ InvalidOid, -1,
+ var->vartype,
+ COERCE_IMPLICIT_CAST,
+ -1,
+ false,
+ false);
}
}
-}
-
-#endif
-
-/* Find a targetlist entry by resno */
-static Node *
-FindMatchingNew(List *tlist, int attno)
-{
- List *i;
-
- foreach(i, tlist)
- {
- TargetEntry *tle = lfirst(i);
-
- if (tle->resdom->resno == attno)
- return tle->expr;
- }
- return NULL;
-}
-
-/* Find a targetlist entry by resname */
-static Node *
-FindMatchingTLEntry(List *tlist, char *e_attname)
-{
- List *i;
-
- foreach(i, tlist)
+ else
{
- TargetEntry *tle = lfirst(i);
- char *resname;
-
- resname = tle->resdom->resname;
- if (!strcmp(e_attname, resname))
- return tle->expr;
+ /* Make a copy of the tlist item to return */
+ Node *n = copyObject(tle->expr);
+
+ /* Adjust varlevelsup if tlist item is from higher query */
+ if (var->varlevelsup > 0)
+ IncrementVarSublevelsUp(n, var->varlevelsup, 0);
+ /* Report it if we are adding a sublink to query */
+ if (!context->inserted_sublink)
+ context->inserted_sublink = checkExprHasSubLink(n);
+ return n;
}
- return NULL;
}
-
-/*
- * ResolveNew - replace Vars with corresponding items from a targetlist
- *
- * Vars matching info->new_varno and sublevels_up are replaced by the
- * entry with matching resno from targetlist, if there is one.
- */
-
-typedef struct
-{
- RewriteInfo *info;
- List *targetlist;
- int sublevels_up;
-} ResolveNew_context;
-
static Node *
ResolveNew_mutator(Node *node, ResolveNew_context *context)
{
int this_varno = (int) var->varno;
int this_varlevelsup = (int) var->varlevelsup;
- if (this_varno == context->info->new_varno &&
+ if (this_varno == context->target_varno &&
this_varlevelsup == context->sublevels_up)
{
- Node *n = FindMatchingNew(context->targetlist,
- var->varattno);
-
- if (n == NULL)
- {
- if (context->info->event == CMD_UPDATE)
- {
- /* For update, just change unmatched var's varno */
- n = copyObject(node);
- ((Var *) n)->varno = context->info->current_varno;
- ((Var *) n)->varnoold = context->info->current_varno;
- return n;
- }
- else
- {
- /* Otherwise replace unmatched var with a null */
- return make_null(var->vartype);
- }
- }
- else
+ if (var->varattno == InvalidAttrNumber)
{
- /* Make a copy of the tlist item to return */
- n = copyObject(n);
- /* Adjust varlevelsup if tlist item is from higher query */
- if (this_varlevelsup > 0)
- IncrementVarSublevelsUp(n, this_varlevelsup, 0);
- return n;
+ /* Must expand whole-tuple reference into RowExpr */
+ RowExpr *rowexpr;
+ List *fields;
+
+ /*
+ * If generating an expansion for a var of a named rowtype
+ * (ie, this is a plain relation RTE), then we must include
+ * dummy items for dropped columns. If the var is RECORD (ie,
+ * this is a JOIN), then omit dropped columns.
+ */
+ expandRTE(context->target_rte,
+ this_varno, this_varlevelsup, var->location,
+ (var->vartype != RECORDOID),
+ NULL, &fields);
+ /* Adjust the generated per-field Vars... */
+ fields = (List *) ResolveNew_mutator((Node *) fields,
+ context);
+ rowexpr = makeNode(RowExpr);
+ rowexpr->args = fields;
+ rowexpr->row_typeid = var->vartype;
+ rowexpr->row_format = COERCE_IMPLICIT_CAST;
+ rowexpr->location = -1;
+
+ return (Node *) rowexpr;
}
+
+ /* Normal case for scalar variable */
+ return resolve_one_var(var, context);
}
/* otherwise fall through to copy the var normally */
}
-
- /*
- * Since expression_tree_mutator won't touch subselects, we have to
- * handle them specially.
- */
- if (IsA(node, SubLink))
+ else if (IsA(node, CurrentOfExpr))
{
- SubLink *sublink = (SubLink *) node;
- SubLink *newnode;
+ CurrentOfExpr *cexpr = (CurrentOfExpr *) node;
+ int this_varno = (int) cexpr->cvarno;
- FLATCOPY(newnode, sublink, SubLink);
- MUTATE(newnode->lefthand, sublink->lefthand, List *,
- ResolveNew_mutator, context);
- context->sublevels_up++;
- MUTATE(newnode->subselect, sublink->subselect, Node *,
- ResolveNew_mutator, context);
- context->sublevels_up--;
- return (Node *) newnode;
+ if (this_varno == context->target_varno &&
+ context->sublevels_up == 0)
+ {
+ /*
+ * We get here if a WHERE CURRENT OF expression turns out to apply
+ * to a view. Someday we might be able to translate the
+ * expression to apply to an underlying table of the view, but
+ * right now it's not implemented.
+ */
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("WHERE CURRENT OF on a view is not implemented")));
+ }
+ /* otherwise fall through to copy the expr normally */
}
- if (IsA(node, Query))
+ else if (IsA(node, Query))
{
- Query *query = (Query *) node;
+ /* Recurse into RTE subquery or not-yet-planned sublink subquery */
Query *newnode;
+ bool save_inserted_sublink;
- FLATCOPY(newnode, query, Query);
- MUTATE(newnode->targetList, query->targetList, List *,
- ResolveNew_mutator, context);
- MUTATE(newnode->qual, query->qual, Node *,
- ResolveNew_mutator, context);
- MUTATE(newnode->havingQual, query->havingQual, Node *,
- ResolveNew_mutator, context);
- MUTATE(newnode->jointree, query->jointree, List *,
- ResolveNew_mutator, context);
+ context->sublevels_up++;
+ save_inserted_sublink = context->inserted_sublink;
+ context->inserted_sublink = false;
+ newnode = query_tree_mutator((Query *) node,
+ ResolveNew_mutator,
+ (void *) context,
+ 0);
+ newnode->hasSubLinks |= context->inserted_sublink;
+ context->inserted_sublink = save_inserted_sublink;
+ context->sublevels_up--;
return (Node *) newnode;
}
return expression_tree_mutator(node, ResolveNew_mutator,
(void *) context);
}
-static Node *
-ResolveNew(Node *node, RewriteInfo *info, List *targetlist,
- int sublevels_up)
+Node *
+ResolveNew(Node *node, int target_varno, int sublevels_up,
+ RangeTblEntry *target_rte,
+ List *targetlist, int event, int update_varno)
{
+ Node *result;
ResolveNew_context context;
- context.info = info;
- context.targetlist = targetlist;
+ context.target_varno = target_varno;
context.sublevels_up = sublevels_up;
-
- return ResolveNew_mutator(node, &context);
-}
-
-void
-FixNew(RewriteInfo *info, Query *parsetree)
-{
- info->rule_action->targetList = (List *)
- ResolveNew((Node *) info->rule_action->targetList,
- info, parsetree->targetList, 0);
- info->rule_action->qual = ResolveNew(info->rule_action->qual,
- info, parsetree->targetList, 0);
- info->rule_action->havingQual = ResolveNew(info->rule_action->havingQual,
- info, parsetree->targetList, 0);
- info->rule_action->jointree = (List *)
- ResolveNew((Node *) info->rule_action->jointree,
- info, parsetree->targetList, 0);
-}
-
-/*
- * HandleRIRAttributeRule
- * Replace Vars matching a given RT index with copies of TL expressions.
- *
- * Handles 'on retrieve to relation.attribute
- * do instead retrieve (attribute = expression) w/qual'
- *
- * XXX Why is this not unified with apply_RIR_view()?
- */
-
-typedef struct
-{
- List *rtable;
- List *targetlist;
- int rt_index;
- int attr_num;
- int *modified;
- int *badsql;
- int sublevels_up;
-} HandleRIRAttributeRule_context;
-
-static Node *
-HandleRIRAttributeRule_mutator(Node *node,
- HandleRIRAttributeRule_context *context)
-{
- if (node == NULL)
- return NULL;
- if (IsA(node, Var))
- {
- Var *var = (Var *) node;
- int this_varno = var->varno;
- int this_varattno = var->varattno;
- int this_varlevelsup = var->varlevelsup;
-
- if (this_varno == context->rt_index &&
- this_varattno == context->attr_num &&
- this_varlevelsup == context->sublevels_up)
- {
- if (var->vartype == 32)
- { /* HACK: disallow SET variables */
- *context->modified = TRUE;
- *context->badsql = TRUE;
- return make_null(var->vartype);
- }
- else
- {
- char *name_to_look_for;
-
- name_to_look_for = get_attname(getrelid(this_varno,
- context->rtable),
- this_varattno);
- if (name_to_look_for)
- {
- Node *n;
-
- *context->modified = TRUE;
- n = FindMatchingTLEntry(context->targetlist,
- name_to_look_for);
- if (n == NULL)
- return make_null(var->vartype);
- /* Make a copy of the tlist item to return */
- n = copyObject(n);
-
- /*
- * Adjust varlevelsup if tlist item is from higher
- * query
- */
- if (this_varlevelsup > 0)
- IncrementVarSublevelsUp(n, this_varlevelsup, 0);
- return n;
- }
- }
- }
- /* otherwise fall through to copy the var normally */
- }
+ context.target_rte = target_rte;
+ context.targetlist = targetlist;
+ context.event = event;
+ context.update_varno = update_varno;
+ context.inserted_sublink = false;
/*
- * Since expression_tree_mutator won't touch subselects, we have to
- * handle them specially.
+ * 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.
*/
- if (IsA(node, SubLink))
- {
- SubLink *sublink = (SubLink *) node;
- SubLink *newnode;
+ result = query_or_expression_tree_mutator(node,
+ ResolveNew_mutator,
+ (void *) &context,
+ 0);
- FLATCOPY(newnode, sublink, SubLink);
- MUTATE(newnode->lefthand, sublink->lefthand, List *,
- HandleRIRAttributeRule_mutator, context);
- context->sublevels_up++;
- MUTATE(newnode->subselect, sublink->subselect, Node *,
- HandleRIRAttributeRule_mutator, context);
- context->sublevels_up--;
- return (Node *) newnode;
- }
- if (IsA(node, Query))
+ if (context.inserted_sublink)
{
- Query *query = (Query *) node;
- Query *newnode;
+ if (IsA(result, Query))
+ ((Query *) result)->hasSubLinks = true;
- FLATCOPY(newnode, query, Query);
- MUTATE(newnode->targetList, query->targetList, List *,
- HandleRIRAttributeRule_mutator, context);
- MUTATE(newnode->qual, query->qual, Node *,
- HandleRIRAttributeRule_mutator, context);
- MUTATE(newnode->havingQual, query->havingQual, Node *,
- HandleRIRAttributeRule_mutator, context);
- MUTATE(newnode->jointree, query->jointree, List *,
- HandleRIRAttributeRule_mutator, context);
- return (Node *) newnode;
+ /*
+ * Note: if we're called on a non-Query node then it's the caller's
+ * responsibility to update hasSubLinks in the ancestor Query. This is
+ * pretty fragile and perhaps should be rethought ...
+ */
}
- return expression_tree_mutator(node, HandleRIRAttributeRule_mutator,
- (void *) context);
-}
-void
-HandleRIRAttributeRule(Query *parsetree,
- List *rtable,
- List *targetlist,
- int rt_index,
- int attr_num,
- int *modified,
- int *badsql)
-{
- HandleRIRAttributeRule_context context;
-
- context.rtable = rtable;
- context.targetlist = targetlist;
- context.rt_index = rt_index;
- context.attr_num = attr_num;
- context.modified = modified;
- context.badsql = badsql;
- context.sublevels_up = 0;
-
- parsetree->targetList = (List *)
- HandleRIRAttributeRule_mutator((Node *) parsetree->targetList,
- &context);
- parsetree->qual =
- HandleRIRAttributeRule_mutator(parsetree->qual,
- &context);
- parsetree->havingQual =
- HandleRIRAttributeRule_mutator(parsetree->havingQual,
- &context);
- parsetree->jointree = (List *)
- HandleRIRAttributeRule_mutator((Node *) parsetree->jointree,
- &context);
+ return result;
}