7
7
*
8
8
*
9
9
* 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 $
11
11
*
12
12
*-------------------------------------------------------------------------
13
13
*/
19
19
#include "parser/parse_agg.h"
20
20
#include "parser/parse_coerce.h"
21
21
#include "parser/parse_expr.h"
22
+ #include "parser/parsetree.h"
22
23
#include "utils/lsyscache.h"
23
24
#include "utils/syscache.h"
24
25
26
+ typedef struct {
27
+ ParseState * pstate ;
28
+ List * groupClauses ;
29
+ } check_ungrouped_columns_context ;
30
+
25
31
static bool contain_agg_clause (Node * clause );
26
32
static 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 );
29
37
30
38
/*
31
39
* contain_agg_clause
@@ -53,9 +61,11 @@ contain_agg_clause_walker(Node *node, void *context)
53
61
}
54
62
55
63
/*
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.
59
69
*
60
70
* NOTE: we assume that the given clause has been transformed suitably for
61
71
* 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
78
* inside the subquery and converted them into a list of parameters for the
69
79
* subquery.
70
80
*/
71
- static bool
72
- exprIsAggOrGroupCol (Node * expr , List * groupClauses )
81
+ static void
82
+ check_ungrouped_columns (Node * node , ParseState * pstate ,
83
+ List * groupClauses )
73
84
{
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 );
79
90
}
80
91
81
92
static bool
82
- exprIsAggOrGroupCol_walker (Node * node , List * groupClauses )
93
+ check_ungrouped_columns_walker (Node * node ,
94
+ check_ungrouped_columns_context * context )
83
95
{
84
96
List * gl ;
85
97
86
98
if (node == NULL )
87
99
return false;
88
- if (IsA (node , Aggref ))
89
- return false; /* OK; do not examine argument of aggregate */
90
100
if (IsA (node , Const ) || IsA (node , Param ))
91
101
return 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.
93
109
* 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 .
95
111
*/
96
- foreach (gl , groupClauses )
112
+ foreach (gl , context -> groupClauses )
97
113
{
98
114
if (equal (node , lfirst (gl )))
99
115
return false; /* acceptable, do not descend more */
100
116
}
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
102
119
* outer-level Var. In that case it's a constant as far as this query
103
120
* level is concerned, and we can accept it. (If it's ungrouped as far
104
121
* as the upper query is concerned, that's someone else's problem...)
105
122
*/
106
123
if (IsA (node , Var ))
107
124
{
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 );
111
141
}
112
142
/* 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 );
115
145
}
116
146
117
147
/*
@@ -135,9 +165,9 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
135
165
/*
136
166
* Aggregates must never appear in WHERE clauses. (Note this check
137
167
* should appear first to deliver an appropriate error message;
138
- * otherwise we are likely to generate 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 to complain about some innocent variable
169
+ * in the target list, which is outright misleading if the problem
170
+ * is in WHERE.)
141
171
*/
142
172
if (contain_agg_clause (qry -> qual ))
143
173
elog (ERROR , "Aggregates not allowed in WHERE clause" );
@@ -146,8 +176,8 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
146
176
* No aggregates allowed in GROUP BY clauses, either.
147
177
*
148
178
* While we are at it, build a list of the acceptable GROUP BY expressions
149
- * for use by exprIsAggOrGroupCol () (this avoids repeated scans of the
150
- * targetlist within the recursive routines ...)
179
+ * for use by check_ungrouped_columns () (this avoids repeated scans of the
180
+ * targetlist within the recursive routine ...)
151
181
*/
152
182
foreach (tl , qry -> groupClause )
153
183
{
@@ -161,26 +191,10 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
161
191
}
162
192
163
193
/*
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.
167
195
*/
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 );
184
198
185
199
/* Release the list storage (but not the pointed-to expressions!) */
186
200
freeList (groupClauses );
0 commit comments