Commit f7f41c7c authored by Tom Lane's avatar Tom Lane

Replace generic 'Illegal use of aggregates' error message with one that

shows the specific ungrouped variable being complained of.  Perhaps this
will reduce user confusion...
parent d65a27f9
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planmain.c,v 1.47 1999/11/15 02:00:07 tgl Exp $ * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planmain.c,v 1.48 1999/12/09 05:58:52 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -100,10 +100,7 @@ query_planner(Query *root, ...@@ -100,10 +100,7 @@ query_planner(Query *root,
* Note we do NOT do this for subplans in WHERE; it's legal * Note we do NOT do this for subplans in WHERE; it's legal
* there because WHERE is evaluated pre-GROUP. * there because WHERE is evaluated pre-GROUP.
*/ */
if (check_subplans_for_ungrouped_vars((Node *) tlist, check_subplans_for_ungrouped_vars((Node *) tlist, root, tlist);
root->groupClause,
tlist))
elog(ERROR, "Sub-SELECT must use only GROUPed attributes from outer SELECT");
} }
} }
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.71 1999/11/15 02:00:08 tgl Exp $ * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.72 1999/12/09 05:58:52 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -344,10 +344,9 @@ union_planner(Query *parse) ...@@ -344,10 +344,9 @@ union_planner(Query *parse)
/* Expand SubLinks to SubPlans */ /* Expand SubLinks to SubPlans */
parse->havingQual = SS_process_sublinks(parse->havingQual); parse->havingQual = SS_process_sublinks(parse->havingQual);
/* Check for ungrouped variables passed to subplans */ /* Check for ungrouped variables passed to subplans */
if (check_subplans_for_ungrouped_vars(parse->havingQual, check_subplans_for_ungrouped_vars(parse->havingQual,
parse->groupClause, parse,
parse->targetList)) parse->targetList);
elog(ERROR, "Sub-SELECT must use only GROUPed attributes from outer SELECT");
} }
} }
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.55 1999/11/22 17:56:17 momjian Exp $ * $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.56 1999/12/09 05:58:53 tgl Exp $
* *
* HISTORY * HISTORY
* AUTHOR DATE MAJOR EVENT * AUTHOR DATE MAJOR EVENT
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
#include "optimizer/tlist.h" #include "optimizer/tlist.h"
#include "optimizer/var.h" #include "optimizer/var.h"
#include "parser/parse_type.h" #include "parser/parse_type.h"
#include "parser/parsetree.h"
#include "utils/lsyscache.h" #include "utils/lsyscache.h"
#include "utils/syscache.h" #include "utils/syscache.h"
...@@ -40,7 +41,7 @@ ...@@ -40,7 +41,7 @@
(isnull), true, false, false)) (isnull), true, false, false))
typedef struct { typedef struct {
List *groupClause; Query *query;
List *targetList; List *targetList;
} check_subplans_for_ungrouped_vars_context; } check_subplans_for_ungrouped_vars_context;
...@@ -427,28 +428,30 @@ pull_agg_clause_walker(Node *node, List **listptr) ...@@ -427,28 +428,30 @@ pull_agg_clause_walker(Node *node, List **listptr)
/* /*
* check_subplans_for_ungrouped_vars * check_subplans_for_ungrouped_vars
* Check for subplans that are being passed ungrouped variables as * Check for subplans that are being passed ungrouped variables as
* parameters; return TRUE if any are found. * parameters; generate an error message if any are found.
* *
* In most contexts, ungrouped variables will be detected by the parser (see * In most contexts, ungrouped variables will be detected by the parser (see
* parse_agg.c, exprIsAggOrGroupCol()). But that routine currently does not * parse_agg.c, check_ungrouped_columns()). But that routine currently does
* check subplans, because the necessary info is not computed until the * not check subplans, because the necessary info is not computed until the
* planner runs. So we do it here, after we have processed the subplan. * planner runs. So we do it here, after we have processed the subplan.
* This ought to be cleaned up someday. * This ought to be cleaned up someday.
* *
* 'clause' is the expression tree to be searched for subplans. * 'clause' is the expression tree to be searched for subplans.
* 'groupClause' is the GROUP BY list (a list of GroupClause nodes). * 'query' provides the GROUP BY list and range table.
* 'targetList' is the target list that the group clauses refer to. * 'targetList' is the target list that the group clauses refer to.
* (Is it really necessary to pass the tlist separately? Couldn't we
* just use the tlist found in the query node?)
*/ */
bool void
check_subplans_for_ungrouped_vars(Node *clause, check_subplans_for_ungrouped_vars(Node *clause,
List *groupClause, Query *query,
List *targetList) List *targetList)
{ {
check_subplans_for_ungrouped_vars_context context; check_subplans_for_ungrouped_vars_context context;
context.groupClause = groupClause; context.query = query;
context.targetList = targetList; context.targetList = targetList;
return check_subplans_for_ungrouped_vars_walker(clause, &context); check_subplans_for_ungrouped_vars_walker(clause, &context);
} }
static bool static bool
...@@ -472,10 +475,27 @@ check_subplans_for_ungrouped_vars_walker(Node *node, ...@@ -472,10 +475,27 @@ check_subplans_for_ungrouped_vars_walker(Node *node,
foreach(t, ((Expr *) node)->args) foreach(t, ((Expr *) node)->args)
{ {
Node *thisarg = lfirst(t); Node *thisarg = lfirst(t);
bool contained_in_group_clause = false; Var *var;
bool contained_in_group_clause;
List *gl; List *gl;
foreach(gl, context->groupClause) /*
* We do not care about args that are not local variables;
* params or outer-level vars are not our responsibility to
* check. (The outer-level query passing them to us needs
* to worry, instead.)
*/
if (! IsA(thisarg, Var))
continue;
var = (Var *) thisarg;
if (var->varlevelsup > 0)
continue;
/*
* Else, see if it is a grouping column.
*/
contained_in_group_clause = false;
foreach(gl, context->query->groupClause)
{ {
GroupClause *gcl = lfirst(gl); GroupClause *gcl = lfirst(gl);
Node *groupexpr; Node *groupexpr;
...@@ -490,7 +510,21 @@ check_subplans_for_ungrouped_vars_walker(Node *node, ...@@ -490,7 +510,21 @@ check_subplans_for_ungrouped_vars_walker(Node *node,
} }
if (!contained_in_group_clause) if (!contained_in_group_clause)
return true; /* found an ungrouped argument */ {
/* Found an ungrouped argument. Complain. */
RangeTblEntry *rte;
char *attname;
Assert(var->varno > 0 &&
var->varno <= length(context->query->rtable));
rte = rt_fetch(var->varno, context->query->rtable);
attname = get_attname(rte->relid, var->varattno);
if (! attname)
elog(ERROR, "cache lookup of attribute %d in relation %u failed",
var->varattno, rte->relid);
elog(ERROR, "Sub-SELECT uses un-GROUPed attribute %s.%s from outer query",
rte->refname, attname);
}
} }
} }
return expression_tree_walker(node, return expression_tree_walker(node,
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/parser/parse_agg.c,v 1.29 1999/10/07 04:23:12 tgl Exp $ * $Header: /cvsroot/pgsql/src/backend/parser/parse_agg.c,v 1.30 1999/12/09 05:58:54 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -19,13 +19,21 @@ ...@@ -19,13 +19,21 @@
#include "parser/parse_agg.h" #include "parser/parse_agg.h"
#include "parser/parse_coerce.h" #include "parser/parse_coerce.h"
#include "parser/parse_expr.h" #include "parser/parse_expr.h"
#include "parser/parsetree.h"
#include "utils/lsyscache.h" #include "utils/lsyscache.h"
#include "utils/syscache.h" #include "utils/syscache.h"
typedef struct {
ParseState *pstate;
List *groupClauses;
} check_ungrouped_columns_context;
static bool contain_agg_clause(Node *clause); static bool contain_agg_clause(Node *clause);
static bool contain_agg_clause_walker(Node *node, void *context); static bool contain_agg_clause_walker(Node *node, void *context);
static bool exprIsAggOrGroupCol(Node *expr, List *groupClauses); static void check_ungrouped_columns(Node *node, ParseState *pstate,
static bool exprIsAggOrGroupCol_walker(Node *node, List *groupClauses); List *groupClauses);
static bool check_ungrouped_columns_walker(Node *node,
check_ungrouped_columns_context *context);
/* /*
* contain_agg_clause * contain_agg_clause
...@@ -53,9 +61,11 @@ contain_agg_clause_walker(Node *node, void *context) ...@@ -53,9 +61,11 @@ contain_agg_clause_walker(Node *node, void *context)
} }
/* /*
* exprIsAggOrGroupCol - * check_ungrouped_columns -
* returns true if the expression does not contain non-group columns, * Scan the given expression tree for ungrouped variables (variables
* other than within the arguments of aggregate functions. * that are not listed in the groupClauses list and are not within
* the arguments of aggregate functions). Emit a suitable error message
* if any are found.
* *
* NOTE: we assume that the given clause has been transformed suitably for * NOTE: we assume that the given clause has been transformed suitably for
* parser output. This means we can use the planner's expression_tree_walker. * parser output. This means we can use the planner's expression_tree_walker.
...@@ -68,50 +78,70 @@ contain_agg_clause_walker(Node *node, void *context) ...@@ -68,50 +78,70 @@ contain_agg_clause_walker(Node *node, void *context)
* inside the subquery and converted them into a list of parameters for the * inside the subquery and converted them into a list of parameters for the
* subquery. * subquery.
*/ */
static bool static void
exprIsAggOrGroupCol(Node *expr, List *groupClauses) check_ungrouped_columns(Node *node, ParseState *pstate,
List *groupClauses)
{ {
/* My walker returns TRUE if it finds a subexpression that is NOT check_ungrouped_columns_context context;
* acceptable (since we can abort the recursion at that point).
* So, invert its result. context.pstate = pstate;
*/ context.groupClauses = groupClauses;
return ! exprIsAggOrGroupCol_walker(expr, groupClauses); check_ungrouped_columns_walker(node, &context);
} }
static bool static bool
exprIsAggOrGroupCol_walker(Node *node, List *groupClauses) check_ungrouped_columns_walker(Node *node,
check_ungrouped_columns_context *context)
{ {
List *gl; List *gl;
if (node == NULL) if (node == NULL)
return false; return false;
if (IsA(node, Aggref))
return false; /* OK; do not examine argument of aggregate */
if (IsA(node, Const) || IsA(node, Param)) if (IsA(node, Const) || IsA(node, Param))
return false; /* constants are always acceptable */ return false; /* constants are always acceptable */
/* Now check to see if expression as a whole matches any GROUP BY item. /*
* If we find an aggregate function, do not recurse into its arguments.
*/
if (IsA(node, Aggref))
return false;
/*
* Check to see if subexpression as a whole matches any GROUP BY item.
* We need to do this at every recursion level so that we recognize * We need to do this at every recursion level so that we recognize
* GROUPed-BY expressions. * GROUPed-BY expressions before reaching variables within them.
*/ */
foreach(gl, groupClauses) foreach(gl, context->groupClauses)
{ {
if (equal(node, lfirst(gl))) if (equal(node, lfirst(gl)))
return false; /* acceptable, do not descend more */ return false; /* acceptable, do not descend more */
} }
/* If we have an ungrouped Var, we have a failure --- unless it is an /*
* If we have an ungrouped Var, we have a failure --- unless it is an
* outer-level Var. In that case it's a constant as far as this query * outer-level Var. In that case it's a constant as far as this query
* level is concerned, and we can accept it. (If it's ungrouped as far * level is concerned, and we can accept it. (If it's ungrouped as far
* as the upper query is concerned, that's someone else's problem...) * as the upper query is concerned, that's someone else's problem...)
*/ */
if (IsA(node, Var)) if (IsA(node, Var))
{ {
if (((Var *) node)->varlevelsup == 0) Var *var = (Var *) node;
return true; /* found an ungrouped local variable */ RangeTblEntry *rte;
return false; /* outer-level Var is acceptable */ char *attname;
if (var->varlevelsup > 0)
return false; /* outer-level Var is acceptable */
/* Found an ungrouped local variable; generate error message */
Assert(var->varno > 0 &&
var->varno <= length(context->pstate->p_rtable));
rte = rt_fetch(var->varno, context->pstate->p_rtable);
attname = get_attname(rte->relid, var->varattno);
if (! attname)
elog(ERROR, "cache lookup of attribute %d in relation %u failed",
var->varattno, rte->relid);
elog(ERROR, "Attribute %s.%s must be GROUPed or used in an aggregate function",
rte->refname, attname);
} }
/* Otherwise, recurse. */ /* Otherwise, recurse. */
return expression_tree_walker(node, exprIsAggOrGroupCol_walker, return expression_tree_walker(node, check_ungrouped_columns_walker,
(void *) groupClauses); (void *) context);
} }
/* /*
...@@ -135,9 +165,9 @@ parseCheckAggregates(ParseState *pstate, Query *qry) ...@@ -135,9 +165,9 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
/* /*
* Aggregates must never appear in WHERE clauses. (Note this check * Aggregates must never appear in WHERE clauses. (Note this check
* should appear first to deliver an appropriate error message; * should appear first to deliver an appropriate error message;
* otherwise we are likely to generate the generic "illegal use of * otherwise we are likely to complain about some innocent variable
* aggregates in target list" message, which is outright misleading if * in the target list, which is outright misleading if the problem
* the problem is in WHERE.) * is in WHERE.)
*/ */
if (contain_agg_clause(qry->qual)) if (contain_agg_clause(qry->qual))
elog(ERROR, "Aggregates not allowed in WHERE clause"); elog(ERROR, "Aggregates not allowed in WHERE clause");
...@@ -146,8 +176,8 @@ parseCheckAggregates(ParseState *pstate, Query *qry) ...@@ -146,8 +176,8 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
* No aggregates allowed in GROUP BY clauses, either. * No aggregates allowed in GROUP BY clauses, either.
* *
* While we are at it, build a list of the acceptable GROUP BY expressions * While we are at it, build a list of the acceptable GROUP BY expressions
* for use by exprIsAggOrGroupCol() (this avoids repeated scans of the * for use by check_ungrouped_columns() (this avoids repeated scans of the
* targetlist within the recursive routines...) * targetlist within the recursive routine...)
*/ */
foreach(tl, qry->groupClause) foreach(tl, qry->groupClause)
{ {
...@@ -161,26 +191,10 @@ parseCheckAggregates(ParseState *pstate, Query *qry) ...@@ -161,26 +191,10 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
} }
/* /*
* The expression specified in the HAVING clause can only contain * Check the targetlist and HAVING clause for ungrouped variables.
* aggregates, group columns and functions thereof. As with WHERE,
* we want to point the finger at HAVING before the target list.
*/ */
if (!exprIsAggOrGroupCol(qry->havingQual, groupClauses)) check_ungrouped_columns((Node *) qry->targetList, pstate, groupClauses);
elog(ERROR, check_ungrouped_columns((Node *) qry->havingQual, pstate, groupClauses);
"Illegal use of aggregates or non-group column in HAVING clause");
/*
* The target list can only contain aggregates, group columns and
* functions thereof.
*/
foreach(tl, qry->targetList)
{
TargetEntry *tle = lfirst(tl);
if (!exprIsAggOrGroupCol(tle->expr, groupClauses))
elog(ERROR,
"Illegal use of aggregates or non-group column in target list");
}
/* Release the list storage (but not the pointed-to expressions!) */ /* Release the list storage (but not the pointed-to expressions!) */
freeList(groupClauses); freeList(groupClauses);
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
* *
* Copyright (c) 1994, Regents of the University of California * Copyright (c) 1994, Regents of the University of California
* *
* $Id: clauses.h,v 1.30 1999/09/26 02:28:44 tgl Exp $ * $Id: clauses.h,v 1.31 1999/12/09 05:58:55 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -39,8 +39,8 @@ extern List *make_ands_implicit(Expr *clause); ...@@ -39,8 +39,8 @@ extern List *make_ands_implicit(Expr *clause);
extern List *pull_constant_clauses(List *quals, List **constantQual); extern List *pull_constant_clauses(List *quals, List **constantQual);
extern List *pull_agg_clause(Node *clause); extern List *pull_agg_clause(Node *clause);
extern bool check_subplans_for_ungrouped_vars(Node *clause, extern void check_subplans_for_ungrouped_vars(Node *clause,
List *groupClause, Query *query,
List *targetList); List *targetList);
extern void clause_get_relids_vars(Node *clause, Relids *relids, List **vars); extern void clause_get_relids_vars(Node *clause, Relids *relids, List **vars);
......
...@@ -32,7 +32,7 @@ count ...@@ -32,7 +32,7 @@ count
(6 rows) (6 rows)
QUERY: SELECT count(*) FROM test_missing_target GROUP BY a ORDER BY b; QUERY: SELECT count(*) FROM test_missing_target GROUP BY a ORDER BY b;
ERROR: Illegal use of aggregates or non-group column in target list ERROR: Attribute test_missing_target.b must be GROUPed or used in an aggregate function
QUERY: SELECT count(*) FROM test_missing_target GROUP BY b ORDER BY b; QUERY: SELECT count(*) FROM test_missing_target GROUP BY b ORDER BY b;
count count
----- -----
...@@ -194,7 +194,7 @@ count ...@@ -194,7 +194,7 @@ count
(4 rows) (4 rows)
QUERY: SELECT count(a) FROM test_missing_target GROUP BY a ORDER BY b; QUERY: SELECT count(a) FROM test_missing_target GROUP BY a ORDER BY b;
ERROR: Illegal use of aggregates or non-group column in target list ERROR: Attribute test_missing_target.b must be GROUPed or used in an aggregate function
QUERY: SELECT count(b) FROM test_missing_target GROUP BY b/2 ORDER BY b/2; QUERY: SELECT count(b) FROM test_missing_target GROUP BY b/2 ORDER BY b/2;
count count
----- -----
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment