77 *
88 *
99 * IDENTIFICATION
10- * $Header: /cvsroot/pgsql/src/backend/parser/parse_agg.c,v 1.29 1999/10/07 04:23:12 tgl Exp $
10+ * $Header: /cvsroot/pgsql/src/backend/parser/parse_agg.c,v 1.30 1999/12/09 05:58:54 tgl Exp $
1111 *
1212 *-------------------------------------------------------------------------
1313 */
1919#include "parser/parse_agg.h"
2020#include "parser/parse_coerce.h"
2121#include "parser/parse_expr.h"
22+ #include "parser/parsetree.h"
2223#include "utils/lsyscache.h"
2324#include "utils/syscache.h"
2425
26+ typedef struct {
27+ ParseState * pstate ;
28+ List * groupClauses ;
29+ }check_ungrouped_columns_context ;
30+
2531static bool contain_agg_clause (Node * clause );
2632static bool contain_agg_clause_walker (Node * node ,void * context );
27- static bool exprIsAggOrGroupCol (Node * expr ,List * groupClauses );
28- static bool exprIsAggOrGroupCol_walker (Node * node ,List * groupClauses );
33+ static void check_ungrouped_columns (Node * node ,ParseState * pstate ,
34+ List * groupClauses );
35+ static bool check_ungrouped_columns_walker (Node * node ,
36+ check_ungrouped_columns_context * context );
2937
3038/*
3139 * contain_agg_clause
@@ -53,9 +61,11 @@ contain_agg_clause_walker(Node *node, void *context)
5361}
5462
5563/*
56- * exprIsAggOrGroupCol -
57- * returns true if the expression does not contain non-group columns,
58- * other than within the arguments of aggregate functions.
64+ * check_ungrouped_columns -
65+ * Scan the given expression tree for ungrouped variables (variables
66+ * that are not listed in the groupClauses list and are not within
67+ * the arguments of aggregate functions). Emit a suitable error message
68+ * if any are found.
5969 *
6070 * NOTE: we assume that the given clause has been transformed suitably for
6171 * 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)
6878 * inside the subquery and converted them into a list of parameters for the
6979 * subquery.
7080 */
71- static bool
72- exprIsAggOrGroupCol (Node * expr ,List * groupClauses )
81+ static void
82+ check_ungrouped_columns (Node * node ,ParseState * pstate ,
83+ List * groupClauses )
7384{
74- /* My walker returns TRUE if it finds a subexpression that is NOT
75- * acceptable (since we can abort the recursion at that point).
76- * So, invert its result.
77- */
78- return ! exprIsAggOrGroupCol_walker ( expr , groupClauses );
85+ check_ungrouped_columns_context context ;
86+
87+ context . pstate = pstate ;
88+ context . groupClauses = groupClauses ;
89+ check_ungrouped_columns_walker ( node , & context );
7990}
8091
8192static bool
82- exprIsAggOrGroupCol_walker (Node * node ,List * groupClauses )
93+ check_ungrouped_columns_walker (Node * node ,
94+ check_ungrouped_columns_context * context )
8395{
8496List * gl ;
8597
8698if (node == NULL )
8799return false;
88- if (IsA (node ,Aggref ))
89- return false;/* OK; do not examine argument of aggregate */
90100if (IsA (node ,Const )|| IsA (node ,Param ))
91101return false;/* constants are always acceptable */
92- /* Now check to see if expression as a whole matches any GROUP BY item.
102+ /*
103+ * If we find an aggregate function, do not recurse into its arguments.
104+ */
105+ if (IsA (node ,Aggref ))
106+ return false;
107+ /*
108+ * Check to see if subexpression as a whole matches any GROUP BY item.
93109 * We need to do this at every recursion level so that we recognize
94- * GROUPed-BY expressions.
110+ * GROUPed-BY expressions before reaching variables within them .
95111 */
96- foreach (gl ,groupClauses )
112+ foreach (gl ,context -> groupClauses )
97113{
98114if (equal (node ,lfirst (gl )))
99115return false;/* acceptable, do not descend more */
100116}
101- /* If we have an ungrouped Var, we have a failure --- unless it is an
117+ /*
118+ * If we have an ungrouped Var, we have a failure --- unless it is an
102119 * outer-level Var. In that case it's a constant as far as this query
103120 * level is concerned, and we can accept it. (If it's ungrouped as far
104121 * as the upper query is concerned, that's someone else's problem...)
105122 */
106123if (IsA (node ,Var ))
107124{
108- if (((Var * )node )-> varlevelsup == 0 )
109- return true;/* found an ungrouped local variable */
110- return false;/* outer-level Var is acceptable */
125+ Var * var = (Var * )node ;
126+ RangeTblEntry * rte ;
127+ char * attname ;
128+
129+ if (var -> varlevelsup > 0 )
130+ return false;/* outer-level Var is acceptable */
131+ /* Found an ungrouped local variable; generate error message */
132+ Assert (var -> varno > 0 &&
133+ var -> varno <=length (context -> pstate -> p_rtable ));
134+ rte = rt_fetch (var -> varno ,context -> pstate -> p_rtable );
135+ attname = get_attname (rte -> relid ,var -> varattno );
136+ if (!attname )
137+ elog (ERROR ,"cache lookup of attribute %d in relation %u failed" ,
138+ var -> varattno ,rte -> relid );
139+ elog (ERROR ,"Attribute %s.%s must be GROUPed or used in an aggregate function" ,
140+ rte -> refname ,attname );
111141}
112142/* Otherwise, recurse. */
113- return expression_tree_walker (node ,exprIsAggOrGroupCol_walker ,
114- (void * )groupClauses );
143+ return expression_tree_walker (node ,check_ungrouped_columns_walker ,
144+ (void * )context );
115145}
116146
117147/*
@@ -135,9 +165,9 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
135165/*
136166 * Aggregates must never appear in WHERE clauses. (Note this check
137167 * should appear first to deliver an appropriate error message;
138- * otherwise we are likely togenerate the generic "illegal use of
139- *aggregates in target list" message , which is outright misleading if
140- *the problem is in WHERE.)
168+ * otherwise we are likely tocomplain about some innocent variable
169+ * inthe target list, which is outright misleading if the problem
170+ * is in WHERE.)
141171 */
142172if (contain_agg_clause (qry -> qual ))
143173elog (ERROR ,"Aggregates not allowed in WHERE clause" );
@@ -146,8 +176,8 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
146176 * No aggregates allowed in GROUP BY clauses, either.
147177 *
148178 * While we are at it, build a list of the acceptable GROUP BY expressions
149- * for use byexprIsAggOrGroupCol () (this avoids repeated scans of the
150- * targetlist within the recursiveroutines ...)
179+ * for use bycheck_ungrouped_columns () (this avoids repeated scans of the
180+ * targetlist within the recursiveroutine ...)
151181 */
152182foreach (tl ,qry -> groupClause )
153183{
@@ -161,26 +191,10 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
161191}
162192
163193/*
164- * The expression specified in the HAVING clause can only contain
165- * aggregates, group columns and functions thereof. As with WHERE,
166- * we want to point the finger at HAVING before the target list.
194+ * Check the targetlist and HAVING clause for ungrouped variables.
167195 */
168- if (!exprIsAggOrGroupCol (qry -> havingQual ,groupClauses ))
169- elog (ERROR ,
170- "Illegal use of aggregates or non-group column in HAVING clause" );
171-
172- /*
173- * The target list can only contain aggregates, group columns and
174- * functions thereof.
175- */
176- foreach (tl ,qry -> targetList )
177- {
178- TargetEntry * tle = lfirst (tl );
179-
180- if (!exprIsAggOrGroupCol (tle -> expr ,groupClauses ))
181- elog (ERROR ,
182- "Illegal use of aggregates or non-group column in target list" );
183- }
196+ check_ungrouped_columns ((Node * )qry -> targetList ,pstate ,groupClauses );
197+ check_ungrouped_columns ((Node * )qry -> havingQual ,pstate ,groupClauses );
184198
185199/* Release the list storage (but not the pointed-to expressions!) */
186200freeList (groupClauses );