in src/backend/parser/cypher_parse_agg.c [57:268]
static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
List *groupClauses, List *groupClauseVars,
bool have_non_var_grouping,
List **func_grouped_rels);
static bool check_ungrouped_columns_walker(Node *node,
check_ungrouped_columns_context *context);
static void finalize_grouping_exprs(Node *node, ParseState *pstate, Query *qry,
List *groupClauses, PlannerInfo *root,
bool have_non_var_grouping);
static bool finalize_grouping_exprs_walker(Node *node,
check_ungrouped_columns_context *context);
static List *expand_groupingset_node(GroupingSet *gs);
static List * expand_grouping_sets(List *groupingSets, int limit);
/*
* From PG's parseCheckAggregates
*
* Check for aggregates where they shouldn't be and improper grouping.
* This function should be called after the target list and qualifications
* are finalized.
*
* Misplaced aggregates are now mostly detected in transformAggregateCall,
* but it seems more robust to check for aggregates in recursive queries
* only after everything is finalized. In any case it's hard to detect
* improper grouping on-the-fly, so we have to make another pass over the
* query for that.
*/
void parse_check_aggregates(ParseState *pstate, Query *qry)
{
List *gset_common = NIL;
List *groupClauses = NIL;
List *groupClauseCommonVars = NIL;
bool have_non_var_grouping;
List *func_grouped_rels = NIL;
ListCell *l;
bool hasJoinRTEs;
bool hasSelfRefRTEs;
PlannerInfo *root = NULL;
Node *clause;
/* This should only be called if we found aggregates or grouping */
Assert(pstate->p_hasAggs || qry->groupClause || qry->havingQual || qry->groupingSets);
/*
* If we have grouping sets, expand them and find the intersection of all
* sets.
*/
if (qry->groupingSets)
{
/*
* The limit of 4096 is arbitrary and exists simply to avoid resource
* issues from pathological constructs.
*/
List *gsets = expand_grouping_sets(qry->groupingSets, 4096);
if (!gsets)
ereport(ERROR,
(errcode(ERRCODE_STATEMENT_TOO_COMPLEX),
errmsg("too many grouping sets present (maximum 4096)"),
parser_errposition(pstate,
qry->groupClause ?
exprLocation((Node *) qry->groupClause) :
exprLocation((Node *) qry->groupingSets))));
/*
* The intersection will often be empty, so help things along by
* seeding the intersect with the smallest set.
*/
gset_common = linitial(gsets);
if (gset_common)
{
for_each_cell(l, gsets, lnext(gsets, list_head(gsets)))
{
gset_common = list_intersection_int(gset_common, lfirst(l));
if (!gset_common)
break;
}
}
/*
* If there was only one grouping set in the expansion, AND if the
* groupClause is non-empty (meaning that the grouping set is not
* empty either), then we can ditch the grouping set and pretend we
* just had a normal GROUP BY.
*/
if (list_length(gsets) == 1 && qry->groupClause)
qry->groupingSets = NIL;
}
/*
* Scan the range table to see if there are JOIN or self-reference CTE
* entries. We'll need this info below.
*/
hasJoinRTEs = hasSelfRefRTEs = false;
foreach(l, pstate->p_rtable)
{
RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
if (rte->rtekind == RTE_JOIN)
hasJoinRTEs = true;
else if (rte->rtekind == RTE_CTE && rte->self_reference)
hasSelfRefRTEs = true;
}
/*
* Build a list of the acceptable GROUP BY expressions for use by
* check_ungrouped_columns().
*
* We get the TLE, not just the expr, because GROUPING wants to know the
* sortgroupref.
*/
foreach(l, qry->groupClause)
{
SortGroupClause *grpcl = (SortGroupClause *) lfirst(l);
TargetEntry *expr;
expr = get_sortgroupclause_tle(grpcl, qry->targetList);
if (expr == NULL)
continue; /* probably cannot happen */
groupClauses = lcons(expr, groupClauses);
}
/*
* If there are join alias vars involved, we have to flatten them to the
* underlying vars, so that aliased and unaliased vars will be correctly
* taken as equal. We can skip the expense of doing this if no rangetable
* entries are RTE_JOIN kind. We use the planner's flatten_join_alias_vars
* routine to do the flattening; it wants a PlannerInfo root node, which
* fortunately can be mostly dummy.
*/
if (hasJoinRTEs)
{
root = makeNode(PlannerInfo);
root->parse = qry;
root->planner_cxt = CurrentMemoryContext;
root->hasJoinRTEs = true;
groupClauses = (List *) flatten_join_alias_vars(root, qry,
(Node *) groupClauses);
}
/*
* Detect whether any of the grouping expressions aren't simple Vars; if
* they're all Vars then we don't have to work so hard in the recursive
* scans. (Note we have to flatten aliases before this.)
*
* Track Vars that are included in all grouping sets separately in
* groupClauseCommonVars, since these are the only ones we can use to
* check for functional dependencies.
*/
have_non_var_grouping = false;
foreach(l, groupClauses)
{
TargetEntry *tle = lfirst(l);
if (!IsA(tle->expr, Var))
{
have_non_var_grouping = true;
}
else if (!qry->groupingSets ||
list_member_int(gset_common, tle->ressortgroupref))
{
groupClauseCommonVars = lappend(groupClauseCommonVars, tle->expr);
}
}
/*
* Check the targetlist and HAVING clause for ungrouped variables.
*
* Note: because we check resjunk tlist elements as well as regular ones,
* this will also find ungrouped variables that came from ORDER BY and
* WINDOW clauses. For that matter, it's also going to examine the
* grouping expressions themselves --- but they'll all pass the test ...
*
* We also finalize GROUPING expressions, but for that we need to traverse
* the original (unflattened) clause in order to modify nodes.
*/
clause = (Node *) qry->targetList;
finalize_grouping_exprs(clause, pstate, qry, groupClauses, root,
have_non_var_grouping);
if (hasJoinRTEs)
{
clause = flatten_join_alias_vars(root, qry, clause);
}
check_ungrouped_columns(clause, pstate, qry, groupClauses,
groupClauseCommonVars, have_non_var_grouping,
&func_grouped_rels);
clause = (Node *) qry->havingQual;
finalize_grouping_exprs(clause, pstate, qry, groupClauses, root,
have_non_var_grouping);
if (hasJoinRTEs)
{
clause = flatten_join_alias_vars(root, qry, clause);
}
check_ungrouped_columns(clause, pstate, qry, groupClauses,
groupClauseCommonVars, have_non_var_grouping,
&func_grouped_rels);
/*
* Per spec, aggregates can't appear in a recursive term.
*/
if (pstate->p_hasAggs && hasSelfRefRTEs)
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_RECURSION),
errmsg("aggregate functions are not allowed in a recursive query's recursive term"),
parser_errposition(pstate, locate_agg_of_level((Node *) qry, 0))));
}
}