mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-09-09 01:09:25 +02:00
389af07cf0
expression_tree_mutator rather than ad-hoc tree walking code. This shortens the code materially and fixes a fair number of sins of omission. Also, change modifyAggrefQual to *not* recurse into subselects, since its mission is satisfied if it removes aggregate functions from the top level of a WHERE clause. This cures problems with queries of the form SELECT ... WHERE x IN (SELECT ... HAVING something-using-an-aggregate), which would formerly get mucked up by modifyAggrefQual. The routine is still fundamentally broken, of course, but I don't think there's any way to get rid of it before we implement subselects in FROM ...
657 lines
16 KiB
C
657 lines
16 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* rewriteManip.c
|
|
*
|
|
* Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteManip.c,v 1.41 1999/10/01 04:08:24 tgl Exp $
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include "optimizer/clauses.h"
|
|
#include "parser/parsetree.h"
|
|
#include "parser/parse_clause.h"
|
|
#include "rewrite/rewriteManip.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/lsyscache.h"
|
|
|
|
|
|
/* macros borrowed from expression_tree_mutator */
|
|
|
|
#define FLATCOPY(newnode, node, nodetype) \
|
|
( (newnode) = makeNode(nodetype), \
|
|
memcpy((newnode), (node), sizeof(nodetype)) )
|
|
|
|
#define MUTATE(newfield, oldfield, fieldtype, mutator, context) \
|
|
( (newfield) = (fieldtype) mutator((Node *) (oldfield), (context)) )
|
|
|
|
|
|
/*
|
|
* OffsetVarNodes - adjust Vars when appending one query's RT to another
|
|
*
|
|
* 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.
|
|
*
|
|
* 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!
|
|
*/
|
|
|
|
typedef struct {
|
|
int offset;
|
|
int sublevels_up;
|
|
} OffsetVarNodes_context;
|
|
|
|
static bool
|
|
OffsetVarNodes_walker(Node *node, OffsetVarNodes_context *context)
|
|
{
|
|
if (node == NULL)
|
|
return false;
|
|
if (IsA(node, Var))
|
|
{
|
|
Var *var = (Var *) node;
|
|
|
|
if (var->varlevelsup == context->sublevels_up)
|
|
{
|
|
var->varno += context->offset;
|
|
var->varnoold += context->offset;
|
|
}
|
|
return false;
|
|
}
|
|
if (IsA(node, SubLink))
|
|
{
|
|
/*
|
|
* Standard expression_tree_walker will not recurse into subselect,
|
|
* but here we must do so.
|
|
*/
|
|
SubLink *sub = (SubLink *) node;
|
|
|
|
if (OffsetVarNodes_walker((Node *) (sub->lefthand),
|
|
context))
|
|
return true;
|
|
OffsetVarNodes((Node *) (sub->subselect),
|
|
context->offset,
|
|
context->sublevels_up + 1);
|
|
return false;
|
|
}
|
|
if (IsA(node, Query))
|
|
{
|
|
/* Reach here after recursing down into subselect above... */
|
|
Query *qry = (Query *) node;
|
|
|
|
if (OffsetVarNodes_walker((Node *) (qry->targetList),
|
|
context))
|
|
return true;
|
|
if (OffsetVarNodes_walker((Node *) (qry->qual),
|
|
context))
|
|
return true;
|
|
if (OffsetVarNodes_walker((Node *) (qry->havingQual),
|
|
context))
|
|
return true;
|
|
return false;
|
|
}
|
|
return expression_tree_walker(node, OffsetVarNodes_walker,
|
|
(void *) context);
|
|
}
|
|
|
|
void
|
|
OffsetVarNodes(Node *node, int offset, int sublevels_up)
|
|
{
|
|
OffsetVarNodes_context context;
|
|
|
|
context.offset = offset;
|
|
context.sublevels_up = sublevels_up;
|
|
OffsetVarNodes_walker(node, &context);
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*
|
|
* 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!
|
|
*/
|
|
|
|
typedef struct {
|
|
int rt_index;
|
|
int new_index;
|
|
int sublevels_up;
|
|
} ChangeVarNodes_context;
|
|
|
|
static bool
|
|
ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context)
|
|
{
|
|
if (node == NULL)
|
|
return false;
|
|
if (IsA(node, Var))
|
|
{
|
|
Var *var = (Var *) node;
|
|
|
|
if (var->varlevelsup == context->sublevels_up &&
|
|
var->varno == context->rt_index)
|
|
{
|
|
var->varno = context->new_index;
|
|
var->varnoold = context->new_index;
|
|
}
|
|
return false;
|
|
}
|
|
if (IsA(node, SubLink))
|
|
{
|
|
/*
|
|
* Standard expression_tree_walker will not recurse into subselect,
|
|
* but here we must do so.
|
|
*/
|
|
SubLink *sub = (SubLink *) node;
|
|
|
|
if (ChangeVarNodes_walker((Node *) (sub->lefthand),
|
|
context))
|
|
return true;
|
|
ChangeVarNodes((Node *) (sub->subselect),
|
|
context->rt_index,
|
|
context->new_index,
|
|
context->sublevels_up + 1);
|
|
return false;
|
|
}
|
|
if (IsA(node, Query))
|
|
{
|
|
/* Reach here after recursing down into subselect above... */
|
|
Query *qry = (Query *) node;
|
|
|
|
if (ChangeVarNodes_walker((Node *) (qry->targetList),
|
|
context))
|
|
return true;
|
|
if (ChangeVarNodes_walker((Node *) (qry->qual),
|
|
context))
|
|
return true;
|
|
if (ChangeVarNodes_walker((Node *) (qry->havingQual),
|
|
context))
|
|
return true;
|
|
return false;
|
|
}
|
|
return expression_tree_walker(node, ChangeVarNodes_walker,
|
|
(void *) context);
|
|
}
|
|
|
|
void
|
|
ChangeVarNodes(Node *node, int rt_index, int new_index, int sublevels_up)
|
|
{
|
|
ChangeVarNodes_context context;
|
|
|
|
context.rt_index = rt_index;
|
|
context.new_index = new_index;
|
|
context.sublevels_up = sublevels_up;
|
|
ChangeVarNodes_walker(node, &context);
|
|
}
|
|
|
|
/*
|
|
* Add the given qualifier condition to the query's WHERE clause
|
|
*/
|
|
void
|
|
AddQual(Query *parsetree, Node *qual)
|
|
{
|
|
Node *copy,
|
|
*old;
|
|
|
|
if (qual == NULL)
|
|
return;
|
|
|
|
/* 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));
|
|
}
|
|
|
|
/*
|
|
* Add the given havingQual to the one already contained in the parsetree
|
|
* just as AddQual does for the normal 'where' qual
|
|
*/
|
|
void
|
|
AddHavingQual(Query *parsetree, Node *havingQual)
|
|
{
|
|
Node *copy,
|
|
*old;
|
|
|
|
if (havingQual == NULL)
|
|
return;
|
|
|
|
/* INTERSECT want's the original, but we need to copy - Jan */
|
|
copy = copyObject(havingQual);
|
|
|
|
old = parsetree->havingQual;
|
|
if (old == NULL)
|
|
parsetree->havingQual = copy;
|
|
else
|
|
parsetree->havingQual = (Node *) make_andclause(makeList(old, copy, -1));
|
|
}
|
|
|
|
#ifdef NOT_USED
|
|
void
|
|
AddNotHavingQual(Query *parsetree, Node *havingQual)
|
|
{
|
|
Node *notqual;
|
|
|
|
if (havingQual == NULL)
|
|
return;
|
|
|
|
/* Need not copy input qual, because AddHavingQual will... */
|
|
notqual = (Node *) make_notclause((Expr *) havingQual);
|
|
|
|
AddHavingQual(parsetree, notqual);
|
|
}
|
|
#endif
|
|
|
|
void
|
|
AddNotQual(Query *parsetree, Node *qual)
|
|
{
|
|
Node *notqual;
|
|
|
|
if (qual == NULL)
|
|
return;
|
|
|
|
/* Need not copy input qual, because AddQual will... */
|
|
notqual = (Node *) make_notclause((Expr *) qual);
|
|
|
|
AddQual(parsetree, notqual);
|
|
}
|
|
|
|
|
|
/*
|
|
* Add all expressions used by the given GroupClause list to the
|
|
* parsetree's targetlist and groupclause list.
|
|
*
|
|
* tlist is the old targetlist associated with the input groupclauses.
|
|
*
|
|
* XXX shouldn't we be checking to see if there are already matching
|
|
* entries in parsetree->targetlist?
|
|
*/
|
|
void
|
|
AddGroupClause(Query *parsetree, List *group_by, List *tlist)
|
|
{
|
|
List *l;
|
|
|
|
foreach(l, group_by)
|
|
{
|
|
GroupClause *groupclause = (GroupClause *) copyObject(lfirst(l));
|
|
Index refnumber = groupclause->tleSortGroupRef;
|
|
TargetEntry *tle = NULL;
|
|
List *tl;
|
|
|
|
/* Find and copy the groupclause's TLE in the old tlist */
|
|
foreach(tl, tlist)
|
|
{
|
|
if (((TargetEntry *) lfirst(tl))->resdom->ressortgroupref ==
|
|
refnumber)
|
|
{
|
|
tle = (TargetEntry *) copyObject(lfirst(tl));
|
|
break;
|
|
}
|
|
}
|
|
if (tle == NULL)
|
|
elog(ERROR, "AddGroupClause(): GROUP BY entry not found in rules targetlist");
|
|
|
|
/* 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);
|
|
}
|
|
}
|
|
|
|
static Node *
|
|
make_null(Oid type)
|
|
{
|
|
Const *c = makeNode(Const);
|
|
|
|
c->consttype = type;
|
|
c->constlen = get_typlen(type);
|
|
c->constvalue = PointerGetDatum(NULL);
|
|
c->constisnull = true;
|
|
c->constbyval = get_typbyval(type);
|
|
return (Node *) c;
|
|
}
|
|
|
|
#ifdef NOT_USED
|
|
void
|
|
FixResdomTypes(List *tlist)
|
|
{
|
|
List *i;
|
|
|
|
foreach(i, tlist)
|
|
{
|
|
TargetEntry *tle = lfirst(i);
|
|
|
|
if (nodeTag(tle->expr) == T_Var)
|
|
{
|
|
Var *var = (Var *) tle->expr;
|
|
|
|
tle->resdom->restype = var->vartype;
|
|
tle->resdom->restypmod = var->vartypmod;
|
|
}
|
|
}
|
|
}
|
|
|
|
#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)
|
|
{
|
|
TargetEntry *tle = lfirst(i);
|
|
char *resname;
|
|
|
|
resname = tle->resdom->resname;
|
|
if (!strcmp(e_attname, resname))
|
|
return tle->expr;
|
|
}
|
|
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)
|
|
{
|
|
if (node == NULL)
|
|
return NULL;
|
|
if (IsA(node, Var))
|
|
{
|
|
Var *var = (Var *) node;
|
|
int this_varno = (int) var->varno;
|
|
int this_varlevelsup = (int) var->varlevelsup;
|
|
|
|
if (this_varno == context->info->new_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
|
|
{
|
|
/* Make a copy of the tlist item to return */
|
|
n = copyObject(n);
|
|
if (IsA(n, Var))
|
|
{
|
|
((Var *) n)->varlevelsup = this_varlevelsup;
|
|
}
|
|
/* XXX what to do if tlist item is NOT a var?
|
|
* Should we be using something like apply_RIR_adjust_sublevel?
|
|
*/
|
|
return n;
|
|
}
|
|
}
|
|
/* 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))
|
|
{
|
|
SubLink *sublink = (SubLink *) node;
|
|
SubLink *newnode;
|
|
|
|
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 (IsA(node, Query))
|
|
{
|
|
Query *query = (Query *) node;
|
|
Query *newnode;
|
|
|
|
/*
|
|
* XXX original code for ResolveNew only recursed into qual field
|
|
* of subquery. I'm assuming that was an oversight ... tgl 9/99
|
|
*/
|
|
|
|
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);
|
|
return (Node *) newnode;
|
|
}
|
|
return expression_tree_mutator(node, ResolveNew_mutator,
|
|
(void *) context);
|
|
}
|
|
|
|
static Node *
|
|
ResolveNew(Node *node, RewriteInfo *info, List *targetlist,
|
|
int sublevels_up)
|
|
{
|
|
ResolveNew_context context;
|
|
|
|
context.info = info;
|
|
context.targetlist = targetlist;
|
|
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);
|
|
/* XXX original code didn't fix havingQual; presumably an oversight? */
|
|
info->rule_action->havingQual = ResolveNew(info->rule_action->havingQual,
|
|
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
|
|
{
|
|
NameData name_to_look_for;
|
|
|
|
name_to_look_for.data[0] = '\0';
|
|
namestrcpy(&name_to_look_for,
|
|
(char *) get_attname(getrelid(this_varno,
|
|
context->rtable),
|
|
this_varattno));
|
|
if (name_to_look_for.data[0])
|
|
{
|
|
Node *n;
|
|
|
|
*context->modified = TRUE;
|
|
n = FindMatchingTLEntry(context->targetlist,
|
|
(char *) &name_to_look_for);
|
|
if (n == NULL)
|
|
return make_null(var->vartype);
|
|
else
|
|
return copyObject(n);
|
|
}
|
|
}
|
|
}
|
|
/* 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))
|
|
{
|
|
SubLink *sublink = (SubLink *) node;
|
|
SubLink *newnode;
|
|
|
|
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))
|
|
{
|
|
Query *query = (Query *) node;
|
|
Query *newnode;
|
|
|
|
/*
|
|
* XXX original code for HandleRIRAttributeRule only recursed into
|
|
* qual field of subquery. I'm assuming that was an oversight ...
|
|
*/
|
|
|
|
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);
|
|
return (Node *) newnode;
|
|
}
|
|
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);
|
|
/* XXX original code did not fix havingQual ... oversight? */
|
|
parsetree->havingQual = HandleRIRAttributeRule_mutator(parsetree->havingQual,
|
|
&context);
|
|
}
|