
in Resdom and GroupClause so changing of resno's doesn't confuse the grouping any more. Jan
1076 lines
29 KiB
C
1076 lines
29 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* setrefs.c
|
|
* Routines to change varno/attno entries to contain references
|
|
*
|
|
* Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* $Header: /cvsroot/pgsql/src/backend/optimizer/plan/setrefs.c,v 1.46 1999/05/12 15:01:39 wieck Exp $
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include <sys/types.h>
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "nodes/pg_list.h"
|
|
#include "nodes/plannodes.h"
|
|
#include "nodes/primnodes.h"
|
|
#include "nodes/relation.h"
|
|
|
|
#include "utils/elog.h"
|
|
#include "nodes/nodeFuncs.h"
|
|
#include "nodes/makefuncs.h"
|
|
|
|
#include "optimizer/internal.h"
|
|
#include "optimizer/clauses.h"
|
|
#include "optimizer/restrictinfo.h"
|
|
#include "optimizer/keys.h"
|
|
#include "optimizer/planmain.h"
|
|
#include "optimizer/tlist.h"
|
|
#include "optimizer/var.h"
|
|
#include "optimizer/tlist.h"
|
|
|
|
static void set_join_tlist_references(Join *join);
|
|
static void set_nonamescan_tlist_references(SeqScan *nonamescan);
|
|
static void set_noname_tlist_references(Noname *noname);
|
|
static Node *replace_clause_joinvar_refs(Node *clause,
|
|
List *outer_tlist,
|
|
List *inner_tlist);
|
|
static Var *replace_joinvar_refs(Var *var,
|
|
List *outer_tlist,
|
|
List *inner_tlist);
|
|
static List *tlist_noname_references(Oid nonameid, List *tlist);
|
|
static bool OperandIsInner(Node *opnd, int inner_relid);
|
|
static List *pull_agg_clause(Node *clause);
|
|
static Node *del_agg_clause(Node *clause);
|
|
static void set_result_tlist_references(Result *resultNode);
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* SUBPLAN REFERENCES
|
|
*
|
|
*****************************************************************************/
|
|
|
|
/*
|
|
* set_tlist_references
|
|
* Modifies the target list of nodes in a plan to reference target lists
|
|
* at lower levels.
|
|
*
|
|
* 'plan' is the plan whose target list and children's target lists will
|
|
* be modified
|
|
*
|
|
* Returns nothing of interest, but modifies internal fields of nodes.
|
|
*
|
|
*/
|
|
void
|
|
set_tlist_references(Plan *plan)
|
|
{
|
|
if (plan == NULL)
|
|
return;
|
|
|
|
if (IsA_Join(plan))
|
|
set_join_tlist_references((Join *) plan);
|
|
else if (IsA(plan, SeqScan) &&plan->lefttree &&
|
|
IsA_Noname(plan->lefttree))
|
|
set_nonamescan_tlist_references((SeqScan *) plan);
|
|
else if (IsA(plan, Sort))
|
|
set_noname_tlist_references((Noname *) plan);
|
|
else if (IsA(plan, Result))
|
|
set_result_tlist_references((Result *) plan);
|
|
else if (IsA(plan, Hash))
|
|
set_tlist_references(plan->lefttree);
|
|
}
|
|
|
|
/*
|
|
* set_join_tlist_references
|
|
* Modifies the target list of a join node by setting the varnos and
|
|
* varattnos to reference the target list of the outer and inner join
|
|
* relations.
|
|
*
|
|
* Creates a target list for a join node to contain references by setting
|
|
* varno values to OUTER or INNER and setting attno values to the
|
|
* result domain number of either the corresponding outer or inner join
|
|
* tuple.
|
|
*
|
|
* 'join' is a join plan node
|
|
*
|
|
* Returns nothing of interest, but modifies internal fields of nodes.
|
|
*
|
|
*/
|
|
static void
|
|
set_join_tlist_references(Join *join)
|
|
{
|
|
Plan *outer = ((Plan *) join)->lefttree;
|
|
Plan *inner = ((Plan *) join)->righttree;
|
|
List *outer_tlist = ((outer == NULL) ? NIL : outer->targetlist);
|
|
List *inner_tlist = ((inner == NULL) ? NIL : inner->targetlist);
|
|
List *new_join_targetlist = NIL;
|
|
List *qptlist = ((Plan *) join)->targetlist;
|
|
List *entry;
|
|
|
|
foreach(entry, qptlist)
|
|
{
|
|
TargetEntry *xtl = (TargetEntry *) lfirst(entry);
|
|
Node *joinvar = replace_clause_joinvar_refs(xtl->expr,
|
|
outer_tlist,
|
|
inner_tlist);
|
|
new_join_targetlist = lappend(new_join_targetlist,
|
|
makeTargetEntry(xtl->resdom, joinvar));
|
|
}
|
|
|
|
((Plan *) join)->targetlist = new_join_targetlist;
|
|
if (outer != NULL)
|
|
set_tlist_references(outer);
|
|
if (inner != NULL)
|
|
set_tlist_references(inner);
|
|
}
|
|
|
|
/*
|
|
* set_nonamescan_tlist_references
|
|
* Modifies the target list of a node that scans a noname relation (i.e., a
|
|
* sort or hash node) so that the varnos refer to the child noname.
|
|
*
|
|
* 'nonamescan' is a seqscan node
|
|
*
|
|
* Returns nothing of interest, but modifies internal fields of nodes.
|
|
*
|
|
*/
|
|
static void
|
|
set_nonamescan_tlist_references(SeqScan *nonamescan)
|
|
{
|
|
Noname *noname = (Noname *) ((Plan *) nonamescan)->lefttree;
|
|
|
|
((Plan *) nonamescan)->targetlist = tlist_noname_references(noname->nonameid,
|
|
((Plan *) nonamescan)->targetlist);
|
|
set_noname_tlist_references(noname);
|
|
}
|
|
|
|
/*
|
|
* set_noname_tlist_references
|
|
* The noname's vars are made consistent with (actually, identical to) the
|
|
* modified version of the target list of the node from which noname node
|
|
* receives its tuples.
|
|
*
|
|
* 'noname' is a noname (e.g., sort, hash) plan node
|
|
*
|
|
* Returns nothing of interest, but modifies internal fields of nodes.
|
|
*
|
|
*/
|
|
static void
|
|
set_noname_tlist_references(Noname *noname)
|
|
{
|
|
Plan *source = ((Plan *) noname)->lefttree;
|
|
|
|
if (source != NULL)
|
|
{
|
|
set_tlist_references(source);
|
|
((Plan *) noname)->targetlist = copy_vars(((Plan *) noname)->targetlist,
|
|
(source)->targetlist);
|
|
}
|
|
else
|
|
elog(ERROR, "calling set_noname_tlist_references with empty lefttree");
|
|
}
|
|
|
|
/*
|
|
* join_references
|
|
* Creates a new set of join clauses by changing the varno/varattno
|
|
* values of variables in the clauses to reference target list values
|
|
* from the outer and inner join relation target lists.
|
|
* This is just an external interface for replace_clause_joinvar_refs.
|
|
*
|
|
* 'clauses' is the list of join clauses
|
|
* 'outer_tlist' is the target list of the outer join relation
|
|
* 'inner_tlist' is the target list of the inner join relation
|
|
*
|
|
* Returns the new join clauses. The original clause structure is
|
|
* not modified.
|
|
*
|
|
*/
|
|
List *
|
|
join_references(List *clauses,
|
|
List *outer_tlist,
|
|
List *inner_tlist)
|
|
{
|
|
return (List *) replace_clause_joinvar_refs((Node *) clauses,
|
|
outer_tlist,
|
|
inner_tlist);
|
|
}
|
|
|
|
/*
|
|
* index_outerjoin_references
|
|
* Given a list of join clauses, replace the operand corresponding to the
|
|
* outer relation in the join with references to the corresponding target
|
|
* list element in 'outer_tlist' (the outer is rather obscurely
|
|
* identified as the side that doesn't contain a var whose varno equals
|
|
* 'inner_relid').
|
|
*
|
|
* As a side effect, the operator is replaced by the regproc id.
|
|
*
|
|
* 'inner_indxqual' is the list of join clauses (so-called because they
|
|
* are used as qualifications for the inner (inbex) scan of a nestloop)
|
|
*
|
|
* Returns the new list of clauses.
|
|
*
|
|
*/
|
|
List *
|
|
index_outerjoin_references(List *inner_indxqual,
|
|
List *outer_tlist,
|
|
Index inner_relid)
|
|
{
|
|
List *t_list = NIL;
|
|
Expr *temp = NULL;
|
|
List *t_clause = NIL;
|
|
Expr *clause = NULL;
|
|
|
|
foreach(t_clause, inner_indxqual)
|
|
{
|
|
clause = lfirst(t_clause);
|
|
|
|
/*
|
|
* if inner scan on the right.
|
|
*/
|
|
if (OperandIsInner((Node *) get_rightop(clause), inner_relid))
|
|
{
|
|
Var *joinvar = (Var *)
|
|
replace_clause_joinvar_refs((Node *) get_leftop(clause),
|
|
outer_tlist,
|
|
NIL);
|
|
|
|
temp = make_opclause(replace_opid((Oper *) ((Expr *) clause)->oper),
|
|
joinvar,
|
|
get_rightop(clause));
|
|
t_list = lappend(t_list, temp);
|
|
}
|
|
else
|
|
{
|
|
/* inner scan on left */
|
|
Var *joinvar = (Var *)
|
|
replace_clause_joinvar_refs((Node *) get_rightop(clause),
|
|
outer_tlist,
|
|
NIL);
|
|
|
|
temp = make_opclause(replace_opid((Oper *) ((Expr *) clause)->oper),
|
|
get_leftop(clause),
|
|
joinvar);
|
|
t_list = lappend(t_list, temp);
|
|
}
|
|
|
|
}
|
|
return t_list;
|
|
}
|
|
|
|
/*
|
|
* replace_clause_joinvar_refs
|
|
* replace_joinvar_refs
|
|
*
|
|
* Replaces all variables within a join clause with a new var node
|
|
* whose varno/varattno fields contain a reference to a target list
|
|
* element from either the outer or inner join relation.
|
|
*
|
|
* 'clause' is the join clause
|
|
* 'outer_tlist' is the target list of the outer join relation
|
|
* 'inner_tlist' is the target list of the inner join relation
|
|
*
|
|
* Returns the new join clause.
|
|
* NB: it is critical that the original clause structure not be modified!
|
|
* The changes must be applied to a copy.
|
|
*
|
|
* XXX the current implementation does not copy unchanged primitive
|
|
* nodes; they remain shared with the original. Is this safe?
|
|
*/
|
|
static Node *
|
|
replace_clause_joinvar_refs(Node *clause,
|
|
List *outer_tlist,
|
|
List *inner_tlist)
|
|
{
|
|
if (clause == NULL)
|
|
return NULL;
|
|
if (IsA(clause, Var))
|
|
{
|
|
Var *temp = replace_joinvar_refs((Var *) clause,
|
|
outer_tlist, inner_tlist);
|
|
if (temp != NULL)
|
|
return (Node *) temp;
|
|
else
|
|
return clause;
|
|
}
|
|
else if (single_node(clause))
|
|
return clause;
|
|
else if (and_clause(clause))
|
|
{
|
|
return (Node *) make_andclause((List *)
|
|
replace_clause_joinvar_refs((Node *) ((Expr *) clause)->args,
|
|
outer_tlist,
|
|
inner_tlist));
|
|
}
|
|
else if (or_clause(clause))
|
|
{
|
|
return (Node *) make_orclause((List *)
|
|
replace_clause_joinvar_refs((Node *) ((Expr *) clause)->args,
|
|
outer_tlist,
|
|
inner_tlist));
|
|
}
|
|
else if (IsA(clause, ArrayRef))
|
|
{
|
|
ArrayRef *oldnode = (ArrayRef *) clause;
|
|
ArrayRef *newnode = makeNode(ArrayRef);
|
|
|
|
newnode->refattrlength = oldnode->refattrlength;
|
|
newnode->refelemlength = oldnode->refelemlength;
|
|
newnode->refelemtype = oldnode->refelemtype;
|
|
newnode->refelembyval = oldnode->refelembyval;
|
|
newnode->refupperindexpr = (List *)
|
|
replace_clause_joinvar_refs((Node *) oldnode->refupperindexpr,
|
|
outer_tlist,
|
|
inner_tlist);
|
|
newnode->reflowerindexpr = (List *)
|
|
replace_clause_joinvar_refs((Node *) oldnode->reflowerindexpr,
|
|
outer_tlist,
|
|
inner_tlist);
|
|
newnode->refexpr =
|
|
replace_clause_joinvar_refs(oldnode->refexpr,
|
|
outer_tlist,
|
|
inner_tlist);
|
|
newnode->refassgnexpr =
|
|
replace_clause_joinvar_refs(oldnode->refassgnexpr,
|
|
outer_tlist,
|
|
inner_tlist);
|
|
|
|
return (Node *) newnode;
|
|
}
|
|
else if (is_funcclause(clause))
|
|
{
|
|
return (Node *) make_funcclause(
|
|
(Func *) ((Expr *) clause)->oper,
|
|
(List *) replace_clause_joinvar_refs(
|
|
(Node *) ((Expr *) clause)->args,
|
|
outer_tlist,
|
|
inner_tlist));
|
|
}
|
|
else if (not_clause(clause))
|
|
{
|
|
return (Node *) make_notclause((Expr *)
|
|
replace_clause_joinvar_refs(
|
|
(Node *) get_notclausearg((Expr *) clause),
|
|
outer_tlist,
|
|
inner_tlist));
|
|
}
|
|
else if (is_opclause(clause))
|
|
{
|
|
return (Node *) make_opclause(
|
|
replace_opid((Oper *) ((Expr *) clause)->oper),
|
|
(Var *) replace_clause_joinvar_refs(
|
|
(Node *) get_leftop((Expr *) clause),
|
|
outer_tlist,
|
|
inner_tlist),
|
|
(Var *) replace_clause_joinvar_refs(
|
|
(Node *) get_rightop((Expr *) clause),
|
|
outer_tlist,
|
|
inner_tlist));
|
|
}
|
|
else if (IsA(clause, List))
|
|
{
|
|
List *t_list = NIL;
|
|
List *subclause;
|
|
|
|
foreach(subclause, (List *) clause)
|
|
{
|
|
t_list = lappend(t_list,
|
|
replace_clause_joinvar_refs(lfirst(subclause),
|
|
outer_tlist,
|
|
inner_tlist));
|
|
}
|
|
return (Node *) t_list;
|
|
}
|
|
else if (is_subplan(clause))
|
|
{
|
|
/* This is a tad wasteful of space, but it works... */
|
|
Expr *newclause = (Expr *) copyObject(clause);
|
|
newclause->args = (List *)
|
|
replace_clause_joinvar_refs((Node *) newclause->args,
|
|
outer_tlist,
|
|
inner_tlist);
|
|
((SubPlan *) newclause->oper)->sublink->oper = (List *)
|
|
replace_clause_joinvar_refs(
|
|
(Node *) ((SubPlan *) newclause->oper)->sublink->oper,
|
|
outer_tlist,
|
|
inner_tlist);
|
|
return (Node *) newclause;
|
|
}
|
|
else if (IsA(clause, CaseExpr))
|
|
{
|
|
CaseExpr *oldnode = (CaseExpr *) clause;
|
|
CaseExpr *newnode = makeNode(CaseExpr);
|
|
|
|
newnode->casetype = oldnode->casetype;
|
|
newnode->arg = oldnode->arg; /* XXX should always be null anyway ... */
|
|
newnode->args = (List *)
|
|
replace_clause_joinvar_refs((Node *) oldnode->args,
|
|
outer_tlist,
|
|
inner_tlist);
|
|
newnode->defresult =
|
|
replace_clause_joinvar_refs(oldnode->defresult,
|
|
outer_tlist,
|
|
inner_tlist);
|
|
|
|
return (Node *) newnode;
|
|
}
|
|
else if (IsA(clause, CaseWhen))
|
|
{
|
|
CaseWhen *oldnode = (CaseWhen *) clause;
|
|
CaseWhen *newnode = makeNode(CaseWhen);
|
|
|
|
newnode->expr =
|
|
replace_clause_joinvar_refs(oldnode->expr,
|
|
outer_tlist,
|
|
inner_tlist);
|
|
newnode->result =
|
|
replace_clause_joinvar_refs(oldnode->result,
|
|
outer_tlist,
|
|
inner_tlist);
|
|
|
|
return (Node *) newnode;
|
|
}
|
|
else
|
|
{
|
|
elog(ERROR, "replace_clause_joinvar_refs: unsupported clause %d",
|
|
nodeTag(clause));
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static Var *
|
|
replace_joinvar_refs(Var *var, List *outer_tlist, List *inner_tlist)
|
|
{
|
|
Resdom *outer_resdom;
|
|
|
|
outer_resdom = tlist_member(var, outer_tlist);
|
|
|
|
if (outer_resdom != NULL && IsA(outer_resdom, Resdom))
|
|
{
|
|
return (makeVar(OUTER,
|
|
outer_resdom->resno,
|
|
var->vartype,
|
|
var->vartypmod,
|
|
0,
|
|
var->varnoold,
|
|
var->varoattno));
|
|
}
|
|
else
|
|
{
|
|
Resdom *inner_resdom;
|
|
|
|
inner_resdom = tlist_member(var, inner_tlist);
|
|
if (inner_resdom != NULL && IsA(inner_resdom, Resdom))
|
|
{
|
|
return (makeVar(INNER,
|
|
inner_resdom->resno,
|
|
var->vartype,
|
|
var->vartypmod,
|
|
0,
|
|
var->varnoold,
|
|
var->varoattno));
|
|
}
|
|
}
|
|
return (Var *) NULL;
|
|
}
|
|
|
|
/*
|
|
* tlist_noname_references
|
|
* Creates a new target list for a node that scans a noname relation,
|
|
* setting the varnos to the id of the noname relation and setting varids
|
|
* if necessary (varids are only needed if this is a targetlist internal
|
|
* to the tree, in which case the targetlist entry always contains a var
|
|
* node, so we can just copy it from the noname).
|
|
*
|
|
* 'nonameid' is the id of the noname relation
|
|
* 'tlist' is the target list to be modified
|
|
*
|
|
* Returns new target list
|
|
*
|
|
*/
|
|
static List *
|
|
tlist_noname_references(Oid nonameid,
|
|
List *tlist)
|
|
{
|
|
List *t_list = NIL;
|
|
TargetEntry *noname = (TargetEntry *) NULL;
|
|
TargetEntry *xtl = NULL;
|
|
List *entry;
|
|
|
|
foreach(entry, tlist)
|
|
{
|
|
AttrNumber oattno;
|
|
|
|
xtl = lfirst(entry);
|
|
if (IsA(get_expr(xtl), Var))
|
|
oattno = ((Var *) xtl->expr)->varoattno;
|
|
else
|
|
oattno = 0;
|
|
|
|
noname = makeTargetEntry(xtl->resdom,
|
|
(Node *) makeVar(nonameid,
|
|
xtl->resdom->resno,
|
|
xtl->resdom->restype,
|
|
xtl->resdom->restypmod,
|
|
0,
|
|
nonameid,
|
|
oattno));
|
|
|
|
t_list = lappend(t_list, noname);
|
|
}
|
|
return t_list;
|
|
}
|
|
|
|
/*---------------------------------------------------------
|
|
*
|
|
* set_result_tlist_references
|
|
*
|
|
* Change the target list of a Result node, so that it correctly
|
|
* addresses the tuples returned by its left tree subplan.
|
|
*
|
|
* NOTE:
|
|
* 1) we ignore the right tree! (in the current implementation
|
|
* it is always nil
|
|
* 2) this routine will probably *NOT* work with nested dot
|
|
* fields....
|
|
*/
|
|
static void
|
|
set_result_tlist_references(Result *resultNode)
|
|
{
|
|
Plan *subplan;
|
|
List *resultTargetList;
|
|
List *subplanTargetList;
|
|
|
|
resultTargetList = ((Plan *) resultNode)->targetlist;
|
|
|
|
/*
|
|
* NOTE: we only consider the left tree subplan. This is usually a seq
|
|
* scan.
|
|
*/
|
|
subplan = ((Plan *) resultNode)->lefttree;
|
|
if (subplan != NULL)
|
|
subplanTargetList = subplan->targetlist;
|
|
else
|
|
subplanTargetList = NIL;
|
|
|
|
replace_tlist_with_subplan_refs(resultTargetList,
|
|
(Index) OUTER,
|
|
subplanTargetList);
|
|
}
|
|
|
|
/*---------------------------------------------------------
|
|
*
|
|
* replace_tlist_with_subplan_refs
|
|
*
|
|
* Applies replace_vars_with_subplan_refs() to each entry of a targetlist.
|
|
*/
|
|
void
|
|
replace_tlist_with_subplan_refs(List *tlist,
|
|
Index subvarno,
|
|
List *subplanTargetList)
|
|
{
|
|
List *t;
|
|
|
|
foreach(t, tlist)
|
|
{
|
|
TargetEntry *entry = (TargetEntry *) lfirst(t);
|
|
replace_vars_with_subplan_refs((Node *) get_expr(entry),
|
|
subvarno, subplanTargetList);
|
|
}
|
|
}
|
|
|
|
/*---------------------------------------------------------
|
|
*
|
|
* replace_vars_with_subplan_refs
|
|
*
|
|
* This routine modifies (destructively!) an expression tree so that all
|
|
* Var nodes reference target nodes of a subplan. It is used to fix up
|
|
* target expressions of upper-level plan nodes.
|
|
*
|
|
* 'clause': the tree to be fixed
|
|
* 'subvarno': varno to be assigned to all Vars
|
|
* 'subplanTargetList': target list for subplan
|
|
*
|
|
* Afterwards, all Var nodes have varno = subvarno, varattno = resno
|
|
* of corresponding subplan target.
|
|
*/
|
|
void
|
|
replace_vars_with_subplan_refs(Node *clause,
|
|
Index subvarno,
|
|
List *subplanTargetList)
|
|
{
|
|
List *t;
|
|
|
|
if (clause == NULL)
|
|
return;
|
|
if (IsA(clause, Var))
|
|
{
|
|
/*
|
|
* Ha! A Var node!
|
|
*
|
|
* It could be that this varnode has been created by make_groupplan
|
|
* and is already set up to reference the subplan target list.
|
|
* We recognize that case by varno = 1, varnoold = -1,
|
|
* varattno = varoattno, and varlevelsup = 0. (Probably ought to
|
|
* have an explicit flag, but this should do for now.)
|
|
*/
|
|
Var *var = (Var *) clause;
|
|
TargetEntry *subplanVar;
|
|
|
|
if (var->varno == (Index) 1 &&
|
|
var->varnoold == ((Index) -1) &&
|
|
var->varattno == var->varoattno &&
|
|
var->varlevelsup == 0)
|
|
return; /* OK to leave it alone */
|
|
|
|
/* Otherwise it had better be in the subplan list. */
|
|
subplanVar = match_varid(var, subplanTargetList);
|
|
if (! subplanVar)
|
|
elog(ERROR, "replace_vars_with_subplan_refs: variable not in target list");
|
|
|
|
/*
|
|
* Change the varno & varattno fields of the var node.
|
|
*/
|
|
var->varno = subvarno;
|
|
var->varattno = subplanVar->resdom->resno;
|
|
}
|
|
else if (single_node(clause))
|
|
{
|
|
/* do nothing! */
|
|
}
|
|
else if (IsA(clause, Iter))
|
|
replace_vars_with_subplan_refs(((Iter *) clause)->iterexpr,
|
|
subvarno, subplanTargetList);
|
|
else if (is_subplan(clause))
|
|
{
|
|
foreach(t, ((Expr *) clause)->args)
|
|
replace_vars_with_subplan_refs(lfirst(t),
|
|
subvarno, subplanTargetList);
|
|
foreach(t, ((SubPlan *) ((Expr *) clause)->oper)->sublink->oper)
|
|
replace_vars_with_subplan_refs(lfirst(((Expr *) lfirst(t))->args),
|
|
subvarno, subplanTargetList);
|
|
}
|
|
else if (IsA(clause, Expr))
|
|
{
|
|
/*
|
|
* Recursively scan the arguments of an expression.
|
|
* NOTE: this must come after is_subplan() case since
|
|
* subplan is a kind of Expr node.
|
|
*/
|
|
foreach(t, ((Expr *) clause)->args)
|
|
replace_vars_with_subplan_refs(lfirst(t),
|
|
subvarno, subplanTargetList);
|
|
}
|
|
else if (IsA(clause, Aggref))
|
|
replace_vars_with_subplan_refs(((Aggref *) clause)->target,
|
|
subvarno, subplanTargetList);
|
|
else if (IsA(clause, ArrayRef))
|
|
{
|
|
ArrayRef *aref = (ArrayRef *) clause;
|
|
foreach(t, aref->refupperindexpr)
|
|
replace_vars_with_subplan_refs(lfirst(t),
|
|
subvarno, subplanTargetList);
|
|
foreach(t, aref->reflowerindexpr)
|
|
replace_vars_with_subplan_refs(lfirst(t),
|
|
subvarno, subplanTargetList);
|
|
replace_vars_with_subplan_refs(aref->refexpr,
|
|
subvarno, subplanTargetList);
|
|
replace_vars_with_subplan_refs(aref->refassgnexpr,
|
|
subvarno, subplanTargetList);
|
|
}
|
|
else if (case_clause(clause))
|
|
{
|
|
foreach(t, ((CaseExpr *) clause)->args)
|
|
{
|
|
CaseWhen *when = (CaseWhen *) lfirst(t);
|
|
replace_vars_with_subplan_refs(when->expr,
|
|
subvarno, subplanTargetList);
|
|
replace_vars_with_subplan_refs(when->result,
|
|
subvarno, subplanTargetList);
|
|
}
|
|
replace_vars_with_subplan_refs(((CaseExpr *) clause)->defresult,
|
|
subvarno, subplanTargetList);
|
|
}
|
|
else
|
|
{
|
|
elog(ERROR, "replace_vars_with_subplan_refs: Cannot handle node type %d",
|
|
nodeTag(clause));
|
|
}
|
|
}
|
|
|
|
static bool
|
|
OperandIsInner(Node *opnd, int inner_relid)
|
|
{
|
|
|
|
/*
|
|
* Can be the inner scan if its a varnode or a function and the
|
|
* inner_relid is equal to the varnode's var number or in the case of
|
|
* a function the first argument's var number (all args in a
|
|
* functional index are from the same relation).
|
|
*/
|
|
if (IsA(opnd, Var) &&
|
|
(inner_relid == ((Var *) opnd)->varno))
|
|
return true;
|
|
if (is_funcclause(opnd))
|
|
{
|
|
List *firstArg = lfirst(((Expr *) opnd)->args);
|
|
|
|
if (IsA(firstArg, Var) &&
|
|
(inner_relid == ((Var *) firstArg)->varno))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
*****************************************************************************/
|
|
|
|
/*---------------------------------------------------------
|
|
*
|
|
* set_agg_tlist_references -
|
|
* This routine has several responsibilities:
|
|
* * Update the target list of an Agg node so that it points to
|
|
* the tuples returned by its left tree subplan.
|
|
* * If there is a qual list (from a HAVING clause), similarly update
|
|
* vars in it to point to the subplan target list.
|
|
* * Generate the aggNode->aggs list of Aggref nodes contained in the Agg.
|
|
*
|
|
* The return value is TRUE if all qual clauses include Aggrefs, or FALSE
|
|
* if any do not (caller may choose to raise an error condition).
|
|
*/
|
|
bool
|
|
set_agg_tlist_references(Agg *aggNode)
|
|
{
|
|
List *subplanTargetList;
|
|
List *tl;
|
|
List *ql;
|
|
bool all_quals_ok;
|
|
|
|
subplanTargetList = aggNode->plan.lefttree->targetlist;
|
|
aggNode->aggs = NIL;
|
|
|
|
foreach(tl, aggNode->plan.targetlist)
|
|
{
|
|
TargetEntry *tle = lfirst(tl);
|
|
|
|
replace_vars_with_subplan_refs(tle->expr,
|
|
(Index) 0,
|
|
subplanTargetList);
|
|
aggNode->aggs = nconc(pull_agg_clause(tle->expr), aggNode->aggs);
|
|
}
|
|
|
|
all_quals_ok = true;
|
|
foreach(ql, aggNode->plan.qual)
|
|
{
|
|
Node *qual = lfirst(ql);
|
|
List *qualaggs;
|
|
|
|
replace_vars_with_subplan_refs(qual,
|
|
(Index) 0,
|
|
subplanTargetList);
|
|
qualaggs = pull_agg_clause(qual);
|
|
if (qualaggs == NIL)
|
|
all_quals_ok = false; /* this qual clause has no agg functions! */
|
|
else
|
|
aggNode->aggs = nconc(qualaggs, aggNode->aggs);
|
|
}
|
|
|
|
return all_quals_ok;
|
|
}
|
|
|
|
/*
|
|
* Make a list of all Aggref nodes contained in the given expression.
|
|
*/
|
|
static List *
|
|
pull_agg_clause(Node *clause)
|
|
{
|
|
List *agg_list = NIL;
|
|
List *t;
|
|
|
|
if (clause == NULL)
|
|
return NIL;
|
|
else if (single_node(clause))
|
|
return NIL;
|
|
else if (IsA(clause, Iter))
|
|
return pull_agg_clause(((Iter *) clause)->iterexpr);
|
|
else if (is_subplan(clause))
|
|
{
|
|
SubLink *sublink = ((SubPlan *) ((Expr *) clause)->oper)->sublink;
|
|
|
|
/*
|
|
* Only the lefthand side of the sublink should be checked for
|
|
* aggregates to be attached to the aggs list
|
|
*/
|
|
foreach(t, sublink->lefthand)
|
|
agg_list = nconc(pull_agg_clause(lfirst(t)), agg_list);
|
|
/* The first argument of ...->oper has also to be checked */
|
|
foreach(t, sublink->oper)
|
|
agg_list = nconc(pull_agg_clause(lfirst(t)), agg_list);
|
|
}
|
|
else if (IsA(clause, Expr))
|
|
{
|
|
/*
|
|
* Recursively scan the arguments of an expression.
|
|
* NOTE: this must come after is_subplan() case since
|
|
* subplan is a kind of Expr node.
|
|
*/
|
|
foreach(t, ((Expr *) clause)->args)
|
|
agg_list = nconc(pull_agg_clause(lfirst(t)), agg_list);
|
|
}
|
|
else if (IsA(clause, Aggref))
|
|
{
|
|
return lcons(clause,
|
|
pull_agg_clause(((Aggref *) clause)->target));
|
|
}
|
|
else if (IsA(clause, ArrayRef))
|
|
{
|
|
ArrayRef *aref = (ArrayRef *) clause;
|
|
foreach(t, aref->refupperindexpr)
|
|
agg_list = nconc(pull_agg_clause(lfirst(t)), agg_list);
|
|
foreach(t, aref->reflowerindexpr)
|
|
agg_list = nconc(pull_agg_clause(lfirst(t)), agg_list);
|
|
agg_list = nconc(pull_agg_clause(aref->refexpr), agg_list);
|
|
agg_list = nconc(pull_agg_clause(aref->refassgnexpr), agg_list);
|
|
}
|
|
else if (case_clause(clause))
|
|
{
|
|
foreach(t, ((CaseExpr *) clause)->args)
|
|
{
|
|
CaseWhen *when = (CaseWhen *) lfirst(t);
|
|
agg_list = nconc(agg_list, pull_agg_clause(when->expr));
|
|
agg_list = nconc(agg_list, pull_agg_clause(when->result));
|
|
}
|
|
agg_list = nconc(pull_agg_clause(((CaseExpr *) clause)->defresult),
|
|
agg_list);
|
|
}
|
|
else
|
|
{
|
|
elog(ERROR, "pull_agg_clause: Cannot handle node type %d",
|
|
nodeTag(clause));
|
|
}
|
|
|
|
return agg_list;
|
|
}
|
|
|
|
|
|
/*
|
|
* del_agg_tlist_references
|
|
* Remove the Agg nodes from the target list
|
|
* We do this so inheritance only does aggregates in the upper node
|
|
*/
|
|
void
|
|
del_agg_tlist_references(List *tlist)
|
|
{
|
|
List *tl;
|
|
|
|
foreach(tl, tlist)
|
|
{
|
|
TargetEntry *tle = lfirst(tl);
|
|
|
|
tle->expr = del_agg_clause(tle->expr);
|
|
}
|
|
}
|
|
|
|
static Node *
|
|
del_agg_clause(Node *clause)
|
|
{
|
|
List *t;
|
|
|
|
if (clause == NULL)
|
|
return clause;
|
|
|
|
if (IsA(clause, Var))
|
|
return clause;
|
|
else if (is_funcclause(clause))
|
|
{
|
|
|
|
/*
|
|
* This is a function. Recursively call this routine for its
|
|
* arguments...
|
|
*/
|
|
foreach(t, ((Expr *) clause)->args)
|
|
lfirst(t) = del_agg_clause(lfirst(t));
|
|
}
|
|
else if (IsA(clause, Aggref))
|
|
{
|
|
|
|
/* here is the real action, to remove the Agg node */
|
|
return del_agg_clause(((Aggref *) clause)->target);
|
|
|
|
}
|
|
else if (IsA(clause, ArrayRef))
|
|
{
|
|
ArrayRef *aref = (ArrayRef *) clause;
|
|
|
|
/*
|
|
* This is an arrayref. Recursively call this routine for its
|
|
* expression and its index expression...
|
|
*/
|
|
foreach(t, aref->refupperindexpr)
|
|
lfirst(t) = del_agg_clause(lfirst(t));
|
|
foreach(t, aref->reflowerindexpr)
|
|
lfirst(t) = del_agg_clause(lfirst(t));
|
|
aref->refexpr = del_agg_clause(aref->refexpr);
|
|
aref->refassgnexpr = del_agg_clause(aref->refassgnexpr);
|
|
}
|
|
else if (is_opclause(clause))
|
|
{
|
|
|
|
/*
|
|
* This is an operator. Recursively call this routine for both its
|
|
* left and right operands
|
|
*/
|
|
Node *left = (Node *) get_leftop((Expr *) clause);
|
|
Node *right = (Node *) get_rightop((Expr *) clause);
|
|
|
|
if (left != (Node *) NULL)
|
|
left = del_agg_clause(left);
|
|
if (right != (Node *) NULL)
|
|
right = del_agg_clause(right);
|
|
}
|
|
else if (IsA(clause, Param) ||IsA(clause, Const))
|
|
return clause;
|
|
else
|
|
{
|
|
|
|
/*
|
|
* Ooops! we can not handle that!
|
|
*/
|
|
elog(ERROR, "del_agg_clause: Can not handle this tlist!\n");
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* check_having_for_ungrouped_vars takes the havingQual and the list of
|
|
* GROUP BY clauses and checks for subplans in the havingQual that are being
|
|
* passed ungrouped variables as parameters. In other contexts, ungrouped
|
|
* vars in the havingQual will be detected by the parser (see parse_agg.c,
|
|
* exprIsAggOrGroupCol()). But that routine currently does not check subplans,
|
|
* because the necessary info is not computed until the planner runs.
|
|
* This ought to be cleaned up someday.
|
|
*
|
|
* NOTE: the havingClause has been cnf-ified, so AND subclauses have been
|
|
* turned into a plain List. Thus, this routine has to cope with List nodes
|
|
* where the routine above does not...
|
|
*/
|
|
|
|
void
|
|
check_having_for_ungrouped_vars(Node *clause, List *groupClause,
|
|
List *targetList)
|
|
{
|
|
List *t;
|
|
|
|
if (clause == NULL)
|
|
return;
|
|
|
|
if (IsA(clause, Var))
|
|
{
|
|
/* Ignore vars elsewhere in the having clause, since the
|
|
* parser already checked 'em.
|
|
*/
|
|
}
|
|
else if (single_node(clause))
|
|
{
|
|
/* ignore */
|
|
}
|
|
else if (IsA(clause, Iter))
|
|
{
|
|
check_having_for_ungrouped_vars(((Iter *) clause)->iterexpr,
|
|
groupClause, targetList);
|
|
}
|
|
else if (is_subplan(clause))
|
|
{
|
|
/*
|
|
* The args list of the subplan node represents attributes from outside
|
|
* passed into the sublink.
|
|
*/
|
|
foreach(t, ((Expr *) clause)->args)
|
|
{
|
|
bool contained_in_group_clause = false;
|
|
List *gl;
|
|
|
|
foreach(gl, groupClause)
|
|
{
|
|
if (var_equal(lfirst(t),
|
|
get_groupclause_expr((GroupClause *)
|
|
lfirst(gl), targetList)))
|
|
{
|
|
contained_in_group_clause = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!contained_in_group_clause)
|
|
elog(ERROR, "Sub-SELECT in HAVING clause must use only GROUPed attributes from outer SELECT");
|
|
}
|
|
}
|
|
else if (IsA(clause, Expr))
|
|
{
|
|
/*
|
|
* Recursively scan the arguments of an expression.
|
|
* NOTE: this must come after is_subplan() case since
|
|
* subplan is a kind of Expr node.
|
|
*/
|
|
foreach(t, ((Expr *) clause)->args)
|
|
check_having_for_ungrouped_vars(lfirst(t), groupClause,
|
|
targetList);
|
|
}
|
|
else if (IsA(clause, List))
|
|
{
|
|
/*
|
|
* Recursively scan AND subclauses (see NOTE above).
|
|
*/
|
|
foreach(t, ((List *) clause))
|
|
check_having_for_ungrouped_vars(lfirst(t), groupClause,
|
|
targetList);
|
|
}
|
|
else if (IsA(clause, Aggref))
|
|
{
|
|
check_having_for_ungrouped_vars(((Aggref *) clause)->target,
|
|
groupClause, targetList);
|
|
}
|
|
else if (IsA(clause, ArrayRef))
|
|
{
|
|
ArrayRef *aref = (ArrayRef *) clause;
|
|
|
|
/*
|
|
* This is an arrayref. Recursively call this routine for its
|
|
* expression and its index expression...
|
|
*/
|
|
foreach(t, aref->refupperindexpr)
|
|
check_having_for_ungrouped_vars(lfirst(t), groupClause,
|
|
targetList);
|
|
foreach(t, aref->reflowerindexpr)
|
|
check_having_for_ungrouped_vars(lfirst(t), groupClause,
|
|
targetList);
|
|
check_having_for_ungrouped_vars(aref->refexpr, groupClause,
|
|
targetList);
|
|
check_having_for_ungrouped_vars(aref->refassgnexpr, groupClause,
|
|
targetList);
|
|
}
|
|
else if (case_clause(clause))
|
|
{
|
|
foreach(t, ((CaseExpr *) clause)->args)
|
|
{
|
|
CaseWhen *when = (CaseWhen *) lfirst(t);
|
|
check_having_for_ungrouped_vars(when->expr, groupClause,
|
|
targetList);
|
|
check_having_for_ungrouped_vars(when->result, groupClause,
|
|
targetList);
|
|
}
|
|
check_having_for_ungrouped_vars(((CaseExpr *) clause)->defresult,
|
|
groupClause, targetList);
|
|
}
|
|
else
|
|
{
|
|
elog(ERROR, "check_having_for_ungrouped_vars: Cannot handle node type %d",
|
|
nodeTag(clause));
|
|
}
|
|
}
|