Department of Redundancy Department: makeNode(FuncCall) division
Folks,
Per suggestions and lots of help from Andrew Gierth, please find
attached a patch to clean up the call sites for FuncCall nodes, which
I'd like to expand centrally rather than in each of the 37 (or 38, but
I only redid 37) places where it's called. The remaining one is in
src/backend/nodes/copyfuncs.c, which has to be modified for any
changes in the that struct anyhow.
The immediate purpose is two-fold: to reduce some redundancies, which
I believe is worth doing in and of itself, and to prepare for adding
FILTER on aggregates from the spec, and possibly other things in
the <aggregate function> part of the spec.
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
Attachments:
makeFuncArgs_001.patchtext/plain; charset=us-asciiDownload
*** a/src/backend/nodes/makefuncs.c
--- b/src/backend/nodes/makefuncs.c
***************
*** 508,510 **** makeDefElemExtended(char *nameSpace, char *name, Node *arg,
--- 508,530 ----
return res;
}
+
+ /*
+ * makeFuncCall -
+ * set up to call a function. DRY!
+ */
+ FuncCall *
+ makeFuncCall(List *name, List *args, int location)
+ {
+ FuncCall *n = makeNode(FuncCall);
+ n->funcname = name;
+ n->args = args;
+ n->location = location;
+ n->agg_order = NIL;
+ n->agg_star = FALSE;
+ n->agg_distinct = FALSE;
+ n->func_variadic = FALSE;
+ n->over = NULL;
+ return n;
+ }
+
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 10344,10359 **** a_expr: c_expr { $$ = $1; }
}
| a_expr AT TIME ZONE a_expr %prec AT
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("timezone");
! n->args = list_make2($5, $1);
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @2;
! $$ = (Node *) n;
}
/*
* These operators must be called out explicitly in order to make use
--- 10344,10352 ----
}
| a_expr AT TIME ZONE a_expr %prec AT
{
! $$ = (Node *) makeFuncCall(SystemFuncName("timezone"),
! list_make2($5, $1),
! @2);
}
/*
* These operators must be called out explicitly in order to make use
***************
*** 10405,10517 **** a_expr: c_expr { $$ = $1; }
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~", $1, $3, @2); }
| a_expr LIKE a_expr ESCAPE a_expr
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("like_escape");
! n->args = list_make2($3, $5);
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @2;
! $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~", $1, (Node *) n, @2);
}
| a_expr NOT LIKE a_expr
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~", $1, $4, @2); }
| a_expr NOT LIKE a_expr ESCAPE a_expr
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("like_escape");
! n->args = list_make2($4, $6);
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @2;
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~", $1, (Node *) n, @2);
}
| a_expr ILIKE a_expr
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~*", $1, $3, @2); }
| a_expr ILIKE a_expr ESCAPE a_expr
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("like_escape");
! n->args = list_make2($3, $5);
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @2;
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~*", $1, (Node *) n, @2);
}
| a_expr NOT ILIKE a_expr
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~*", $1, $4, @2); }
| a_expr NOT ILIKE a_expr ESCAPE a_expr
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("like_escape");
! n->args = list_make2($4, $6);
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @2;
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~*", $1, (Node *) n, @2);
}
| a_expr SIMILAR TO a_expr %prec SIMILAR
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("similar_escape");
! n->args = list_make2($4, makeNullAConst(-1));
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @2;
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~", $1, (Node *) n, @2);
}
| a_expr SIMILAR TO a_expr ESCAPE a_expr
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("similar_escape");
! n->args = list_make2($4, $6);
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @2;
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~", $1, (Node *) n, @2);
}
| a_expr NOT SIMILAR TO a_expr %prec SIMILAR
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("similar_escape");
! n->args = list_make2($5, makeNullAConst(-1));
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @2;
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~", $1, (Node *) n, @2);
}
| a_expr NOT SIMILAR TO a_expr ESCAPE a_expr
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("similar_escape");
! n->args = list_make2($5, $7);
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @2;
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~", $1, (Node *) n, @2);
}
--- 10398,10463 ----
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~", $1, $3, @2); }
| a_expr LIKE a_expr ESCAPE a_expr
{
! FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
! list_make2($3, $5),
! @2);
! $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~", $1, (Node *) n, @2);
!
}
| a_expr NOT LIKE a_expr
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~", $1, $4, @2); }
| a_expr NOT LIKE a_expr ESCAPE a_expr
{
! FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
! list_make2($4, $6),
! @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~", $1, (Node *) n, @2);
}
| a_expr ILIKE a_expr
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~*", $1, $3, @2); }
| a_expr ILIKE a_expr ESCAPE a_expr
{
! FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
! list_make2($3, $5),
! @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~*", $1, (Node *) n, @2);
}
| a_expr NOT ILIKE a_expr
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~*", $1, $4, @2); }
| a_expr NOT ILIKE a_expr ESCAPE a_expr
{
! FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
! list_make2($4, $6),
! @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~*", $1, (Node *) n, @2);
}
| a_expr SIMILAR TO a_expr %prec SIMILAR
{
! FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
! list_make2($4, makeNullAConst(-1)),
! @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~", $1, (Node *) n, @2);
}
| a_expr SIMILAR TO a_expr ESCAPE a_expr
{
! FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
! list_make2($4, $6),
! @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~", $1, (Node *) n, @2);
}
| a_expr NOT SIMILAR TO a_expr %prec SIMILAR
{
! FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
! list_make2($5, makeNullAConst(-1)),
! @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~", $1, (Node *) n, @2);
}
| a_expr NOT SIMILAR TO a_expr ESCAPE a_expr
{
! FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
! list_make2($5, $7),
! @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~", $1, (Node *) n, @2);
}
***************
*** 10946,11042 **** c_expr: columnref { $$ = $1; }
*/
func_expr: func_name '(' ')' over_clause
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = $1;
! n->args = NIL;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
n->over = $4;
- n->location = @1;
$$ = (Node *)n;
}
| func_name '(' func_arg_list ')' over_clause
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = $1;
! n->args = $3;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
n->over = $5;
- n->location = @1;
$$ = (Node *)n;
}
| func_name '(' VARIADIC func_arg_expr ')' over_clause
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = $1;
! n->args = list_make1($4);
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
n->func_variadic = TRUE;
n->over = $6;
- n->location = @1;
$$ = (Node *)n;
}
| func_name '(' func_arg_list ',' VARIADIC func_arg_expr ')' over_clause
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = $1;
! n->args = lappend($3, $6);
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
n->func_variadic = TRUE;
n->over = $8;
- n->location = @1;
$$ = (Node *)n;
}
| func_name '(' func_arg_list sort_clause ')' over_clause
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = $1;
! n->args = $3;
n->agg_order = $4;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
n->over = $6;
- n->location = @1;
$$ = (Node *)n;
}
| func_name '(' ALL func_arg_list opt_sort_clause ')' over_clause
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = $1;
! n->args = $4;
n->agg_order = $5;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
/* Ideally we'd mark the FuncCall node to indicate
* "must be an aggregate", but there's no provision
* for that in FuncCall at the moment.
*/
- n->func_variadic = FALSE;
n->over = $7;
- n->location = @1;
$$ = (Node *)n;
}
| func_name '(' DISTINCT func_arg_list opt_sort_clause ')' over_clause
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = $1;
! n->args = $4;
n->agg_order = $5;
- n->agg_star = FALSE;
n->agg_distinct = TRUE;
- n->func_variadic = FALSE;
n->over = $7;
- n->location = @1;
$$ = (Node *)n;
}
| func_name '(' '*' ')' over_clause
--- 10892,10945 ----
*/
func_expr: func_name '(' ')' over_clause
{
! FuncCall *n = makeFuncCall($1, NIL, @1);
n->over = $4;
$$ = (Node *)n;
}
| func_name '(' func_arg_list ')' over_clause
{
! FuncCall *n = makeFuncCall($1, $3, @1);
n->over = $5;
$$ = (Node *)n;
}
| func_name '(' VARIADIC func_arg_expr ')' over_clause
{
! FuncCall *n = makeFuncCall($1, list_make1($4), @1);
n->func_variadic = TRUE;
n->over = $6;
$$ = (Node *)n;
}
| func_name '(' func_arg_list ',' VARIADIC func_arg_expr ')' over_clause
{
! FuncCall *n = makeFuncCall($1, lappend($3, $6), @1);
n->func_variadic = TRUE;
n->over = $8;
$$ = (Node *)n;
}
| func_name '(' func_arg_list sort_clause ')' over_clause
{
! FuncCall *n = makeFuncCall($1, $3, @1);
n->agg_order = $4;
n->over = $6;
$$ = (Node *)n;
}
| func_name '(' ALL func_arg_list opt_sort_clause ')' over_clause
{
! FuncCall *n = makeFuncCall($1, $4, @1);
n->agg_order = $5;
/* Ideally we'd mark the FuncCall node to indicate
* "must be an aggregate", but there's no provision
* for that in FuncCall at the moment.
*/
n->over = $7;
$$ = (Node *)n;
}
| func_name '(' DISTINCT func_arg_list opt_sort_clause ')' over_clause
{
! FuncCall *n = makeFuncCall($1, $4, @1);
n->agg_order = $5;
n->agg_distinct = TRUE;
n->over = $7;
$$ = (Node *)n;
}
| func_name '(' '*' ')' over_clause
***************
*** 11051,11079 **** func_expr: func_name '(' ')' over_clause
* so that later processing can detect what the argument
* really was.
*/
! FuncCall *n = makeNode(FuncCall);
! n->funcname = $1;
! n->args = NIL;
! n->agg_order = NIL;
n->agg_star = TRUE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
n->over = $5;
- n->location = @1;
$$ = (Node *)n;
}
| COLLATION FOR '(' a_expr ')'
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("pg_collation_for");
! n->args = list_make1($4);
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| CURRENT_DATE
{
--- 10954,10969 ----
* so that later processing can detect what the argument
* really was.
*/
! FuncCall *n = makeFuncCall($1, NIL, @1);
n->agg_star = TRUE;
n->over = $5;
$$ = (Node *)n;
}
| COLLATION FOR '(' a_expr ')'
{
! $$ = (Node *) makeFuncCall(SystemFuncName("pg_collation_for"),
! list_make1($4),
! @1);
}
| CURRENT_DATE
{
***************
*** 11125,11140 **** func_expr: func_name '(' ')' over_clause
* Translate as "now()", since we have a function that
* does exactly what is needed.
*/
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("now");
! n->args = NIL;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| CURRENT_TIMESTAMP '(' Iconst ')'
{
--- 11015,11021 ----
* Translate as "now()", since we have a function that
* does exactly what is needed.
*/
! $$ = (Node *) makeFuncCall(SystemFuncName("now"), NIL, @1);
}
| CURRENT_TIMESTAMP '(' Iconst ')'
{
***************
*** 11197,11292 **** func_expr: func_name '(' ')' over_clause
}
| CURRENT_ROLE
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("current_user");
! n->args = NIL;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| CURRENT_USER
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("current_user");
! n->args = NIL;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| SESSION_USER
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("session_user");
! n->args = NIL;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| USER
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("current_user");
! n->args = NIL;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| CURRENT_CATALOG
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("current_database");
! n->args = NIL;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| CURRENT_SCHEMA
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("current_schema");
! n->args = NIL;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| CAST '(' a_expr AS Typename ')'
{ $$ = makeTypeCast($3, $5, @1); }
| EXTRACT '(' extract_list ')'
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("date_part");
! n->args = $3;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| OVERLAY '(' overlay_list ')'
{
--- 11078,11110 ----
}
| CURRENT_ROLE
{
! $$ = (Node *) makeFuncCall(SystemFuncName("current_user"), NIL, @1);
}
| CURRENT_USER
{
! $$ = (Node *) makeFuncCall(SystemFuncName("current_user"), NIL, @1);
}
| SESSION_USER
{
! $$ = (Node *) makeFuncCall(SystemFuncName("session_user"), NIL, @1);
}
| USER
{
! $$ = (Node *) makeFuncCall(SystemFuncName("current_user"), NIL, @1);
}
| CURRENT_CATALOG
{
! $$ = (Node *) makeFuncCall(SystemFuncName("current_database"), NIL, @1);
}
| CURRENT_SCHEMA
{
! $$ = (Node *) makeFuncCall(SystemFuncName("current_schema"), NIL, @1);
}
| CAST '(' a_expr AS Typename ')'
{ $$ = makeTypeCast($3, $5, @1); }
| EXTRACT '(' extract_list ')'
{
! $$ = (Node *) makeFuncCall(SystemFuncName("date_part"), $3, @1);
}
| OVERLAY '(' overlay_list ')'
{
***************
*** 11295,11340 **** func_expr: func_name '(' ')' over_clause
* overlay(A PLACING B FROM C) is converted to
* overlay(A, B, C)
*/
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("overlay");
! n->args = $3;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| POSITION '(' position_list ')'
{
/* position(A in B) is converted to position(B, A) */
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("position");
! n->args = $3;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| SUBSTRING '(' substr_list ')'
{
/* substring(A from B for C) is converted to
* substring(A, B, C) - thomas 2000-11-28
*/
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("substring");
! n->args = $3;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| TREAT '(' a_expr AS Typename ')'
{
--- 11113,11131 ----
* overlay(A PLACING B FROM C) is converted to
* overlay(A, B, C)
*/
! $$ = (Node *) makeFuncCall(SystemFuncName("overlay"), $3, @1);
}
| POSITION '(' position_list ')'
{
/* position(A in B) is converted to position(B, A) */
! $$ = (Node *) makeFuncCall(SystemFuncName("position"), $3, @1);
}
| SUBSTRING '(' substr_list ')'
{
/* substring(A from B for C) is converted to
* substring(A, B, C) - thomas 2000-11-28
*/
! $$ = (Node *) makeFuncCall(SystemFuncName("substring"), $3, @1);
}
| TREAT '(' a_expr AS Typename ')'
{
***************
*** 11343,11417 **** func_expr: func_name '(' ')' over_clause
* In SQL99, this is intended for use with structured UDTs,
* but let's make this a generally useful form allowing stronger
* coercions than are handled by implicit casting.
! */
! FuncCall *n = makeNode(FuncCall);
! /* Convert SystemTypeName() to SystemFuncName() even though
* at the moment they result in the same thing.
*/
! n->funcname = SystemFuncName(((Value *)llast($5->names))->val.str);
! n->args = list_make1($3);
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| TRIM '(' BOTH trim_list ')'
{
/* various trim expressions are defined in SQL92
* - thomas 1997-07-19
*/
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("btrim");
! n->args = $4;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| TRIM '(' LEADING trim_list ')'
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("ltrim");
! n->args = $4;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| TRIM '(' TRAILING trim_list ')'
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("rtrim");
! n->args = $4;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| TRIM '(' trim_list ')'
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("btrim");
! n->args = $3;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| NULLIF '(' a_expr ',' a_expr ')'
{
--- 11134,11165 ----
* In SQL99, this is intended for use with structured UDTs,
* but let's make this a generally useful form allowing stronger
* coercions than are handled by implicit casting.
! *
! * Convert SystemTypeName() to SystemFuncName() even though
* at the moment they result in the same thing.
*/
! $$ = (Node *) makeFuncCall(SystemFuncName(((Value *)llast($5->names))->val.str),
! list_make1($3),
! @1);
}
| TRIM '(' BOTH trim_list ')'
{
/* various trim expressions are defined in SQL92
* - thomas 1997-07-19
*/
! $$ = (Node *) makeFuncCall(SystemFuncName("btrim"), $4, @1);
}
| TRIM '(' LEADING trim_list ')'
{
! $$ = (Node *) makeFuncCall(SystemFuncName("ltrim"), $4, @1);
}
| TRIM '(' TRAILING trim_list ')'
{
! $$ = (Node *) makeFuncCall(SystemFuncName("rtrim"), $4, @1);
}
| TRIM '(' trim_list ')'
{
! $$ = (Node *) makeFuncCall(SystemFuncName("btrim"), $3, @1);
}
| NULLIF '(' a_expr ',' a_expr ')'
{
***************
*** 11464,11479 **** func_expr: func_name '(' ')' over_clause
{
/* xmlexists(A PASSING [BY REF] B [BY REF]) is
* converted to xmlexists(A, B)*/
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("xmlexists");
! n->args = list_make2($3, $4);
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| XMLFOREST '(' xml_attribute_list ')'
{
--- 11212,11218 ----
{
/* xmlexists(A PASSING [BY REF] B [BY REF]) is
* converted to xmlexists(A, B)*/
! $$ = (Node *) makeFuncCall(SystemFuncName("xmlexists"), list_make2($3, $4), @1);
}
| XMLFOREST '(' xml_attribute_list ')'
{
***************
*** 13118,13126 **** makeBoolAConst(bool state, int location)
static FuncCall *
makeOverlaps(List *largs, List *rargs, int location, core_yyscan_t yyscanner)
{
! FuncCall *n = makeNode(FuncCall);
!
! n->funcname = SystemFuncName("overlaps");
if (list_length(largs) == 1)
largs = lappend(largs, largs);
else if (list_length(largs) != 2)
--- 12857,12863 ----
static FuncCall *
makeOverlaps(List *largs, List *rargs, int location, core_yyscan_t yyscanner)
{
! FuncCall *n;
if (list_length(largs) == 1)
largs = lappend(largs, largs);
else if (list_length(largs) != 2)
***************
*** 13135,13147 **** makeOverlaps(List *largs, List *rargs, int location, core_yyscan_t yyscanner)
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("wrong number of parameters on right side of OVERLAPS expression"),
parser_errposition(location)));
! n->args = list_concat(largs, rargs);
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = location;
return n;
}
--- 12872,12878 ----
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("wrong number of parameters on right side of OVERLAPS expression"),
parser_errposition(location)));
! n = makeFuncCall(SystemFuncName("overlaps"), list_concat(largs, rargs), location);
return n;
}
*** a/src/backend/parser/parse_utilcmd.c
--- b/src/backend/parser/parse_utilcmd.c
***************
*** 441,456 **** transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
castnode->typeName = SystemTypeName("regclass");
castnode->arg = (Node *) snamenode;
castnode->location = -1;
! funccallnode = makeNode(FuncCall);
! funccallnode->funcname = SystemFuncName("nextval");
! funccallnode->args = list_make1(castnode);
! funccallnode->agg_order = NIL;
! funccallnode->agg_star = false;
! funccallnode->agg_distinct = false;
! funccallnode->func_variadic = false;
! funccallnode->over = NULL;
! funccallnode->location = -1;
!
constraint = makeNode(Constraint);
constraint->contype = CONSTR_DEFAULT;
constraint->location = -1;
--- 441,449 ----
castnode->typeName = SystemTypeName("regclass");
castnode->arg = (Node *) snamenode;
castnode->location = -1;
! funccallnode = makeFuncCall(SystemFuncName("nextval"),
! list_make1(castnode),
! -1);
constraint = makeNode(Constraint);
constraint->contype = CONSTR_DEFAULT;
constraint->location = -1;
*** a/src/include/nodes/makefuncs.h
--- b/src/include/nodes/makefuncs.h
***************
*** 75,80 **** extern TypeName *makeTypeNameFromOid(Oid typeOid, int32 typmod);
--- 75,82 ----
extern FuncExpr *makeFuncExpr(Oid funcid, Oid rettype, List *args,
Oid funccollid, Oid inputcollid, CoercionForm fformat);
+ extern FuncCall *makeFuncCall(List *name, List *args, int location);
+
extern DefElem *makeDefElem(char *name, Node *arg);
extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
DefElemAction defaction);
David Fetter <david@fetter.org> writes:
Per suggestions and lots of help from Andrew Gierth, please find
attached a patch to clean up the call sites for FuncCall nodes, which
I'd like to expand centrally rather than in each of the 37 (or 38, but
I only redid 37) places where it's called. The remaining one is in
src/backend/nodes/copyfuncs.c, which has to be modified for any
changes in the that struct anyhow.
TBH, I don't think this is an improvement.
The problem with adding a new field to any struct is that you have to
run around and examine (and, usually, modify) every place that
manufactures that type of struct. With a makeFuncCall defined like
this, you'd still have to do that; it would just become a lot easier
to forget to do so.
If the subroutine were defined like most other makeXXX subroutines,
ie you have to supply *all* the fields, that argument would go away,
but the notational advantage is then dubious.
The bigger-picture point is that you're proposing to make the coding
conventions for building FuncCalls different from what they are for
any other grammar node. I don't think that's a great idea; it will
mostly foster confusion.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sun, Feb 10, 2013 at 10:09:19AM -0500, Tom Lane wrote:
David Fetter <david@fetter.org> writes:
Per suggestions and lots of help from Andrew Gierth, please find
attached a patch to clean up the call sites for FuncCall nodes, which
I'd like to expand centrally rather than in each of the 37 (or 38, but
I only redid 37) places where it's called. The remaining one is in
src/backend/nodes/copyfuncs.c, which has to be modified for any
changes in the that struct anyhow.TBH, I don't think this is an improvement.
The problem with adding a new field to any struct is that you have to
run around and examine (and, usually, modify) every place that
manufactures that type of struct. With a makeFuncCall defined like
this, you'd still have to do that; it would just become a lot easier
to forget to do so.
So you're saying that I've insufficiently put the choke point there?
It seems to me that needing to go back to each and every jot and
tittle of the code to add a new default parameter in each place is a
recipe for mishaps, where in this one, the new correct defaults will
just appear in all the places they need to.
If the subroutine were defined like most other makeXXX subroutines,
ie you have to supply *all* the fields, that argument would go away,
but the notational advantage is then dubious.The bigger-picture point is that you're proposing to make the coding
conventions for building FuncCalls different from what they are for
any other grammar node. I don't think that's a great idea; it will
mostly foster confusion.
I find this a good bit less confusing than the litany of repeated
parameter settings, the vast majority of which in most cases are along
the lines of NULL/NIL/etc.
This way, anything set that's not the default stands out.
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sun, Feb 10, 2013 at 10:09:19AM -0500, Tom Lane wrote:
David Fetter <david@fetter.org> writes:
Per suggestions and lots of help from Andrew Gierth, please find
attached a patch to clean up the call sites for FuncCall nodes, which
I'd like to expand centrally rather than in each of the 37 (or 38, but
I only redid 37) places where it's called. The remaining one is in
src/backend/nodes/copyfuncs.c, which has to be modified for any
changes in the that struct anyhow.TBH, I don't think this is an improvement.
The problem with adding a new field to any struct is that you have to
run around and examine (and, usually, modify) every place that
manufactures that type of struct. With a makeFuncCall defined like
this, you'd still have to do that; it would just become a lot easier
to forget to do so.If the subroutine were defined like most other makeXXX subroutines,
ie you have to supply *all* the fields, that argument would go away,
but the notational advantage is then dubious.The bigger-picture point is that you're proposing to make the coding
conventions for building FuncCalls different from what they are for
any other grammar node. I don't think that's a great idea; it will
mostly foster confusion.
The major difference between FuncCalls and others is that `while most
raw-parsetree nodes are constructed only in their own syntax
productions, FuncCall is constructed in many places unrelated to
actual function call syntax.
This really will make things a good bit easier on our successors.
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sat, Feb 09, 2013 at 11:59:22PM -0800, David Fetter wrote:
Folks,
Per suggestions and lots of help from Andrew Gierth, please find
attached a patch to clean up the call sites for FuncCall nodes, which
I'd like to expand centrally rather than in each of the 37 (or 38, but
I only redid 37) places where it's called. The remaining one is in
src/backend/nodes/copyfuncs.c, which has to be modified for any
changes in the that struct anyhow.The immediate purpose is two-fold: to reduce some redundancies, which
I believe is worth doing in and of itself, and to prepare for adding
FILTER on aggregates from the spec, and possibly other things in
the <aggregate function> part of the spec.Cheers,
David.
Folks,
Please find attached two versions of a patch which provides optional
FILTER clause for aggregates (T612, "Advanced OLAP operations").
The first is intended to be applied on top of the previous patch, the
second without it. The first is, I believe, clearer in what it's
doing. Rather than simply mechanically visiting every place a
function call might be constructed, it visits a central one to change
the default, then goes only to the places where it's relevant.
The patches are both early WIP as they contain no docs or regression
tests yet.
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
Attachments:
filter_mfa_001.difftext/plain; charset=us-asciiDownload
*** a/src/backend/executor/execQual.c
--- b/src/backend/executor/execQual.c
***************
*** 4395,4400 **** ExecInitExpr(Expr *node, PlanState *parent)
--- 4395,4401 ----
astate->args = (List *) ExecInitExpr((Expr *) aggref->args,
parent);
+ astate->agg_filter = ExecInitExpr(aggref->agg_filter, parent);
/*
* Complain if the aggregate's arguments contain any
***************
*** 4433,4438 **** ExecInitExpr(Expr *node, PlanState *parent)
--- 4434,4440 ----
wfstate->args = (List *) ExecInitExpr((Expr *) wfunc->args,
parent);
+ wfstate->agg_filter = ExecInitExpr(wfunc->agg_filter, parent);
/*
* Complain if the windowfunc's arguments contain any
*** a/src/backend/executor/functions.c
--- b/src/backend/executor/functions.c
***************
*** 364,370 **** sql_fn_post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var)
list_make1(subfield),
list_make1(param),
NIL, false, false, false,
! NULL, true, cref->location);
}
return param;
--- 364,370 ----
list_make1(subfield),
list_make1(param),
NIL, false, false, false,
! NULL, NULL, true, cref->location);
}
return param;
*** a/src/backend/executor/nodeAgg.c
--- b/src/backend/executor/nodeAgg.c
***************
*** 487,492 **** advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
--- 487,504 ----
int i;
TupleTableSlot *slot;
+ /* Skip anything FILTERed out */
+ ExprState *filter = peraggstate->aggrefstate->agg_filter;
+ if (filter)
+ {
+ MemoryContext oldcontext = MemoryContextSwitchTo(aggstate->tmpcontext->ecxt_per_tuple_memory);
+ bool isnull;
+ Datum res = ExecEvalExpr(filter, aggstate->tmpcontext, &isnull, NULL);
+ MemoryContextSwitchTo(oldcontext);
+ if (isnull || !DatumGetBool(res))
+ continue;
+ }
+
/* Evaluate the current input expressions for this aggregate */
slot = ExecProject(peraggstate->evalproj, NULL);
*** a/src/backend/executor/nodeWindowAgg.c
--- b/src/backend/executor/nodeWindowAgg.c
***************
*** 226,234 **** advance_windowaggregate(WindowAggState *winstate,
--- 226,247 ----
int i;
MemoryContext oldContext;
ExprContext *econtext = winstate->tmpcontext;
+ ExprState *filter = wfuncstate->agg_filter;
oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+ /* Skip anything FILTERed out */
+ if (filter)
+ {
+ bool isnull;
+ Datum res = ExecEvalExpr(filter, econtext, &isnull, NULL);
+ if (isnull || !DatumGetBool(res))
+ {
+ MemoryContextSwitchTo(oldContext);
+ return;
+ }
+ }
+
/* We start from 1, since the 0th arg will be the transition value */
i = 1;
foreach(arg, wfuncstate->args)
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 1135,1140 **** _copyAggref(const Aggref *from)
--- 1135,1141 ----
COPY_NODE_FIELD(args);
COPY_NODE_FIELD(aggorder);
COPY_NODE_FIELD(aggdistinct);
+ COPY_NODE_FIELD(agg_filter);
COPY_SCALAR_FIELD(aggstar);
COPY_SCALAR_FIELD(agglevelsup);
COPY_LOCATION_FIELD(location);
***************
*** 1155,1160 **** _copyWindowFunc(const WindowFunc *from)
--- 1156,1162 ----
COPY_SCALAR_FIELD(wincollid);
COPY_SCALAR_FIELD(inputcollid);
COPY_NODE_FIELD(args);
+ COPY_NODE_FIELD(agg_filter);
COPY_SCALAR_FIELD(winref);
COPY_SCALAR_FIELD(winstar);
COPY_SCALAR_FIELD(winagg);
***************
*** 2153,2158 **** _copyFuncCall(const FuncCall *from)
--- 2155,2161 ----
COPY_SCALAR_FIELD(agg_star);
COPY_SCALAR_FIELD(agg_distinct);
COPY_SCALAR_FIELD(func_variadic);
+ COPY_NODE_FIELD(agg_filter);
COPY_NODE_FIELD(over);
COPY_LOCATION_FIELD(location);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 195,200 **** _equalAggref(const Aggref *a, const Aggref *b)
--- 195,201 ----
COMPARE_NODE_FIELD(args);
COMPARE_NODE_FIELD(aggorder);
COMPARE_NODE_FIELD(aggdistinct);
+ COMPARE_NODE_FIELD(agg_filter);
COMPARE_SCALAR_FIELD(aggstar);
COMPARE_SCALAR_FIELD(agglevelsup);
COMPARE_LOCATION_FIELD(location);
***************
*** 210,215 **** _equalWindowFunc(const WindowFunc *a, const WindowFunc *b)
--- 211,217 ----
COMPARE_SCALAR_FIELD(wincollid);
COMPARE_SCALAR_FIELD(inputcollid);
COMPARE_NODE_FIELD(args);
+ COMPARE_NODE_FIELD(agg_filter);
COMPARE_SCALAR_FIELD(winref);
COMPARE_SCALAR_FIELD(winstar);
COMPARE_SCALAR_FIELD(winagg);
***************
*** 1997,2002 **** _equalFuncCall(const FuncCall *a, const FuncCall *b)
--- 1999,2005 ----
COMPARE_SCALAR_FIELD(agg_star);
COMPARE_SCALAR_FIELD(agg_distinct);
COMPARE_SCALAR_FIELD(func_variadic);
+ COMPARE_NODE_FIELD(agg_filter);
COMPARE_NODE_FIELD(over);
COMPARE_LOCATION_FIELD(location);
*** a/src/backend/nodes/makefuncs.c
--- b/src/backend/nodes/makefuncs.c
***************
*** 524,529 **** makeFuncCall(List *name, List *args, int location)
--- 524,530 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
return n;
}
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
***************
*** 1570,1575 **** expression_tree_walker(Node *node,
--- 1570,1578 ----
if (expression_tree_walker((Node *) expr->aggdistinct,
walker, context))
return true;
+ if (expression_tree_walker((Node *) expr->agg_filter,
+ walker, context))
+ return true;
}
break;
case T_WindowFunc:
***************
*** 1580,1585 **** expression_tree_walker(Node *node,
--- 1583,1591 ----
if (expression_tree_walker((Node *) expr->args,
walker, context))
return true;
+ if (expression_tree_walker((Node *) expr->agg_filter,
+ walker, context))
+ return true;
}
break;
case T_ArrayRef:
***************
*** 2079,2084 **** expression_tree_mutator(Node *node,
--- 2085,2091 ----
MUTATE(newnode->args, aggref->args, List *);
MUTATE(newnode->aggorder, aggref->aggorder, List *);
MUTATE(newnode->aggdistinct, aggref->aggdistinct, List *);
+ MUTATE(newnode->agg_filter, aggref->agg_filter, Expr *);
return (Node *) newnode;
}
break;
***************
*** 2089,2094 **** expression_tree_mutator(Node *node,
--- 2096,2102 ----
FLATCOPY(newnode, wfunc, WindowFunc);
MUTATE(newnode->args, wfunc->args, List *);
+ MUTATE(newnode->agg_filter, wfunc->agg_filter, Expr *);
return (Node *) newnode;
}
break;
***************
*** 2948,2953 **** raw_expression_tree_walker(Node *node,
--- 2956,2963 ----
return true;
if (walker(fcall->agg_order, context))
return true;
+ if (walker(fcall->agg_filter, context))
+ return true;
if (walker(fcall->over, context))
return true;
/* function name is deemed uninteresting */
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 956,961 **** _outAggref(StringInfo str, const Aggref *node)
--- 956,962 ----
WRITE_NODE_FIELD(args);
WRITE_NODE_FIELD(aggorder);
WRITE_NODE_FIELD(aggdistinct);
+ WRITE_NODE_FIELD(agg_filter);
WRITE_BOOL_FIELD(aggstar);
WRITE_UINT_FIELD(agglevelsup);
WRITE_LOCATION_FIELD(location);
***************
*** 971,976 **** _outWindowFunc(StringInfo str, const WindowFunc *node)
--- 972,978 ----
WRITE_OID_FIELD(wincollid);
WRITE_OID_FIELD(inputcollid);
WRITE_NODE_FIELD(args);
+ WRITE_NODE_FIELD(agg_filter);
WRITE_UINT_FIELD(winref);
WRITE_BOOL_FIELD(winstar);
WRITE_BOOL_FIELD(winagg);
***************
*** 2081,2086 **** _outFuncCall(StringInfo str, const FuncCall *node)
--- 2083,2089 ----
WRITE_BOOL_FIELD(agg_star);
WRITE_BOOL_FIELD(agg_distinct);
WRITE_BOOL_FIELD(func_variadic);
+ WRITE_NODE_FIELD(agg_filter);
WRITE_NODE_FIELD(over);
WRITE_LOCATION_FIELD(location);
}
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 478,483 **** _readAggref(void)
--- 478,484 ----
READ_NODE_FIELD(args);
READ_NODE_FIELD(aggorder);
READ_NODE_FIELD(aggdistinct);
+ READ_NODE_FIELD(agg_filter);
READ_BOOL_FIELD(aggstar);
READ_UINT_FIELD(agglevelsup);
READ_LOCATION_FIELD(location);
***************
*** 498,503 **** _readWindowFunc(void)
--- 499,505 ----
READ_OID_FIELD(wincollid);
READ_OID_FIELD(inputcollid);
READ_NODE_FIELD(args);
+ READ_NODE_FIELD(agg_filter);
READ_UINT_FIELD(winref);
READ_BOOL_FIELD(winstar);
READ_BOOL_FIELD(winagg);
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 491,496 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
--- 491,497 ----
opt_frame_clause frame_extent frame_bound
%type <str> opt_existing_window_name
%type <boolean> opt_if_not_exists
+ %type <node> filter_clause
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
***************
*** 537,543 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
EXTENSION EXTERNAL EXTRACT
! FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
FREEZE FROM FULL FUNCTION FUNCTIONS
GLOBAL GRANT GRANTED GREATEST GROUP_P
--- 538,544 ----
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
EXTENSION EXTERNAL EXTRACT
! FALSE_P FAMILY FETCH FILTER FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
FREEZE FROM FULL FUNCTION FUNCTIONS
GLOBAL GRANT GRANTED GREATEST GROUP_P
***************
*** 10890,10929 **** c_expr: columnref { $$ = $1; }
* (Note that many of the special SQL functions wouldn't actually make any
* sense as functional index entries, but we ignore that consideration here.)
*/
! func_expr: func_name '(' ')' over_clause
{
FuncCall *n = makeFuncCall($1, NIL, @1);
! n->over = $4;
! $$ = (Node *)n;
! }
! | func_name '(' func_arg_list ')' over_clause
! {
! FuncCall *n = makeFuncCall($1, $3, @1);
n->over = $5;
$$ = (Node *)n;
}
! | func_name '(' VARIADIC func_arg_expr ')' over_clause
{
FuncCall *n = makeFuncCall($1, list_make1($4), @1);
n->func_variadic = TRUE;
! n->over = $6;
$$ = (Node *)n;
}
! | func_name '(' func_arg_list ',' VARIADIC func_arg_expr ')' over_clause
{
FuncCall *n = makeFuncCall($1, lappend($3, $6), @1);
n->func_variadic = TRUE;
! n->over = $8;
$$ = (Node *)n;
}
! | func_name '(' func_arg_list sort_clause ')' over_clause
{
FuncCall *n = makeFuncCall($1, $3, @1);
n->agg_order = $4;
! n->over = $6;
$$ = (Node *)n;
}
! | func_name '(' ALL func_arg_list opt_sort_clause ')' over_clause
{
FuncCall *n = makeFuncCall($1, $4, @1);
n->agg_order = $5;
--- 10891,10935 ----
* (Note that many of the special SQL functions wouldn't actually make any
* sense as functional index entries, but we ignore that consideration here.)
*/
! func_expr: func_name '(' ')' filter_clause over_clause
{
FuncCall *n = makeFuncCall($1, NIL, @1);
! n->agg_filter = $4;
n->over = $5;
$$ = (Node *)n;
}
! | func_name '(' func_arg_list ')' filter_clause over_clause
! {
! FuncCall *n = makeFuncCall($1, $3, @1);
! n->agg_filter = $5;
! n->over = $6;
! $$ = (Node *)n;
! }
! | func_name '(' VARIADIC func_arg_expr ')' filter_clause over_clause
{
FuncCall *n = makeFuncCall($1, list_make1($4), @1);
n->func_variadic = TRUE;
! n->agg_filter = $6;
! n->over = $7;
$$ = (Node *)n;
}
! | func_name '(' func_arg_list ',' VARIADIC func_arg_expr ')' filter_clause over_clause
{
FuncCall *n = makeFuncCall($1, lappend($3, $6), @1);
n->func_variadic = TRUE;
! n->agg_filter = $8;
! n->over = $9;
$$ = (Node *)n;
}
! | func_name '(' func_arg_list sort_clause ')' filter_clause over_clause
{
FuncCall *n = makeFuncCall($1, $3, @1);
n->agg_order = $4;
! n->agg_filter = $6;
! n->over = $7;
$$ = (Node *)n;
}
! | func_name '(' ALL func_arg_list opt_sort_clause ')' filter_clause over_clause
{
FuncCall *n = makeFuncCall($1, $4, @1);
n->agg_order = $5;
***************
*** 10931,10948 **** func_expr: func_name '(' ')' over_clause
* "must be an aggregate", but there's no provision
* for that in FuncCall at the moment.
*/
! n->over = $7;
$$ = (Node *)n;
}
! | func_name '(' DISTINCT func_arg_list opt_sort_clause ')' over_clause
{
FuncCall *n = makeFuncCall($1, $4, @1);
n->agg_order = $5;
n->agg_distinct = TRUE;
! n->over = $7;
$$ = (Node *)n;
}
! | func_name '(' '*' ')' over_clause
{
/*
* We consider AGGREGATE(*) to invoke a parameterless
--- 10937,10956 ----
* "must be an aggregate", but there's no provision
* for that in FuncCall at the moment.
*/
! n->agg_filter = $7;
! n->over = $8;
$$ = (Node *)n;
}
! | func_name '(' DISTINCT func_arg_list opt_sort_clause ')' filter_clause over_clause
{
FuncCall *n = makeFuncCall($1, $4, @1);
n->agg_order = $5;
n->agg_distinct = TRUE;
! n->agg_filter = $7;
! n->over = $8;
$$ = (Node *)n;
}
! | func_name '(' '*' ')' filter_clause over_clause
{
/*
* We consider AGGREGATE(*) to invoke a parameterless
***************
*** 10956,10962 **** func_expr: func_name '(' ')' over_clause
*/
FuncCall *n = makeFuncCall($1, NIL, @1);
n->agg_star = TRUE;
! n->over = $5;
$$ = (Node *)n;
}
| COLLATION FOR '(' a_expr ')'
--- 10964,10971 ----
*/
FuncCall *n = makeFuncCall($1, NIL, @1);
n->agg_star = TRUE;
! n->agg_filter = $5;
! n->over = $6;
$$ = (Node *)n;
}
| COLLATION FOR '(' a_expr ')'
***************
*** 11331,11337 **** xmlexists_argument:
window_clause:
WINDOW window_definition_list { $$ = $2; }
| /*EMPTY*/ { $$ = NIL; }
! ;
window_definition_list:
window_definition { $$ = list_make1($1); }
--- 11340,11346 ----
window_clause:
WINDOW window_definition_list { $$ = $2; }
| /*EMPTY*/ { $$ = NIL; }
! ;
window_definition_list:
window_definition { $$ = list_make1($1); }
***************
*** 11348,11353 **** window_definition:
--- 11357,11367 ----
}
;
+ filter_clause:
+ FILTER '(' WHERE a_expr ')' { $$ = $4; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
over_clause: OVER window_specification
{ $$ = $2; }
| OVER ColId
***************
*** 12624,12629 **** reserved_keyword:
--- 12638,12644 ----
| EXCEPT
| FALSE_P
| FETCH
+ | FILTER
| FOR
| FOREIGN
| FROM
*** a/src/backend/parser/parse_agg.c
--- b/src/backend/parser/parse_agg.c
***************
*** 44,50 **** typedef struct
int sublevels_up;
} check_ungrouped_columns_context;
! static int check_agg_arguments(ParseState *pstate, List *args);
static bool check_agg_arguments_walker(Node *node,
check_agg_arguments_context *context);
static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
--- 44,50 ----
int sublevels_up;
} check_ungrouped_columns_context;
! static int check_agg_arguments(ParseState *pstate, List *args, Expr *filter);
static bool check_agg_arguments_walker(Node *node,
check_agg_arguments_context *context);
static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
***************
*** 160,166 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
* Check the arguments to compute the aggregate's level and detect
* improper nesting.
*/
! min_varlevel = check_agg_arguments(pstate, agg->args);
agg->agglevelsup = min_varlevel;
/* Mark the correct pstate level as having aggregates */
--- 160,166 ----
* Check the arguments to compute the aggregate's level and detect
* improper nesting.
*/
! min_varlevel = check_agg_arguments(pstate, agg->args, agg->agg_filter);
agg->agglevelsup = min_varlevel;
/* Mark the correct pstate level as having aggregates */
***************
*** 207,212 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
--- 207,215 ----
case EXPR_KIND_HAVING:
/* okay */
break;
+ case EXPR_KIND_FILTER:
+ errkind = true;
+ break;
case EXPR_KIND_WINDOW_PARTITION:
/* okay */
break;
***************
*** 309,315 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
* which we can't know until we finish scanning the arguments.
*/
static int
! check_agg_arguments(ParseState *pstate, List *args)
{
int agglevel;
check_agg_arguments_context context;
--- 312,318 ----
* which we can't know until we finish scanning the arguments.
*/
static int
! check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
{
int agglevel;
check_agg_arguments_context context;
***************
*** 323,328 **** check_agg_arguments(ParseState *pstate, List *args)
--- 326,335 ----
check_agg_arguments_walker,
(void *) &context);
+ (void) expression_tree_walker((Node *) filter,
+ check_agg_arguments_walker,
+ (void *) &context);
+
/*
* If we found no vars nor aggs at all, it's a level-zero aggregate;
* otherwise, its level is the minimum of vars or aggs.
***************
*** 481,486 **** transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
--- 488,496 ----
case EXPR_KIND_HAVING:
errkind = true;
break;
+ case EXPR_KIND_FILTER:
+ errkind = true;
+ break;
case EXPR_KIND_WINDOW_PARTITION:
case EXPR_KIND_WINDOW_ORDER:
case EXPR_KIND_WINDOW_FRAME_RANGE:
*** a/src/backend/parser/parse_expr.c
--- b/src/backend/parser/parse_expr.c
***************
*** 22,27 ****
--- 22,28 ----
#include "nodes/nodeFuncs.h"
#include "optimizer/var.h"
#include "parser/analyze.h"
+ #include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
***************
*** 463,469 **** transformIndirection(ParseState *pstate, Node *basenode, List *indirection)
list_make1(n),
list_make1(result),
NIL, false, false, false,
! NULL, true, location);
if (newresult == NULL)
unknown_attribute(pstate, result, strVal(n), location);
result = newresult;
--- 464,470 ----
list_make1(n),
list_make1(result),
NIL, false, false, false,
! NULL, NULL, true, location);
if (newresult == NULL)
unknown_attribute(pstate, result, strVal(n), location);
result = newresult;
***************
*** 631,637 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, true, cref->location);
}
break;
}
--- 632,638 ----
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, NULL, true, cref->location);
}
break;
}
***************
*** 676,682 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, true, cref->location);
}
break;
}
--- 677,683 ----
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, NULL, true, cref->location);
}
break;
}
***************
*** 734,740 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, true, cref->location);
}
break;
}
--- 735,741 ----
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, NULL, true, cref->location);
}
break;
}
***************
*** 1241,1246 **** transformFuncCall(ParseState *pstate, FuncCall *fn)
--- 1242,1248 ----
{
List *targs;
ListCell *args;
+ Expr *tagg_filter;
/* Transform the list of arguments ... */
targs = NIL;
***************
*** 1250,1255 **** transformFuncCall(ParseState *pstate, FuncCall *fn)
--- 1252,1263 ----
(Node *) lfirst(args)));
}
+ /* Transform the aggregate filter using transformWhereClause, to
+ * which FILTER is virually identical... */
+ tagg_filter = NULL;
+ if (fn->agg_filter != NULL)
+ tagg_filter = (Expr *)transformWhereClause(pstate, (Node *)fn->agg_filter, EXPR_KIND_FILTER, "FILTER");
+
/* ... and hand off to ParseFuncOrColumn */
return ParseFuncOrColumn(pstate,
fn->funcname,
***************
*** 1258,1263 **** transformFuncCall(ParseState *pstate, FuncCall *fn)
--- 1266,1272 ----
fn->agg_star,
fn->agg_distinct,
fn->func_variadic,
+ tagg_filter,
fn->over,
false,
fn->location);
***************
*** 1430,1435 **** transformSubLink(ParseState *pstate, SubLink *sublink)
--- 1439,1445 ----
case EXPR_KIND_FROM_FUNCTION:
case EXPR_KIND_WHERE:
case EXPR_KIND_HAVING:
+ case EXPR_KIND_FILTER:
case EXPR_KIND_WINDOW_PARTITION:
case EXPR_KIND_WINDOW_ORDER:
case EXPR_KIND_WINDOW_FRAME_RANGE:
***************
*** 2579,2584 **** ParseExprKindName(ParseExprKind exprKind)
--- 2589,2596 ----
return "WHERE";
case EXPR_KIND_HAVING:
return "HAVING";
+ case EXPR_KIND_FILTER:
+ return "FILTER";
case EXPR_KIND_WINDOW_PARTITION:
return "window PARTITION BY";
case EXPR_KIND_WINDOW_ORDER:
*** a/src/backend/parser/parse_func.c
--- b/src/backend/parser/parse_func.c
***************
*** 63,69 **** Node *
ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
List *agg_order, bool agg_star, bool agg_distinct,
bool func_variadic,
! WindowDef *over, bool is_column, int location)
{
Oid rettype;
Oid funcid;
--- 63,69 ----
ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
List *agg_order, bool agg_star, bool agg_distinct,
bool func_variadic,
! Expr *agg_filter, WindowDef *over, bool is_column, int location)
{
Oid rettype;
Oid funcid;
***************
*** 175,181 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
* wasn't any aggregate or variadic decoration, nor an argument name.
*/
if (nargs == 1 && agg_order == NIL && !agg_star && !agg_distinct &&
! over == NULL && !func_variadic && argnames == NIL &&
list_length(funcname) == 1)
{
Oid argtype = actual_arg_types[0];
--- 175,181 ----
* wasn't any aggregate or variadic decoration, nor an argument name.
*/
if (nargs == 1 && agg_order == NIL && !agg_star && !agg_distinct &&
! agg_filter == NULL && over == NULL && !func_variadic && argnames == NIL &&
list_length(funcname) == 1)
{
Oid argtype = actual_arg_types[0];
***************
*** 251,256 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 251,262 ----
errmsg("ORDER BY specified, but %s is not an aggregate function",
NameListToString(funcname)),
parser_errposition(pstate, location)));
+ if (agg_filter)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("FILTER specified, but %s is not an aggregate function",
+ NameListToString(funcname)),
+ parser_errposition(pstate, location)));
if (over)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
***************
*** 402,407 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 408,415 ----
/* aggcollid and inputcollid will be set by parse_collate.c */
/* args, aggorder, aggdistinct will be set by transformAggregateCall */
aggref->aggstar = agg_star;
+ /* filter */
+ aggref->agg_filter = agg_filter;
/* agglevelsup will be set by transformAggregateCall */
aggref->location = location;
***************
*** 460,465 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 468,474 ----
/* winref will be set by transformWindowFuncCall */
wfunc->winstar = agg_star;
wfunc->winagg = (fdresult == FUNCDETAIL_AGGREGATE);
+ wfunc->agg_filter = agg_filter;
wfunc->location = location;
/*
***************
*** 482,487 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 491,506 ----
NameListToString(funcname)),
parser_errposition(pstate, location)));
+ /*
+ * Reject window functions which are not aggregates in the
+ * case of FILTER.
+ */
+ if (!wfunc->winagg && agg_filter)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("FILTER is not implemented in non-aggregate window functions",
+ parser_errposition(pstate, location))));
+
/*
* ordered aggs not allowed in windows yet
*/
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 7419,7425 **** get_agg_expr(Aggref *aggref, deparse_context *context)
--- 7419,7433 ----
appendStringInfoString(buf, " ORDER BY ");
get_rule_orderby(aggref->aggorder, aggref->args, false, context);
}
+
+ if (aggref->agg_filter != NULL)
+ {
+ appendStringInfoString(buf, ") FILTER (WHERE ");
+ get_rule_expr((Node *)aggref->agg_filter, context, false);
+ }
+
appendStringInfoChar(buf, ')');
+
}
/*
***************
*** 7456,7461 **** get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context)
--- 7464,7476 ----
appendStringInfoChar(buf, '*');
else
get_rule_expr((Node *) wfunc->args, context, true);
+
+ if (wfunc->agg_filter != NULL)
+ {
+ appendStringInfoString(buf, ") FILTER (WHERE ");
+ get_rule_expr((Node *)wfunc->agg_filter, context, false);
+ }
+
appendStringInfoString(buf, ") OVER ");
foreach(l, context->windowClause)
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 579,584 **** typedef struct AggrefExprState
--- 579,585 ----
{
ExprState xprstate;
List *args; /* states of argument expressions */
+ Expr *agg_filter; /* FILTER expression */
int aggno; /* ID number for agg within its plan node */
} AggrefExprState;
***************
*** 590,595 **** typedef struct WindowFuncExprState
--- 591,597 ----
{
ExprState xprstate;
List *args; /* states of argument expressions */
+ Expr *agg_filter; /* FILTER expression */
int wfuncno; /* ID number for wfunc within its plan node */
} WindowFuncExprState;
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 295,300 **** typedef struct FuncCall
--- 295,301 ----
bool agg_star; /* argument was really '*' */
bool agg_distinct; /* arguments were labeled DISTINCT */
bool func_variadic; /* last argument was labeled VARIADIC */
+ Node *agg_filter; /* FILTER clause, if any */
struct WindowDef *over; /* OVER clause, if any */
int location; /* token location, or -1 if unknown */
} FuncCall;
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
***************
*** 241,246 **** typedef struct Aggref
--- 241,247 ----
List *args; /* arguments and sort expressions */
List *aggorder; /* ORDER BY (list of SortGroupClause) */
List *aggdistinct; /* DISTINCT (list of SortGroupClause) */
+ Expr *agg_filter; /* FILTER expression */
bool aggstar; /* TRUE if argument list was really '*' */
Index agglevelsup; /* > 0 if agg belongs to outer query */
int location; /* token location, or -1 if unknown */
***************
*** 257,262 **** typedef struct WindowFunc
--- 258,264 ----
Oid wincollid; /* OID of collation of result */
Oid inputcollid; /* OID of collation that function should use */
List *args; /* arguments to the window function */
+ Expr *agg_filter; /* FILTER expression */
Index winref; /* index of associated WindowClause */
bool winstar; /* TRUE if argument list was really '*' */
bool winagg; /* is function a simple aggregate? */
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 155,160 **** PG_KEYWORD("extract", EXTRACT, COL_NAME_KEYWORD)
--- 155,161 ----
PG_KEYWORD("false", FALSE_P, RESERVED_KEYWORD)
PG_KEYWORD("family", FAMILY, UNRESERVED_KEYWORD)
PG_KEYWORD("fetch", FETCH, RESERVED_KEYWORD)
+ PG_KEYWORD("filter", FILTER, RESERVED_KEYWORD)
PG_KEYWORD("first", FIRST_P, UNRESERVED_KEYWORD)
PG_KEYWORD("float", FLOAT_P, COL_NAME_KEYWORD)
PG_KEYWORD("following", FOLLOWING, UNRESERVED_KEYWORD)
*** a/src/include/parser/parse_func.h
--- b/src/include/parser/parse_func.h
***************
*** 46,52 **** extern Node *ParseFuncOrColumn(ParseState *pstate,
List *funcname, List *fargs,
List *agg_order, bool agg_star, bool agg_distinct,
bool func_variadic,
! WindowDef *over, bool is_column, int location);
extern FuncDetailCode func_get_detail(List *funcname,
List *fargs, List *fargnames,
--- 46,52 ----
List *funcname, List *fargs,
List *agg_order, bool agg_star, bool agg_distinct,
bool func_variadic,
! Expr *agg_filter, WindowDef *over, bool is_column, int location);
extern FuncDetailCode func_get_detail(List *funcname,
List *fargs, List *fargnames,
*** a/src/include/parser/parse_node.h
--- b/src/include/parser/parse_node.h
***************
*** 39,44 **** typedef enum ParseExprKind
--- 39,45 ----
EXPR_KIND_FROM_FUNCTION, /* function in FROM clause */
EXPR_KIND_WHERE, /* WHERE */
EXPR_KIND_HAVING, /* HAVING */
+ EXPR_KIND_FILTER, /* FILTER */
EXPR_KIND_WINDOW_PARTITION, /* window definition PARTITION BY */
EXPR_KIND_WINDOW_ORDER, /* window definition ORDER BY */
EXPR_KIND_WINDOW_FRAME_RANGE, /* window frame clause with RANGE */
filter_no_mfa_001.difftext/plain; charset=us-asciiDownload
*** a/src/backend/executor/execQual.c
--- b/src/backend/executor/execQual.c
***************
*** 4395,4400 **** ExecInitExpr(Expr *node, PlanState *parent)
--- 4395,4401 ----
astate->args = (List *) ExecInitExpr((Expr *) aggref->args,
parent);
+ astate->agg_filter = ExecInitExpr(aggref->agg_filter, parent);
/*
* Complain if the aggregate's arguments contain any
***************
*** 4433,4438 **** ExecInitExpr(Expr *node, PlanState *parent)
--- 4434,4440 ----
wfstate->args = (List *) ExecInitExpr((Expr *) wfunc->args,
parent);
+ wfstate->agg_filter = ExecInitExpr(wfunc->agg_filter, parent);
/*
* Complain if the windowfunc's arguments contain any
*** a/src/backend/executor/functions.c
--- b/src/backend/executor/functions.c
***************
*** 364,370 **** sql_fn_post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var)
list_make1(subfield),
list_make1(param),
NIL, false, false, false,
! NULL, true, cref->location);
}
return param;
--- 364,370 ----
list_make1(subfield),
list_make1(param),
NIL, false, false, false,
! NULL, NULL, true, cref->location);
}
return param;
*** a/src/backend/executor/nodeAgg.c
--- b/src/backend/executor/nodeAgg.c
***************
*** 487,492 **** advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
--- 487,504 ----
int i;
TupleTableSlot *slot;
+ /* Skip anything FILTERed out */
+ ExprState *filter = peraggstate->aggrefstate->agg_filter;
+ if (filter)
+ {
+ MemoryContext oldcontext = MemoryContextSwitchTo(aggstate->tmpcontext->ecxt_per_tuple_memory);
+ bool isnull;
+ Datum res = ExecEvalExpr(filter, aggstate->tmpcontext, &isnull, NULL);
+ MemoryContextSwitchTo(oldcontext);
+ if (isnull || !DatumGetBool(res))
+ continue;
+ }
+
/* Evaluate the current input expressions for this aggregate */
slot = ExecProject(peraggstate->evalproj, NULL);
*** a/src/backend/executor/nodeWindowAgg.c
--- b/src/backend/executor/nodeWindowAgg.c
***************
*** 226,234 **** advance_windowaggregate(WindowAggState *winstate,
--- 226,247 ----
int i;
MemoryContext oldContext;
ExprContext *econtext = winstate->tmpcontext;
+ ExprState *filter = wfuncstate->agg_filter;
oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+ /* Skip anything FILTERed out */
+ if (filter)
+ {
+ bool isnull;
+ Datum res = ExecEvalExpr(filter, econtext, &isnull, NULL);
+ if (isnull || !DatumGetBool(res))
+ {
+ MemoryContextSwitchTo(oldContext);
+ return;
+ }
+ }
+
/* We start from 1, since the 0th arg will be the transition value */
i = 1;
foreach(arg, wfuncstate->args)
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 1135,1140 **** _copyAggref(const Aggref *from)
--- 1135,1141 ----
COPY_NODE_FIELD(args);
COPY_NODE_FIELD(aggorder);
COPY_NODE_FIELD(aggdistinct);
+ COPY_NODE_FIELD(agg_filter);
COPY_SCALAR_FIELD(aggstar);
COPY_SCALAR_FIELD(agglevelsup);
COPY_LOCATION_FIELD(location);
***************
*** 1155,1160 **** _copyWindowFunc(const WindowFunc *from)
--- 1156,1162 ----
COPY_SCALAR_FIELD(wincollid);
COPY_SCALAR_FIELD(inputcollid);
COPY_NODE_FIELD(args);
+ COPY_NODE_FIELD(agg_filter);
COPY_SCALAR_FIELD(winref);
COPY_SCALAR_FIELD(winstar);
COPY_SCALAR_FIELD(winagg);
***************
*** 2153,2158 **** _copyFuncCall(const FuncCall *from)
--- 2155,2161 ----
COPY_SCALAR_FIELD(agg_star);
COPY_SCALAR_FIELD(agg_distinct);
COPY_SCALAR_FIELD(func_variadic);
+ COPY_NODE_FIELD(agg_filter);
COPY_NODE_FIELD(over);
COPY_LOCATION_FIELD(location);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 195,200 **** _equalAggref(const Aggref *a, const Aggref *b)
--- 195,201 ----
COMPARE_NODE_FIELD(args);
COMPARE_NODE_FIELD(aggorder);
COMPARE_NODE_FIELD(aggdistinct);
+ COMPARE_NODE_FIELD(agg_filter);
COMPARE_SCALAR_FIELD(aggstar);
COMPARE_SCALAR_FIELD(agglevelsup);
COMPARE_LOCATION_FIELD(location);
***************
*** 210,215 **** _equalWindowFunc(const WindowFunc *a, const WindowFunc *b)
--- 211,217 ----
COMPARE_SCALAR_FIELD(wincollid);
COMPARE_SCALAR_FIELD(inputcollid);
COMPARE_NODE_FIELD(args);
+ COMPARE_NODE_FIELD(agg_filter);
COMPARE_SCALAR_FIELD(winref);
COMPARE_SCALAR_FIELD(winstar);
COMPARE_SCALAR_FIELD(winagg);
***************
*** 1997,2002 **** _equalFuncCall(const FuncCall *a, const FuncCall *b)
--- 1999,2005 ----
COMPARE_SCALAR_FIELD(agg_star);
COMPARE_SCALAR_FIELD(agg_distinct);
COMPARE_SCALAR_FIELD(func_variadic);
+ COMPARE_NODE_FIELD(agg_filter);
COMPARE_NODE_FIELD(over);
COMPARE_LOCATION_FIELD(location);
*** a/src/backend/nodes/makefuncs.c
--- b/src/backend/nodes/makefuncs.c
***************
*** 508,510 **** makeDefElemExtended(char *nameSpace, char *name, Node *arg,
--- 508,534 ----
return res;
}
+ <<<<<<< HEAD
+ =======
+
+ /*
+ * makeFuncCall -
+ * set up to call a function. DRY!
+ */
+ FuncCall *
+ makeFuncCall(List *name, List *args, int location)
+ {
+ FuncCall *n = makeNode(FuncCall);
+ n->funcname = name;
+ n->args = args;
+ n->location = location;
+ n->agg_order = NIL;
+ n->agg_star = FALSE;
+ n->agg_distinct = FALSE;
+ n->func_variadic = FALSE;
+ n->agg_filter = NULL;
+ n->over = NULL;
+ return n;
+ }
+
+ >>>>>>> 4eb9c7c... First cut at FILTER on aggregates
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
***************
*** 1570,1575 **** expression_tree_walker(Node *node,
--- 1570,1578 ----
if (expression_tree_walker((Node *) expr->aggdistinct,
walker, context))
return true;
+ if (expression_tree_walker((Node *) expr->agg_filter,
+ walker, context))
+ return true;
}
break;
case T_WindowFunc:
***************
*** 1580,1585 **** expression_tree_walker(Node *node,
--- 1583,1591 ----
if (expression_tree_walker((Node *) expr->args,
walker, context))
return true;
+ if (expression_tree_walker((Node *) expr->agg_filter,
+ walker, context))
+ return true;
}
break;
case T_ArrayRef:
***************
*** 2079,2084 **** expression_tree_mutator(Node *node,
--- 2085,2091 ----
MUTATE(newnode->args, aggref->args, List *);
MUTATE(newnode->aggorder, aggref->aggorder, List *);
MUTATE(newnode->aggdistinct, aggref->aggdistinct, List *);
+ MUTATE(newnode->agg_filter, aggref->agg_filter, Expr *);
return (Node *) newnode;
}
break;
***************
*** 2089,2094 **** expression_tree_mutator(Node *node,
--- 2096,2102 ----
FLATCOPY(newnode, wfunc, WindowFunc);
MUTATE(newnode->args, wfunc->args, List *);
+ MUTATE(newnode->agg_filter, wfunc->agg_filter, Expr *);
return (Node *) newnode;
}
break;
***************
*** 2948,2953 **** raw_expression_tree_walker(Node *node,
--- 2956,2963 ----
return true;
if (walker(fcall->agg_order, context))
return true;
+ if (walker(fcall->agg_filter, context))
+ return true;
if (walker(fcall->over, context))
return true;
/* function name is deemed uninteresting */
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 956,961 **** _outAggref(StringInfo str, const Aggref *node)
--- 956,962 ----
WRITE_NODE_FIELD(args);
WRITE_NODE_FIELD(aggorder);
WRITE_NODE_FIELD(aggdistinct);
+ WRITE_NODE_FIELD(agg_filter);
WRITE_BOOL_FIELD(aggstar);
WRITE_UINT_FIELD(agglevelsup);
WRITE_LOCATION_FIELD(location);
***************
*** 971,976 **** _outWindowFunc(StringInfo str, const WindowFunc *node)
--- 972,978 ----
WRITE_OID_FIELD(wincollid);
WRITE_OID_FIELD(inputcollid);
WRITE_NODE_FIELD(args);
+ WRITE_NODE_FIELD(agg_filter);
WRITE_UINT_FIELD(winref);
WRITE_BOOL_FIELD(winstar);
WRITE_BOOL_FIELD(winagg);
***************
*** 2081,2086 **** _outFuncCall(StringInfo str, const FuncCall *node)
--- 2083,2089 ----
WRITE_BOOL_FIELD(agg_star);
WRITE_BOOL_FIELD(agg_distinct);
WRITE_BOOL_FIELD(func_variadic);
+ WRITE_NODE_FIELD(agg_filter);
WRITE_NODE_FIELD(over);
WRITE_LOCATION_FIELD(location);
}
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 478,483 **** _readAggref(void)
--- 478,484 ----
READ_NODE_FIELD(args);
READ_NODE_FIELD(aggorder);
READ_NODE_FIELD(aggdistinct);
+ READ_NODE_FIELD(agg_filter);
READ_BOOL_FIELD(aggstar);
READ_UINT_FIELD(agglevelsup);
READ_LOCATION_FIELD(location);
***************
*** 498,503 **** _readWindowFunc(void)
--- 499,505 ----
READ_OID_FIELD(wincollid);
READ_OID_FIELD(inputcollid);
READ_NODE_FIELD(args);
+ READ_NODE_FIELD(agg_filter);
READ_UINT_FIELD(winref);
READ_BOOL_FIELD(winstar);
READ_BOOL_FIELD(winagg);
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 491,496 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
--- 491,497 ----
opt_frame_clause frame_extent frame_bound
%type <str> opt_existing_window_name
%type <boolean> opt_if_not_exists
+ %type <node> filter_clause
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
***************
*** 537,543 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
EXTENSION EXTERNAL EXTRACT
! FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
FREEZE FROM FULL FUNCTION FUNCTIONS
GLOBAL GRANT GRANTED GREATEST GROUP_P
--- 538,544 ----
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
EXTENSION EXTERNAL EXTRACT
! FALSE_P FAMILY FETCH FILTER FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
FREEZE FROM FULL FUNCTION FUNCTIONS
GLOBAL GRANT GRANTED GREATEST GROUP_P
***************
*** 10944,10951 **** c_expr: columnref { $$ = $1; }
* (Note that many of the special SQL functions wouldn't actually make any
* sense as functional index entries, but we ignore that consideration here.)
*/
! func_expr: func_name '(' ')' over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
n->args = NIL;
--- 10945,10953 ----
* (Note that many of the special SQL functions wouldn't actually make any
* sense as functional index entries, but we ignore that consideration here.)
*/
! func_expr: func_name '(' ')' filter_clause over_clause
{
+ <<<<<<< HEAD
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
n->args = NIL;
***************
*** 10955,10964 **** func_expr: func_name '(' ')' over_clause
n->func_variadic = FALSE;
n->over = $4;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' func_arg_list ')' over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
n->args = $3;
--- 10957,10972 ----
n->func_variadic = FALSE;
n->over = $4;
n->location = @1;
+ =======
+ FuncCall *n = makeFuncCall($1, NIL, @1);
+ n->agg_filter = $4;
+ n->over = $5;
+ >>>>>>> 4eb9c7c... First cut at FILTER on aggregates
$$ = (Node *)n;
}
! | func_name '(' func_arg_list ')' filter_clause over_clause
{
+ <<<<<<< HEAD
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
n->args = $3;
***************
*** 10968,10976 **** func_expr: func_name '(' ')' over_clause
n->func_variadic = FALSE;
n->over = $5;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' VARIADIC func_arg_expr ')' over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
--- 10976,10989 ----
n->func_variadic = FALSE;
n->over = $5;
n->location = @1;
+ =======
+ FuncCall *n = makeFuncCall($1, $3, @1);
+ n->agg_filter = $5;
+ n->over = $6;
+ >>>>>>> 4eb9c7c... First cut at FILTER on aggregates
$$ = (Node *)n;
}
! | func_name '(' VARIADIC func_arg_expr ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
***************
*** 10979,10989 **** func_expr: func_name '(' ')' over_clause
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = TRUE;
n->over = $6;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' func_arg_list ',' VARIADIC func_arg_expr ')' over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
--- 10992,11007 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = TRUE;
+ <<<<<<< HEAD
n->over = $6;
n->location = @1;
+ =======
+ n->agg_filter = $6;
+ n->over = $7;
+ >>>>>>> 4eb9c7c... First cut at FILTER on aggregates
$$ = (Node *)n;
}
! | func_name '(' func_arg_list ',' VARIADIC func_arg_expr ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
***************
*** 10992,11015 **** func_expr: func_name '(' ')' over_clause
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = TRUE;
n->over = $8;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' func_arg_list sort_clause ')' over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
n->args = $3;
n->agg_order = $4;
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
n->over = $6;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' ALL func_arg_list opt_sort_clause ')' over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
--- 11010,11043 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = TRUE;
+ <<<<<<< HEAD
n->over = $8;
n->location = @1;
+ =======
+ n->agg_filter = $8;
+ n->over = $9;
+ >>>>>>> 4eb9c7c... First cut at FILTER on aggregates
$$ = (Node *)n;
}
! | func_name '(' func_arg_list sort_clause ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
n->args = $3;
n->agg_order = $4;
+ <<<<<<< HEAD
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
n->over = $6;
n->location = @1;
+ =======
+ n->agg_filter = $6;
+ n->over = $7;
+ >>>>>>> 4eb9c7c... First cut at FILTER on aggregates
$$ = (Node *)n;
}
! | func_name '(' ALL func_arg_list opt_sort_clause ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
***************
*** 11021,11032 **** func_expr: func_name '(' ')' over_clause
* "must be an aggregate", but there's no provision
* for that in FuncCall at the moment.
*/
n->func_variadic = FALSE;
n->over = $7;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' DISTINCT func_arg_list opt_sort_clause ')' over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
--- 11049,11065 ----
* "must be an aggregate", but there's no provision
* for that in FuncCall at the moment.
*/
+ <<<<<<< HEAD
n->func_variadic = FALSE;
n->over = $7;
n->location = @1;
+ =======
+ n->agg_filter = $7;
+ n->over = $8;
+ >>>>>>> 4eb9c7c... First cut at FILTER on aggregates
$$ = (Node *)n;
}
! | func_name '(' DISTINCT func_arg_list opt_sort_clause ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
***************
*** 11034,11045 **** func_expr: func_name '(' ')' over_clause
n->agg_order = $5;
n->agg_star = FALSE;
n->agg_distinct = TRUE;
n->func_variadic = FALSE;
n->over = $7;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' '*' ')' over_clause
{
/*
* We consider AGGREGATE(*) to invoke a parameterless
--- 11067,11083 ----
n->agg_order = $5;
n->agg_star = FALSE;
n->agg_distinct = TRUE;
+ <<<<<<< HEAD
n->func_variadic = FALSE;
n->over = $7;
n->location = @1;
+ =======
+ n->agg_filter = $7;
+ n->over = $8;
+ >>>>>>> 4eb9c7c... First cut at FILTER on aggregates
$$ = (Node *)n;
}
! | func_name '(' '*' ')' filter_clause over_clause
{
/*
* We consider AGGREGATE(*) to invoke a parameterless
***************
*** 11056,11065 **** func_expr: func_name '(' ')' over_clause
--- 11094,11108 ----
n->args = NIL;
n->agg_order = NIL;
n->agg_star = TRUE;
+ <<<<<<< HEAD
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
n->over = $5;
n->location = @1;
+ =======
+ n->agg_filter = $5;
+ n->over = $6;
+ >>>>>>> 4eb9c7c... First cut at FILTER on aggregates
$$ = (Node *)n;
}
| COLLATION FOR '(' a_expr ')'
***************
*** 11592,11598 **** xmlexists_argument:
window_clause:
WINDOW window_definition_list { $$ = $2; }
| /*EMPTY*/ { $$ = NIL; }
! ;
window_definition_list:
window_definition { $$ = list_make1($1); }
--- 11635,11641 ----
window_clause:
WINDOW window_definition_list { $$ = $2; }
| /*EMPTY*/ { $$ = NIL; }
! ;
window_definition_list:
window_definition { $$ = list_make1($1); }
***************
*** 11609,11614 **** window_definition:
--- 11652,11662 ----
}
;
+ filter_clause:
+ FILTER '(' WHERE a_expr ')' { $$ = $4; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
over_clause: OVER window_specification
{ $$ = $2; }
| OVER ColId
***************
*** 12885,12890 **** reserved_keyword:
--- 12933,12939 ----
| EXCEPT
| FALSE_P
| FETCH
+ | FILTER
| FOR
| FOREIGN
| FROM
*** a/src/backend/parser/parse_agg.c
--- b/src/backend/parser/parse_agg.c
***************
*** 44,50 **** typedef struct
int sublevels_up;
} check_ungrouped_columns_context;
! static int check_agg_arguments(ParseState *pstate, List *args);
static bool check_agg_arguments_walker(Node *node,
check_agg_arguments_context *context);
static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
--- 44,50 ----
int sublevels_up;
} check_ungrouped_columns_context;
! static int check_agg_arguments(ParseState *pstate, List *args, Expr *filter);
static bool check_agg_arguments_walker(Node *node,
check_agg_arguments_context *context);
static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
***************
*** 160,166 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
* Check the arguments to compute the aggregate's level and detect
* improper nesting.
*/
! min_varlevel = check_agg_arguments(pstate, agg->args);
agg->agglevelsup = min_varlevel;
/* Mark the correct pstate level as having aggregates */
--- 160,166 ----
* Check the arguments to compute the aggregate's level and detect
* improper nesting.
*/
! min_varlevel = check_agg_arguments(pstate, agg->args, agg->agg_filter);
agg->agglevelsup = min_varlevel;
/* Mark the correct pstate level as having aggregates */
***************
*** 207,212 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
--- 207,215 ----
case EXPR_KIND_HAVING:
/* okay */
break;
+ case EXPR_KIND_FILTER:
+ errkind = true;
+ break;
case EXPR_KIND_WINDOW_PARTITION:
/* okay */
break;
***************
*** 309,315 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
* which we can't know until we finish scanning the arguments.
*/
static int
! check_agg_arguments(ParseState *pstate, List *args)
{
int agglevel;
check_agg_arguments_context context;
--- 312,318 ----
* which we can't know until we finish scanning the arguments.
*/
static int
! check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
{
int agglevel;
check_agg_arguments_context context;
***************
*** 323,328 **** check_agg_arguments(ParseState *pstate, List *args)
--- 326,335 ----
check_agg_arguments_walker,
(void *) &context);
+ (void) expression_tree_walker((Node *) filter,
+ check_agg_arguments_walker,
+ (void *) &context);
+
/*
* If we found no vars nor aggs at all, it's a level-zero aggregate;
* otherwise, its level is the minimum of vars or aggs.
***************
*** 481,486 **** transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
--- 488,496 ----
case EXPR_KIND_HAVING:
errkind = true;
break;
+ case EXPR_KIND_FILTER:
+ errkind = true;
+ break;
case EXPR_KIND_WINDOW_PARTITION:
case EXPR_KIND_WINDOW_ORDER:
case EXPR_KIND_WINDOW_FRAME_RANGE:
*** a/src/backend/parser/parse_expr.c
--- b/src/backend/parser/parse_expr.c
***************
*** 22,27 ****
--- 22,28 ----
#include "nodes/nodeFuncs.h"
#include "optimizer/var.h"
#include "parser/analyze.h"
+ #include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
***************
*** 463,469 **** transformIndirection(ParseState *pstate, Node *basenode, List *indirection)
list_make1(n),
list_make1(result),
NIL, false, false, false,
! NULL, true, location);
if (newresult == NULL)
unknown_attribute(pstate, result, strVal(n), location);
result = newresult;
--- 464,470 ----
list_make1(n),
list_make1(result),
NIL, false, false, false,
! NULL, NULL, true, location);
if (newresult == NULL)
unknown_attribute(pstate, result, strVal(n), location);
result = newresult;
***************
*** 631,637 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, true, cref->location);
}
break;
}
--- 632,638 ----
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, NULL, true, cref->location);
}
break;
}
***************
*** 676,682 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, true, cref->location);
}
break;
}
--- 677,683 ----
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, NULL, true, cref->location);
}
break;
}
***************
*** 734,740 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, true, cref->location);
}
break;
}
--- 735,741 ----
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, NULL, true, cref->location);
}
break;
}
***************
*** 1241,1246 **** transformFuncCall(ParseState *pstate, FuncCall *fn)
--- 1242,1248 ----
{
List *targs;
ListCell *args;
+ Expr *tagg_filter;
/* Transform the list of arguments ... */
targs = NIL;
***************
*** 1250,1255 **** transformFuncCall(ParseState *pstate, FuncCall *fn)
--- 1252,1263 ----
(Node *) lfirst(args)));
}
+ /* Transform the aggregate filter using transformWhereClause, to
+ * which FILTER is virually identical... */
+ tagg_filter = NULL;
+ if (fn->agg_filter != NULL)
+ tagg_filter = (Expr *)transformWhereClause(pstate, (Node *)fn->agg_filter, EXPR_KIND_FILTER, "FILTER");
+
/* ... and hand off to ParseFuncOrColumn */
return ParseFuncOrColumn(pstate,
fn->funcname,
***************
*** 1258,1263 **** transformFuncCall(ParseState *pstate, FuncCall *fn)
--- 1266,1272 ----
fn->agg_star,
fn->agg_distinct,
fn->func_variadic,
+ tagg_filter,
fn->over,
false,
fn->location);
***************
*** 1430,1435 **** transformSubLink(ParseState *pstate, SubLink *sublink)
--- 1439,1445 ----
case EXPR_KIND_FROM_FUNCTION:
case EXPR_KIND_WHERE:
case EXPR_KIND_HAVING:
+ case EXPR_KIND_FILTER:
case EXPR_KIND_WINDOW_PARTITION:
case EXPR_KIND_WINDOW_ORDER:
case EXPR_KIND_WINDOW_FRAME_RANGE:
***************
*** 2579,2584 **** ParseExprKindName(ParseExprKind exprKind)
--- 2589,2596 ----
return "WHERE";
case EXPR_KIND_HAVING:
return "HAVING";
+ case EXPR_KIND_FILTER:
+ return "FILTER";
case EXPR_KIND_WINDOW_PARTITION:
return "window PARTITION BY";
case EXPR_KIND_WINDOW_ORDER:
*** a/src/backend/parser/parse_func.c
--- b/src/backend/parser/parse_func.c
***************
*** 63,69 **** Node *
ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
List *agg_order, bool agg_star, bool agg_distinct,
bool func_variadic,
! WindowDef *over, bool is_column, int location)
{
Oid rettype;
Oid funcid;
--- 63,69 ----
ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
List *agg_order, bool agg_star, bool agg_distinct,
bool func_variadic,
! Expr *agg_filter, WindowDef *over, bool is_column, int location)
{
Oid rettype;
Oid funcid;
***************
*** 175,181 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
* wasn't any aggregate or variadic decoration, nor an argument name.
*/
if (nargs == 1 && agg_order == NIL && !agg_star && !agg_distinct &&
! over == NULL && !func_variadic && argnames == NIL &&
list_length(funcname) == 1)
{
Oid argtype = actual_arg_types[0];
--- 175,181 ----
* wasn't any aggregate or variadic decoration, nor an argument name.
*/
if (nargs == 1 && agg_order == NIL && !agg_star && !agg_distinct &&
! agg_filter == NULL && over == NULL && !func_variadic && argnames == NIL &&
list_length(funcname) == 1)
{
Oid argtype = actual_arg_types[0];
***************
*** 251,256 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 251,262 ----
errmsg("ORDER BY specified, but %s is not an aggregate function",
NameListToString(funcname)),
parser_errposition(pstate, location)));
+ if (agg_filter)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("FILTER specified, but %s is not an aggregate function",
+ NameListToString(funcname)),
+ parser_errposition(pstate, location)));
if (over)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
***************
*** 402,407 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 408,415 ----
/* aggcollid and inputcollid will be set by parse_collate.c */
/* args, aggorder, aggdistinct will be set by transformAggregateCall */
aggref->aggstar = agg_star;
+ /* filter */
+ aggref->agg_filter = agg_filter;
/* agglevelsup will be set by transformAggregateCall */
aggref->location = location;
***************
*** 460,465 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 468,474 ----
/* winref will be set by transformWindowFuncCall */
wfunc->winstar = agg_star;
wfunc->winagg = (fdresult == FUNCDETAIL_AGGREGATE);
+ wfunc->agg_filter = agg_filter;
wfunc->location = location;
/*
***************
*** 482,487 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 491,506 ----
NameListToString(funcname)),
parser_errposition(pstate, location)));
+ /*
+ * Reject window functions which are not aggregates in the
+ * case of FILTER.
+ */
+ if (!wfunc->winagg && agg_filter)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("FILTER is not implemented in non-aggregate window functions",
+ parser_errposition(pstate, location))));
+
/*
* ordered aggs not allowed in windows yet
*/
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 7419,7425 **** get_agg_expr(Aggref *aggref, deparse_context *context)
--- 7419,7433 ----
appendStringInfoString(buf, " ORDER BY ");
get_rule_orderby(aggref->aggorder, aggref->args, false, context);
}
+
+ if (aggref->agg_filter != NULL)
+ {
+ appendStringInfoString(buf, ") FILTER (WHERE ");
+ get_rule_expr((Node *)aggref->agg_filter, context, false);
+ }
+
appendStringInfoChar(buf, ')');
+
}
/*
***************
*** 7456,7461 **** get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context)
--- 7464,7476 ----
appendStringInfoChar(buf, '*');
else
get_rule_expr((Node *) wfunc->args, context, true);
+
+ if (wfunc->agg_filter != NULL)
+ {
+ appendStringInfoString(buf, ") FILTER (WHERE ");
+ get_rule_expr((Node *)wfunc->agg_filter, context, false);
+ }
+
appendStringInfoString(buf, ") OVER ");
foreach(l, context->windowClause)
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 579,584 **** typedef struct AggrefExprState
--- 579,585 ----
{
ExprState xprstate;
List *args; /* states of argument expressions */
+ Expr *agg_filter; /* FILTER expression */
int aggno; /* ID number for agg within its plan node */
} AggrefExprState;
***************
*** 590,595 **** typedef struct WindowFuncExprState
--- 591,597 ----
{
ExprState xprstate;
List *args; /* states of argument expressions */
+ Expr *agg_filter; /* FILTER expression */
int wfuncno; /* ID number for wfunc within its plan node */
} WindowFuncExprState;
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 295,300 **** typedef struct FuncCall
--- 295,301 ----
bool agg_star; /* argument was really '*' */
bool agg_distinct; /* arguments were labeled DISTINCT */
bool func_variadic; /* last argument was labeled VARIADIC */
+ Node *agg_filter; /* FILTER clause, if any */
struct WindowDef *over; /* OVER clause, if any */
int location; /* token location, or -1 if unknown */
} FuncCall;
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
***************
*** 241,246 **** typedef struct Aggref
--- 241,247 ----
List *args; /* arguments and sort expressions */
List *aggorder; /* ORDER BY (list of SortGroupClause) */
List *aggdistinct; /* DISTINCT (list of SortGroupClause) */
+ Expr *agg_filter; /* FILTER expression */
bool aggstar; /* TRUE if argument list was really '*' */
Index agglevelsup; /* > 0 if agg belongs to outer query */
int location; /* token location, or -1 if unknown */
***************
*** 257,262 **** typedef struct WindowFunc
--- 258,264 ----
Oid wincollid; /* OID of collation of result */
Oid inputcollid; /* OID of collation that function should use */
List *args; /* arguments to the window function */
+ Expr *agg_filter; /* FILTER expression */
Index winref; /* index of associated WindowClause */
bool winstar; /* TRUE if argument list was really '*' */
bool winagg; /* is function a simple aggregate? */
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 155,160 **** PG_KEYWORD("extract", EXTRACT, COL_NAME_KEYWORD)
--- 155,161 ----
PG_KEYWORD("false", FALSE_P, RESERVED_KEYWORD)
PG_KEYWORD("family", FAMILY, UNRESERVED_KEYWORD)
PG_KEYWORD("fetch", FETCH, RESERVED_KEYWORD)
+ PG_KEYWORD("filter", FILTER, RESERVED_KEYWORD)
PG_KEYWORD("first", FIRST_P, UNRESERVED_KEYWORD)
PG_KEYWORD("float", FLOAT_P, COL_NAME_KEYWORD)
PG_KEYWORD("following", FOLLOWING, UNRESERVED_KEYWORD)
*** a/src/include/parser/parse_func.h
--- b/src/include/parser/parse_func.h
***************
*** 46,52 **** extern Node *ParseFuncOrColumn(ParseState *pstate,
List *funcname, List *fargs,
List *agg_order, bool agg_star, bool agg_distinct,
bool func_variadic,
! WindowDef *over, bool is_column, int location);
extern FuncDetailCode func_get_detail(List *funcname,
List *fargs, List *fargnames,
--- 46,52 ----
List *funcname, List *fargs,
List *agg_order, bool agg_star, bool agg_distinct,
bool func_variadic,
! Expr *agg_filter, WindowDef *over, bool is_column, int location);
extern FuncDetailCode func_get_detail(List *funcname,
List *fargs, List *fargnames,
*** a/src/include/parser/parse_node.h
--- b/src/include/parser/parse_node.h
***************
*** 39,44 **** typedef enum ParseExprKind
--- 39,45 ----
EXPR_KIND_FROM_FUNCTION, /* function in FROM clause */
EXPR_KIND_WHERE, /* WHERE */
EXPR_KIND_HAVING, /* HAVING */
+ EXPR_KIND_FILTER, /* FILTER */
EXPR_KIND_WINDOW_PARTITION, /* window definition PARTITION BY */
EXPR_KIND_WINDOW_ORDER, /* window definition ORDER BY */
EXPR_KIND_WINDOW_FRAME_RANGE, /* window frame clause with RANGE */
On Wed, Feb 13, 2013 at 06:45:31AM -0800, David Fetter wrote:
On Sat, Feb 09, 2013 at 11:59:22PM -0800, David Fetter wrote:
Folks,
Per suggestions and lots of help from Andrew Gierth, please find
attached a patch to clean up the call sites for FuncCall nodes, which
I'd like to expand centrally rather than in each of the 37 (or 38, but
I only redid 37) places where it's called. The remaining one is in
src/backend/nodes/copyfuncs.c, which has to be modified for any
changes in the that struct anyhow.The immediate purpose is two-fold: to reduce some redundancies, which
I believe is worth doing in and of itself, and to prepare for adding
FILTER on aggregates from the spec, and possibly other things in
the <aggregate function> part of the spec.Cheers,
David.Folks,
Please find attached two versions of a patch which provides optional
FILTER clause for aggregates (T612, "Advanced OLAP operations").The first is intended to be applied on top of the previous patch, the
second without it.
I'll find a brown paper back to wear over my head at some point, but
meanwhile, here's a cleaned-up version of the patch that doesn't use
makeFuncArgs, now without merge artifacts and with the ability to
actually compile. It's still WIP in the sense previously mentioned.
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
Attachments:
filter_no_mfa_002.difftext/plain; charset=us-asciiDownload
*** a/src/backend/executor/execQual.c
--- b/src/backend/executor/execQual.c
***************
*** 4395,4400 **** ExecInitExpr(Expr *node, PlanState *parent)
--- 4395,4401 ----
astate->args = (List *) ExecInitExpr((Expr *) aggref->args,
parent);
+ astate->agg_filter = ExecInitExpr(aggref->agg_filter, parent);
/*
* Complain if the aggregate's arguments contain any
***************
*** 4433,4438 **** ExecInitExpr(Expr *node, PlanState *parent)
--- 4434,4440 ----
wfstate->args = (List *) ExecInitExpr((Expr *) wfunc->args,
parent);
+ wfstate->agg_filter = ExecInitExpr(wfunc->agg_filter, parent);
/*
* Complain if the windowfunc's arguments contain any
*** a/src/backend/executor/functions.c
--- b/src/backend/executor/functions.c
***************
*** 364,370 **** sql_fn_post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var)
list_make1(subfield),
list_make1(param),
NIL, false, false, false,
! NULL, true, cref->location);
}
return param;
--- 364,370 ----
list_make1(subfield),
list_make1(param),
NIL, false, false, false,
! NULL, NULL, true, cref->location);
}
return param;
*** a/src/backend/executor/nodeAgg.c
--- b/src/backend/executor/nodeAgg.c
***************
*** 487,492 **** advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
--- 487,504 ----
int i;
TupleTableSlot *slot;
+ /* Skip anything FILTERed out */
+ ExprState *filter = peraggstate->aggrefstate->agg_filter;
+ if (filter)
+ {
+ MemoryContext oldcontext = MemoryContextSwitchTo(aggstate->tmpcontext->ecxt_per_tuple_memory);
+ bool isnull;
+ Datum res = ExecEvalExpr(filter, aggstate->tmpcontext, &isnull, NULL);
+ MemoryContextSwitchTo(oldcontext);
+ if (isnull || !DatumGetBool(res))
+ continue;
+ }
+
/* Evaluate the current input expressions for this aggregate */
slot = ExecProject(peraggstate->evalproj, NULL);
*** a/src/backend/executor/nodeWindowAgg.c
--- b/src/backend/executor/nodeWindowAgg.c
***************
*** 226,234 **** advance_windowaggregate(WindowAggState *winstate,
--- 226,247 ----
int i;
MemoryContext oldContext;
ExprContext *econtext = winstate->tmpcontext;
+ ExprState *filter = wfuncstate->agg_filter;
oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+ /* Skip anything FILTERed out */
+ if (filter)
+ {
+ bool isnull;
+ Datum res = ExecEvalExpr(filter, econtext, &isnull, NULL);
+ if (isnull || !DatumGetBool(res))
+ {
+ MemoryContextSwitchTo(oldContext);
+ return;
+ }
+ }
+
/* We start from 1, since the 0th arg will be the transition value */
i = 1;
foreach(arg, wfuncstate->args)
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 1135,1140 **** _copyAggref(const Aggref *from)
--- 1135,1141 ----
COPY_NODE_FIELD(args);
COPY_NODE_FIELD(aggorder);
COPY_NODE_FIELD(aggdistinct);
+ COPY_NODE_FIELD(agg_filter);
COPY_SCALAR_FIELD(aggstar);
COPY_SCALAR_FIELD(agglevelsup);
COPY_LOCATION_FIELD(location);
***************
*** 1155,1160 **** _copyWindowFunc(const WindowFunc *from)
--- 1156,1162 ----
COPY_SCALAR_FIELD(wincollid);
COPY_SCALAR_FIELD(inputcollid);
COPY_NODE_FIELD(args);
+ COPY_NODE_FIELD(agg_filter);
COPY_SCALAR_FIELD(winref);
COPY_SCALAR_FIELD(winstar);
COPY_SCALAR_FIELD(winagg);
***************
*** 2153,2158 **** _copyFuncCall(const FuncCall *from)
--- 2155,2161 ----
COPY_SCALAR_FIELD(agg_star);
COPY_SCALAR_FIELD(agg_distinct);
COPY_SCALAR_FIELD(func_variadic);
+ COPY_NODE_FIELD(agg_filter);
COPY_NODE_FIELD(over);
COPY_LOCATION_FIELD(location);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 195,200 **** _equalAggref(const Aggref *a, const Aggref *b)
--- 195,201 ----
COMPARE_NODE_FIELD(args);
COMPARE_NODE_FIELD(aggorder);
COMPARE_NODE_FIELD(aggdistinct);
+ COMPARE_NODE_FIELD(agg_filter);
COMPARE_SCALAR_FIELD(aggstar);
COMPARE_SCALAR_FIELD(agglevelsup);
COMPARE_LOCATION_FIELD(location);
***************
*** 210,215 **** _equalWindowFunc(const WindowFunc *a, const WindowFunc *b)
--- 211,217 ----
COMPARE_SCALAR_FIELD(wincollid);
COMPARE_SCALAR_FIELD(inputcollid);
COMPARE_NODE_FIELD(args);
+ COMPARE_NODE_FIELD(agg_filter);
COMPARE_SCALAR_FIELD(winref);
COMPARE_SCALAR_FIELD(winstar);
COMPARE_SCALAR_FIELD(winagg);
***************
*** 1997,2002 **** _equalFuncCall(const FuncCall *a, const FuncCall *b)
--- 1999,2005 ----
COMPARE_SCALAR_FIELD(agg_star);
COMPARE_SCALAR_FIELD(agg_distinct);
COMPARE_SCALAR_FIELD(func_variadic);
+ COMPARE_NODE_FIELD(agg_filter);
COMPARE_NODE_FIELD(over);
COMPARE_LOCATION_FIELD(location);
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
***************
*** 1570,1575 **** expression_tree_walker(Node *node,
--- 1570,1578 ----
if (expression_tree_walker((Node *) expr->aggdistinct,
walker, context))
return true;
+ if (expression_tree_walker((Node *) expr->agg_filter,
+ walker, context))
+ return true;
}
break;
case T_WindowFunc:
***************
*** 1580,1585 **** expression_tree_walker(Node *node,
--- 1583,1591 ----
if (expression_tree_walker((Node *) expr->args,
walker, context))
return true;
+ if (expression_tree_walker((Node *) expr->agg_filter,
+ walker, context))
+ return true;
}
break;
case T_ArrayRef:
***************
*** 2079,2084 **** expression_tree_mutator(Node *node,
--- 2085,2091 ----
MUTATE(newnode->args, aggref->args, List *);
MUTATE(newnode->aggorder, aggref->aggorder, List *);
MUTATE(newnode->aggdistinct, aggref->aggdistinct, List *);
+ MUTATE(newnode->agg_filter, aggref->agg_filter, Expr *);
return (Node *) newnode;
}
break;
***************
*** 2089,2094 **** expression_tree_mutator(Node *node,
--- 2096,2102 ----
FLATCOPY(newnode, wfunc, WindowFunc);
MUTATE(newnode->args, wfunc->args, List *);
+ MUTATE(newnode->agg_filter, wfunc->agg_filter, Expr *);
return (Node *) newnode;
}
break;
***************
*** 2948,2953 **** raw_expression_tree_walker(Node *node,
--- 2956,2963 ----
return true;
if (walker(fcall->agg_order, context))
return true;
+ if (walker(fcall->agg_filter, context))
+ return true;
if (walker(fcall->over, context))
return true;
/* function name is deemed uninteresting */
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 956,961 **** _outAggref(StringInfo str, const Aggref *node)
--- 956,962 ----
WRITE_NODE_FIELD(args);
WRITE_NODE_FIELD(aggorder);
WRITE_NODE_FIELD(aggdistinct);
+ WRITE_NODE_FIELD(agg_filter);
WRITE_BOOL_FIELD(aggstar);
WRITE_UINT_FIELD(agglevelsup);
WRITE_LOCATION_FIELD(location);
***************
*** 971,976 **** _outWindowFunc(StringInfo str, const WindowFunc *node)
--- 972,978 ----
WRITE_OID_FIELD(wincollid);
WRITE_OID_FIELD(inputcollid);
WRITE_NODE_FIELD(args);
+ WRITE_NODE_FIELD(agg_filter);
WRITE_UINT_FIELD(winref);
WRITE_BOOL_FIELD(winstar);
WRITE_BOOL_FIELD(winagg);
***************
*** 2081,2086 **** _outFuncCall(StringInfo str, const FuncCall *node)
--- 2083,2089 ----
WRITE_BOOL_FIELD(agg_star);
WRITE_BOOL_FIELD(agg_distinct);
WRITE_BOOL_FIELD(func_variadic);
+ WRITE_NODE_FIELD(agg_filter);
WRITE_NODE_FIELD(over);
WRITE_LOCATION_FIELD(location);
}
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 478,483 **** _readAggref(void)
--- 478,484 ----
READ_NODE_FIELD(args);
READ_NODE_FIELD(aggorder);
READ_NODE_FIELD(aggdistinct);
+ READ_NODE_FIELD(agg_filter);
READ_BOOL_FIELD(aggstar);
READ_UINT_FIELD(agglevelsup);
READ_LOCATION_FIELD(location);
***************
*** 498,503 **** _readWindowFunc(void)
--- 499,505 ----
READ_OID_FIELD(wincollid);
READ_OID_FIELD(inputcollid);
READ_NODE_FIELD(args);
+ READ_NODE_FIELD(agg_filter);
READ_UINT_FIELD(winref);
READ_BOOL_FIELD(winstar);
READ_BOOL_FIELD(winagg);
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 491,496 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
--- 491,497 ----
opt_frame_clause frame_extent frame_bound
%type <str> opt_existing_window_name
%type <boolean> opt_if_not_exists
+ %type <node> filter_clause
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
***************
*** 537,543 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
EXTENSION EXTERNAL EXTRACT
! FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
FREEZE FROM FULL FUNCTION FUNCTIONS
GLOBAL GRANT GRANTED GREATEST GROUP_P
--- 538,544 ----
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
EXTENSION EXTERNAL EXTRACT
! FALSE_P FAMILY FETCH FILTER FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
FREEZE FROM FULL FUNCTION FUNCTIONS
GLOBAL GRANT GRANTED GREATEST GROUP_P
***************
*** 10944,10950 **** c_expr: columnref { $$ = $1; }
* (Note that many of the special SQL functions wouldn't actually make any
* sense as functional index entries, but we ignore that consideration here.)
*/
! func_expr: func_name '(' ')' over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
--- 10945,10951 ----
* (Note that many of the special SQL functions wouldn't actually make any
* sense as functional index entries, but we ignore that consideration here.)
*/
! func_expr: func_name '(' ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
***************
*** 10953,10976 **** func_expr: func_name '(' ')' over_clause
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
! n->over = $4;
! n->location = @1;
! $$ = (Node *)n;
! }
! | func_name '(' func_arg_list ')' over_clause
! {
! FuncCall *n = makeNode(FuncCall);
! n->funcname = $1;
! n->args = $3;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
n->over = $5;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' VARIADIC func_arg_expr ')' over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
--- 10954,10979 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
! n->agg_filter = $4;
n->over = $5;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' func_arg_list ')' filter_clause over_clause
! {
! FuncCall *n = makeNode(FuncCall);
! n->funcname = $1;
! n->args = $3;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->agg_filter = $5;
! n->over = $6;
! n->location = @1;
! $$ = (Node *)n;
! }
! | func_name '(' VARIADIC func_arg_expr ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
***************
*** 10979,10989 **** func_expr: func_name '(' ')' over_clause
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = TRUE;
! n->over = $6;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' func_arg_list ',' VARIADIC func_arg_expr ')' over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
--- 10982,10993 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = TRUE;
! n->agg_filter = $6;
! n->over = $7;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' func_arg_list ',' VARIADIC func_arg_expr ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
***************
*** 10992,11002 **** func_expr: func_name '(' ')' over_clause
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = TRUE;
! n->over = $8;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' func_arg_list sort_clause ')' over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
--- 10996,11007 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = TRUE;
! n->agg_filter = $8;
! n->over = $9;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' func_arg_list sort_clause ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
***************
*** 11005,11015 **** func_expr: func_name '(' ')' over_clause
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
! n->over = $6;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' ALL func_arg_list opt_sort_clause ')' over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
--- 11010,11021 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
! n->agg_filter = $6;
! n->over = $7;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' ALL func_arg_list opt_sort_clause ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
***************
*** 11022,11032 **** func_expr: func_name '(' ')' over_clause
* for that in FuncCall at the moment.
*/
n->func_variadic = FALSE;
! n->over = $7;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' DISTINCT func_arg_list opt_sort_clause ')' over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
--- 11028,11039 ----
* for that in FuncCall at the moment.
*/
n->func_variadic = FALSE;
! n->agg_filter = $7;
! n->over = $8;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' DISTINCT func_arg_list opt_sort_clause ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
***************
*** 11035,11045 **** func_expr: func_name '(' ')' over_clause
n->agg_star = FALSE;
n->agg_distinct = TRUE;
n->func_variadic = FALSE;
! n->over = $7;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' '*' ')' over_clause
{
/*
* We consider AGGREGATE(*) to invoke a parameterless
--- 11042,11053 ----
n->agg_star = FALSE;
n->agg_distinct = TRUE;
n->func_variadic = FALSE;
! n->agg_filter = $7;
! n->over = $8;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' '*' ')' filter_clause over_clause
{
/*
* We consider AGGREGATE(*) to invoke a parameterless
***************
*** 11058,11064 **** func_expr: func_name '(' ')' over_clause
n->agg_star = TRUE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
! n->over = $5;
n->location = @1;
$$ = (Node *)n;
}
--- 11066,11073 ----
n->agg_star = TRUE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
! n->agg_filter = $5;
! n->over = $6;
n->location = @1;
$$ = (Node *)n;
}
***************
*** 11592,11598 **** xmlexists_argument:
window_clause:
WINDOW window_definition_list { $$ = $2; }
| /*EMPTY*/ { $$ = NIL; }
! ;
window_definition_list:
window_definition { $$ = list_make1($1); }
--- 11601,11607 ----
window_clause:
WINDOW window_definition_list { $$ = $2; }
| /*EMPTY*/ { $$ = NIL; }
! ;
window_definition_list:
window_definition { $$ = list_make1($1); }
***************
*** 11609,11614 **** window_definition:
--- 11618,11628 ----
}
;
+ filter_clause:
+ FILTER '(' WHERE a_expr ')' { $$ = $4; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
over_clause: OVER window_specification
{ $$ = $2; }
| OVER ColId
***************
*** 12885,12890 **** reserved_keyword:
--- 12899,12905 ----
| EXCEPT
| FALSE_P
| FETCH
+ | FILTER
| FOR
| FOREIGN
| FROM
*** a/src/backend/parser/parse_agg.c
--- b/src/backend/parser/parse_agg.c
***************
*** 44,50 **** typedef struct
int sublevels_up;
} check_ungrouped_columns_context;
! static int check_agg_arguments(ParseState *pstate, List *args);
static bool check_agg_arguments_walker(Node *node,
check_agg_arguments_context *context);
static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
--- 44,50 ----
int sublevels_up;
} check_ungrouped_columns_context;
! static int check_agg_arguments(ParseState *pstate, List *args, Expr *filter);
static bool check_agg_arguments_walker(Node *node,
check_agg_arguments_context *context);
static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
***************
*** 160,166 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
* Check the arguments to compute the aggregate's level and detect
* improper nesting.
*/
! min_varlevel = check_agg_arguments(pstate, agg->args);
agg->agglevelsup = min_varlevel;
/* Mark the correct pstate level as having aggregates */
--- 160,166 ----
* Check the arguments to compute the aggregate's level and detect
* improper nesting.
*/
! min_varlevel = check_agg_arguments(pstate, agg->args, agg->agg_filter);
agg->agglevelsup = min_varlevel;
/* Mark the correct pstate level as having aggregates */
***************
*** 207,212 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
--- 207,215 ----
case EXPR_KIND_HAVING:
/* okay */
break;
+ case EXPR_KIND_FILTER:
+ errkind = true;
+ break;
case EXPR_KIND_WINDOW_PARTITION:
/* okay */
break;
***************
*** 309,315 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
* which we can't know until we finish scanning the arguments.
*/
static int
! check_agg_arguments(ParseState *pstate, List *args)
{
int agglevel;
check_agg_arguments_context context;
--- 312,318 ----
* which we can't know until we finish scanning the arguments.
*/
static int
! check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
{
int agglevel;
check_agg_arguments_context context;
***************
*** 323,328 **** check_agg_arguments(ParseState *pstate, List *args)
--- 326,335 ----
check_agg_arguments_walker,
(void *) &context);
+ (void) expression_tree_walker((Node *) filter,
+ check_agg_arguments_walker,
+ (void *) &context);
+
/*
* If we found no vars nor aggs at all, it's a level-zero aggregate;
* otherwise, its level is the minimum of vars or aggs.
***************
*** 481,486 **** transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
--- 488,496 ----
case EXPR_KIND_HAVING:
errkind = true;
break;
+ case EXPR_KIND_FILTER:
+ errkind = true;
+ break;
case EXPR_KIND_WINDOW_PARTITION:
case EXPR_KIND_WINDOW_ORDER:
case EXPR_KIND_WINDOW_FRAME_RANGE:
*** a/src/backend/parser/parse_expr.c
--- b/src/backend/parser/parse_expr.c
***************
*** 22,27 ****
--- 22,28 ----
#include "nodes/nodeFuncs.h"
#include "optimizer/var.h"
#include "parser/analyze.h"
+ #include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
***************
*** 463,469 **** transformIndirection(ParseState *pstate, Node *basenode, List *indirection)
list_make1(n),
list_make1(result),
NIL, false, false, false,
! NULL, true, location);
if (newresult == NULL)
unknown_attribute(pstate, result, strVal(n), location);
result = newresult;
--- 464,470 ----
list_make1(n),
list_make1(result),
NIL, false, false, false,
! NULL, NULL, true, location);
if (newresult == NULL)
unknown_attribute(pstate, result, strVal(n), location);
result = newresult;
***************
*** 631,637 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, true, cref->location);
}
break;
}
--- 632,638 ----
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, NULL, true, cref->location);
}
break;
}
***************
*** 676,682 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, true, cref->location);
}
break;
}
--- 677,683 ----
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, NULL, true, cref->location);
}
break;
}
***************
*** 734,740 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, true, cref->location);
}
break;
}
--- 735,741 ----
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, NULL, true, cref->location);
}
break;
}
***************
*** 1241,1246 **** transformFuncCall(ParseState *pstate, FuncCall *fn)
--- 1242,1248 ----
{
List *targs;
ListCell *args;
+ Expr *tagg_filter;
/* Transform the list of arguments ... */
targs = NIL;
***************
*** 1250,1255 **** transformFuncCall(ParseState *pstate, FuncCall *fn)
--- 1252,1263 ----
(Node *) lfirst(args)));
}
+ /* Transform the aggregate filter using transformWhereClause, to
+ * which FILTER is virually identical... */
+ tagg_filter = NULL;
+ if (fn->agg_filter != NULL)
+ tagg_filter = (Expr *)transformWhereClause(pstate, (Node *)fn->agg_filter, EXPR_KIND_FILTER, "FILTER");
+
/* ... and hand off to ParseFuncOrColumn */
return ParseFuncOrColumn(pstate,
fn->funcname,
***************
*** 1258,1263 **** transformFuncCall(ParseState *pstate, FuncCall *fn)
--- 1266,1272 ----
fn->agg_star,
fn->agg_distinct,
fn->func_variadic,
+ tagg_filter,
fn->over,
false,
fn->location);
***************
*** 1430,1435 **** transformSubLink(ParseState *pstate, SubLink *sublink)
--- 1439,1445 ----
case EXPR_KIND_FROM_FUNCTION:
case EXPR_KIND_WHERE:
case EXPR_KIND_HAVING:
+ case EXPR_KIND_FILTER:
case EXPR_KIND_WINDOW_PARTITION:
case EXPR_KIND_WINDOW_ORDER:
case EXPR_KIND_WINDOW_FRAME_RANGE:
***************
*** 2579,2584 **** ParseExprKindName(ParseExprKind exprKind)
--- 2589,2596 ----
return "WHERE";
case EXPR_KIND_HAVING:
return "HAVING";
+ case EXPR_KIND_FILTER:
+ return "FILTER";
case EXPR_KIND_WINDOW_PARTITION:
return "window PARTITION BY";
case EXPR_KIND_WINDOW_ORDER:
*** a/src/backend/parser/parse_func.c
--- b/src/backend/parser/parse_func.c
***************
*** 63,69 **** Node *
ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
List *agg_order, bool agg_star, bool agg_distinct,
bool func_variadic,
! WindowDef *over, bool is_column, int location)
{
Oid rettype;
Oid funcid;
--- 63,69 ----
ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
List *agg_order, bool agg_star, bool agg_distinct,
bool func_variadic,
! Expr *agg_filter, WindowDef *over, bool is_column, int location)
{
Oid rettype;
Oid funcid;
***************
*** 175,181 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
* wasn't any aggregate or variadic decoration, nor an argument name.
*/
if (nargs == 1 && agg_order == NIL && !agg_star && !agg_distinct &&
! over == NULL && !func_variadic && argnames == NIL &&
list_length(funcname) == 1)
{
Oid argtype = actual_arg_types[0];
--- 175,181 ----
* wasn't any aggregate or variadic decoration, nor an argument name.
*/
if (nargs == 1 && agg_order == NIL && !agg_star && !agg_distinct &&
! agg_filter == NULL && over == NULL && !func_variadic && argnames == NIL &&
list_length(funcname) == 1)
{
Oid argtype = actual_arg_types[0];
***************
*** 251,256 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 251,262 ----
errmsg("ORDER BY specified, but %s is not an aggregate function",
NameListToString(funcname)),
parser_errposition(pstate, location)));
+ if (agg_filter)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("FILTER specified, but %s is not an aggregate function",
+ NameListToString(funcname)),
+ parser_errposition(pstate, location)));
if (over)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
***************
*** 402,407 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 408,415 ----
/* aggcollid and inputcollid will be set by parse_collate.c */
/* args, aggorder, aggdistinct will be set by transformAggregateCall */
aggref->aggstar = agg_star;
+ /* filter */
+ aggref->agg_filter = agg_filter;
/* agglevelsup will be set by transformAggregateCall */
aggref->location = location;
***************
*** 460,465 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 468,474 ----
/* winref will be set by transformWindowFuncCall */
wfunc->winstar = agg_star;
wfunc->winagg = (fdresult == FUNCDETAIL_AGGREGATE);
+ wfunc->agg_filter = agg_filter;
wfunc->location = location;
/*
***************
*** 482,487 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 491,506 ----
NameListToString(funcname)),
parser_errposition(pstate, location)));
+ /*
+ * Reject window functions which are not aggregates in the
+ * case of FILTER.
+ */
+ if (!wfunc->winagg && agg_filter)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("FILTER is not implemented in non-aggregate window functions",
+ parser_errposition(pstate, location))));
+
/*
* ordered aggs not allowed in windows yet
*/
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 7419,7425 **** get_agg_expr(Aggref *aggref, deparse_context *context)
--- 7419,7433 ----
appendStringInfoString(buf, " ORDER BY ");
get_rule_orderby(aggref->aggorder, aggref->args, false, context);
}
+
+ if (aggref->agg_filter != NULL)
+ {
+ appendStringInfoString(buf, ") FILTER (WHERE ");
+ get_rule_expr((Node *)aggref->agg_filter, context, false);
+ }
+
appendStringInfoChar(buf, ')');
+
}
/*
***************
*** 7456,7461 **** get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context)
--- 7464,7476 ----
appendStringInfoChar(buf, '*');
else
get_rule_expr((Node *) wfunc->args, context, true);
+
+ if (wfunc->agg_filter != NULL)
+ {
+ appendStringInfoString(buf, ") FILTER (WHERE ");
+ get_rule_expr((Node *)wfunc->agg_filter, context, false);
+ }
+
appendStringInfoString(buf, ") OVER ");
foreach(l, context->windowClause)
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 579,584 **** typedef struct AggrefExprState
--- 579,585 ----
{
ExprState xprstate;
List *args; /* states of argument expressions */
+ Expr *agg_filter; /* FILTER expression */
int aggno; /* ID number for agg within its plan node */
} AggrefExprState;
***************
*** 590,595 **** typedef struct WindowFuncExprState
--- 591,597 ----
{
ExprState xprstate;
List *args; /* states of argument expressions */
+ Expr *agg_filter; /* FILTER expression */
int wfuncno; /* ID number for wfunc within its plan node */
} WindowFuncExprState;
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 295,300 **** typedef struct FuncCall
--- 295,301 ----
bool agg_star; /* argument was really '*' */
bool agg_distinct; /* arguments were labeled DISTINCT */
bool func_variadic; /* last argument was labeled VARIADIC */
+ Node *agg_filter; /* FILTER clause, if any */
struct WindowDef *over; /* OVER clause, if any */
int location; /* token location, or -1 if unknown */
} FuncCall;
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
***************
*** 241,246 **** typedef struct Aggref
--- 241,247 ----
List *args; /* arguments and sort expressions */
List *aggorder; /* ORDER BY (list of SortGroupClause) */
List *aggdistinct; /* DISTINCT (list of SortGroupClause) */
+ Expr *agg_filter; /* FILTER expression */
bool aggstar; /* TRUE if argument list was really '*' */
Index agglevelsup; /* > 0 if agg belongs to outer query */
int location; /* token location, or -1 if unknown */
***************
*** 257,262 **** typedef struct WindowFunc
--- 258,264 ----
Oid wincollid; /* OID of collation of result */
Oid inputcollid; /* OID of collation that function should use */
List *args; /* arguments to the window function */
+ Expr *agg_filter; /* FILTER expression */
Index winref; /* index of associated WindowClause */
bool winstar; /* TRUE if argument list was really '*' */
bool winagg; /* is function a simple aggregate? */
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 155,160 **** PG_KEYWORD("extract", EXTRACT, COL_NAME_KEYWORD)
--- 155,161 ----
PG_KEYWORD("false", FALSE_P, RESERVED_KEYWORD)
PG_KEYWORD("family", FAMILY, UNRESERVED_KEYWORD)
PG_KEYWORD("fetch", FETCH, RESERVED_KEYWORD)
+ PG_KEYWORD("filter", FILTER, RESERVED_KEYWORD)
PG_KEYWORD("first", FIRST_P, UNRESERVED_KEYWORD)
PG_KEYWORD("float", FLOAT_P, COL_NAME_KEYWORD)
PG_KEYWORD("following", FOLLOWING, UNRESERVED_KEYWORD)
*** a/src/include/parser/parse_func.h
--- b/src/include/parser/parse_func.h
***************
*** 46,52 **** extern Node *ParseFuncOrColumn(ParseState *pstate,
List *funcname, List *fargs,
List *agg_order, bool agg_star, bool agg_distinct,
bool func_variadic,
! WindowDef *over, bool is_column, int location);
extern FuncDetailCode func_get_detail(List *funcname,
List *fargs, List *fargnames,
--- 46,52 ----
List *funcname, List *fargs,
List *agg_order, bool agg_star, bool agg_distinct,
bool func_variadic,
! Expr *agg_filter, WindowDef *over, bool is_column, int location);
extern FuncDetailCode func_get_detail(List *funcname,
List *fargs, List *fargnames,
*** a/src/include/parser/parse_node.h
--- b/src/include/parser/parse_node.h
***************
*** 39,44 **** typedef enum ParseExprKind
--- 39,45 ----
EXPR_KIND_FROM_FUNCTION, /* function in FROM clause */
EXPR_KIND_WHERE, /* WHERE */
EXPR_KIND_HAVING, /* HAVING */
+ EXPR_KIND_FILTER, /* FILTER */
EXPR_KIND_WINDOW_PARTITION, /* window definition PARTITION BY */
EXPR_KIND_WINDOW_ORDER, /* window definition ORDER BY */
EXPR_KIND_WINDOW_FRAME_RANGE, /* window frame clause with RANGE */
On Wed, Feb 13, 2013 at 06:45:31AM -0800, David Fetter wrote:
On Sat, Feb 09, 2013 at 11:59:22PM -0800, David Fetter wrote:
Folks,
Per suggestions and lots of help from Andrew Gierth, please find
attached a patch to clean up the call sites for FuncCall nodes, which
I'd like to expand centrally rather than in each of the 37 (or 38, but
I only redid 37) places where it's called. The remaining one is in
src/backend/nodes/copyfuncs.c, which has to be modified for any
changes in the that struct anyhow.The immediate purpose is two-fold: to reduce some redundancies, which
I believe is worth doing in and of itself, and to prepare for adding
FILTER on aggregates from the spec, and possibly other things in
the <aggregate function> part of the spec.Cheers,
David.Folks,
Please find attached two versions of a patch which provides optional
FILTER clause for aggregates (T612, "Advanced OLAP operations").The first is intended to be applied on top of the previous patch, the
second without it. The first is, I believe, clearer in what it's
doing. Rather than simply mechanically visiting every place a
function call might be constructed, it visits a central one to change
the default, then goes only to the places where it's relevant.The patches are both early WIP as they contain no docs or regression
tests yet.
Docs and regression tests added, makeFuncArgs approached dropped for
now, will re-visit later.
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
Attachments:
filter_no_mfa_003.difftext/plain; charset=us-asciiDownload
*** a/doc/src/sgml/ref/select.sgml
--- b/doc/src/sgml/ref/select.sgml
***************
*** 595,604 **** GROUP BY <replaceable class="parameter">expression</replaceable> [, ...]
</para>
<para>
! Aggregate functions, if any are used, are computed across all rows
making up each group, producing a separate value for each group
(whereas without <literal>GROUP BY</literal>, an aggregate
produces a single value computed across all the selected rows).
When <literal>GROUP BY</literal> is present, it is not valid for
the <command>SELECT</command> list expressions to refer to
ungrouped columns except within aggregate functions or if the
--- 595,607 ----
</para>
<para>
! In the absence of a <literal>FILTER</literal> clause,
! aggregate functions, if any are used, are computed across all rows
making up each group, producing a separate value for each group
(whereas without <literal>GROUP BY</literal>, an aggregate
produces a single value computed across all the selected rows).
+ When a <literal>FILTER</literal> clause is present, only those
+ rows matching the FILTER clause are included.
When <literal>GROUP BY</literal> is present, it is not valid for
the <command>SELECT</command> list expressions to refer to
ungrouped columns except within aggregate functions or if the
*** a/doc/src/sgml/syntax.sgml
--- b/doc/src/sgml/syntax.sgml
***************
*** 1562,1585 **** sqrt(2)
syntax of an aggregate expression is one of the following:
<synopsis>
! <replaceable>aggregate_name</replaceable> (<replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] )
! <replaceable>aggregate_name</replaceable> (ALL <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] )
! <replaceable>aggregate_name</replaceable> (DISTINCT <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] )
! <replaceable>aggregate_name</replaceable> ( * )
</synopsis>
where <replaceable>aggregate_name</replaceable> is a previously
defined aggregate (possibly qualified with a schema name),
! <replaceable>expression</replaceable> is
! any value expression that does not itself contain an aggregate
! expression or a window function call, and
! <replaceable>order_by_clause</replaceable> is a optional
! <literal>ORDER BY</> clause as described below.
</para>
<para>
! The first form of aggregate expression invokes the aggregate
! once for each input row.
The second form is the same as the first, since
<literal>ALL</literal> is the default.
The third form invokes the aggregate once for each distinct value
--- 1562,1587 ----
syntax of an aggregate expression is one of the following:
<synopsis>
! <replaceable>aggregate_name</replaceable> (<replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
! <replaceable>aggregate_name</replaceable> (ALL <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
! <replaceable>aggregate_name</replaceable> (DISTINCT <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
! <replaceable>aggregate_name</replaceable> ( * ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
</synopsis>
where <replaceable>aggregate_name</replaceable> is a previously
defined aggregate (possibly qualified with a schema name),
! <replaceable>expression</replaceable> is any value expression that
! does not itself contain an aggregate expression or a window
! function call, <replaceable>order_by_clause</replaceable> is a
! optional <literal>ORDER BY</> clause as described below. The
! <replaceable>aggregate_name</replaceable> can also be suffixed
! with <literal>FILTER</literal> as described below.
</para>
<para>
! The first form of aggregate expression invokes the aggregate once
! for each input row, or when a FILTER clause is present, each row
! matching same.
The second form is the same as the first, since
<literal>ALL</literal> is the default.
The third form invokes the aggregate once for each distinct value
***************
*** 1607,1612 **** sqrt(2)
--- 1609,1629 ----
</para>
<para>
+ Adding a FILTER clause to an aggregate specifies which values of
+ the expression being aggregated to evaluate. For example:
+ <programlisting>
+ SELECT
+ count(*) AS unfiltered,
+ count(*) FILTER (WHERE i < 5) AS filtered
+ FROM generate_series(1,10) AS s(i);
+ unfiltered | filtered
+ ------------+----------
+ 10 | 4
+ (1 row)
+ </programlisting>
+ </para>
+
+ <para>
Ordinarily, the input rows are fed to the aggregate function in an
unspecified order. In many cases this does not matter; for example,
<function>min</> produces the same result no matter what order it
***************
*** 1709,1718 **** SELECT string_agg(a ORDER BY a, ',') FROM table; -- incorrect
The syntax of a window function call is one of the following:
<synopsis>
! <replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) OVER ( <replaceable class="parameter">window_definition</replaceable> )
! <replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) OVER <replaceable>window_name</replaceable>
! <replaceable>function_name</replaceable> ( * ) OVER ( <replaceable class="parameter">window_definition</replaceable> )
! <replaceable>function_name</replaceable> ( * ) OVER <replaceable>window_name</replaceable>
</synopsis>
where <replaceable class="parameter">window_definition</replaceable>
has the syntax
--- 1726,1735 ----
The syntax of a window function call is one of the following:
<synopsis>
! <replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER ( <replaceable class="parameter">window_definition</replaceable> )
! <replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER <replaceable>window_name</replaceable>
! <replaceable>function_name</replaceable> ( * ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER ( <replaceable class="parameter">window_definition</replaceable> )
! <replaceable>function_name</replaceable> ( * ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER <replaceable>window_name</replaceable>
</synopsis>
where <replaceable class="parameter">window_definition</replaceable>
has the syntax
***************
*** 1836,1851 **** UNBOUNDED FOLLOWING
The built-in window functions are described in <xref
linkend="functions-window-table">. Other window functions can be added by
the user. Also, any built-in or user-defined aggregate function can be
! used as a window function.
</para>
<para>
! The syntaxes using <literal>*</> are used for calling parameter-less
! aggregate functions as window functions, for example
! <literal>count(*) OVER (PARTITION BY x ORDER BY y)</>.
! The asterisk (<literal>*</>) is customarily not used for non-aggregate window functions.
! Aggregate window functions, unlike normal aggregate functions, do not
! allow <literal>DISTINCT</> or <literal>ORDER BY</> to be used within the
function argument list.
</para>
--- 1853,1870 ----
The built-in window functions are described in <xref
linkend="functions-window-table">. Other window functions can be added by
the user. Also, any built-in or user-defined aggregate function can be
! used as a window function. A <literal>FILTER</literal> clause is
! only valid for aggregate functions used in windowing.
</para>
<para>
! The syntaxes using <literal>*</> are used for calling
! parameter-less aggregate functions as window functions, for
! example <literal>count(*) OVER (PARTITION BY x ORDER BY y)</>.
! The asterisk (<literal>*</>) is customarily not used for
! non-aggregate window functions. Aggregate window functions,
! unlike normal aggregate functions, do not allow
! <literal>DISTINCT</> or <literal>ORDER BY</> to be used within the
function argument list.
</para>
*** a/src/backend/executor/execQual.c
--- b/src/backend/executor/execQual.c
***************
*** 4395,4400 **** ExecInitExpr(Expr *node, PlanState *parent)
--- 4395,4401 ----
astate->args = (List *) ExecInitExpr((Expr *) aggref->args,
parent);
+ astate->agg_filter = ExecInitExpr(aggref->agg_filter, parent);
/*
* Complain if the aggregate's arguments contain any
***************
*** 4433,4438 **** ExecInitExpr(Expr *node, PlanState *parent)
--- 4434,4440 ----
wfstate->args = (List *) ExecInitExpr((Expr *) wfunc->args,
parent);
+ wfstate->agg_filter = ExecInitExpr(wfunc->agg_filter, parent);
/*
* Complain if the windowfunc's arguments contain any
*** a/src/backend/executor/functions.c
--- b/src/backend/executor/functions.c
***************
*** 364,370 **** sql_fn_post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var)
list_make1(subfield),
list_make1(param),
NIL, false, false, false,
! NULL, true, cref->location);
}
return param;
--- 364,370 ----
list_make1(subfield),
list_make1(param),
NIL, false, false, false,
! NULL, NULL, true, cref->location);
}
return param;
*** a/src/backend/executor/nodeAgg.c
--- b/src/backend/executor/nodeAgg.c
***************
*** 487,492 **** advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
--- 487,504 ----
int i;
TupleTableSlot *slot;
+ /* Skip anything FILTERed out */
+ ExprState *filter = peraggstate->aggrefstate->agg_filter;
+ if (filter)
+ {
+ MemoryContext oldcontext = MemoryContextSwitchTo(aggstate->tmpcontext->ecxt_per_tuple_memory);
+ bool isnull;
+ Datum res = ExecEvalExpr(filter, aggstate->tmpcontext, &isnull, NULL);
+ MemoryContextSwitchTo(oldcontext);
+ if (isnull || !DatumGetBool(res))
+ continue;
+ }
+
/* Evaluate the current input expressions for this aggregate */
slot = ExecProject(peraggstate->evalproj, NULL);
*** a/src/backend/executor/nodeWindowAgg.c
--- b/src/backend/executor/nodeWindowAgg.c
***************
*** 226,234 **** advance_windowaggregate(WindowAggState *winstate,
--- 226,247 ----
int i;
MemoryContext oldContext;
ExprContext *econtext = winstate->tmpcontext;
+ ExprState *filter = wfuncstate->agg_filter;
oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+ /* Skip anything FILTERed out */
+ if (filter)
+ {
+ bool isnull;
+ Datum res = ExecEvalExpr(filter, econtext, &isnull, NULL);
+ if (isnull || !DatumGetBool(res))
+ {
+ MemoryContextSwitchTo(oldContext);
+ return;
+ }
+ }
+
/* We start from 1, since the 0th arg will be the transition value */
i = 1;
foreach(arg, wfuncstate->args)
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 1135,1140 **** _copyAggref(const Aggref *from)
--- 1135,1141 ----
COPY_NODE_FIELD(args);
COPY_NODE_FIELD(aggorder);
COPY_NODE_FIELD(aggdistinct);
+ COPY_NODE_FIELD(agg_filter);
COPY_SCALAR_FIELD(aggstar);
COPY_SCALAR_FIELD(agglevelsup);
COPY_LOCATION_FIELD(location);
***************
*** 1155,1160 **** _copyWindowFunc(const WindowFunc *from)
--- 1156,1162 ----
COPY_SCALAR_FIELD(wincollid);
COPY_SCALAR_FIELD(inputcollid);
COPY_NODE_FIELD(args);
+ COPY_NODE_FIELD(agg_filter);
COPY_SCALAR_FIELD(winref);
COPY_SCALAR_FIELD(winstar);
COPY_SCALAR_FIELD(winagg);
***************
*** 2153,2158 **** _copyFuncCall(const FuncCall *from)
--- 2155,2161 ----
COPY_SCALAR_FIELD(agg_star);
COPY_SCALAR_FIELD(agg_distinct);
COPY_SCALAR_FIELD(func_variadic);
+ COPY_NODE_FIELD(agg_filter);
COPY_NODE_FIELD(over);
COPY_LOCATION_FIELD(location);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 195,200 **** _equalAggref(const Aggref *a, const Aggref *b)
--- 195,201 ----
COMPARE_NODE_FIELD(args);
COMPARE_NODE_FIELD(aggorder);
COMPARE_NODE_FIELD(aggdistinct);
+ COMPARE_NODE_FIELD(agg_filter);
COMPARE_SCALAR_FIELD(aggstar);
COMPARE_SCALAR_FIELD(agglevelsup);
COMPARE_LOCATION_FIELD(location);
***************
*** 210,215 **** _equalWindowFunc(const WindowFunc *a, const WindowFunc *b)
--- 211,217 ----
COMPARE_SCALAR_FIELD(wincollid);
COMPARE_SCALAR_FIELD(inputcollid);
COMPARE_NODE_FIELD(args);
+ COMPARE_NODE_FIELD(agg_filter);
COMPARE_SCALAR_FIELD(winref);
COMPARE_SCALAR_FIELD(winstar);
COMPARE_SCALAR_FIELD(winagg);
***************
*** 1997,2002 **** _equalFuncCall(const FuncCall *a, const FuncCall *b)
--- 1999,2005 ----
COMPARE_SCALAR_FIELD(agg_star);
COMPARE_SCALAR_FIELD(agg_distinct);
COMPARE_SCALAR_FIELD(func_variadic);
+ COMPARE_NODE_FIELD(agg_filter);
COMPARE_NODE_FIELD(over);
COMPARE_LOCATION_FIELD(location);
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
***************
*** 1570,1575 **** expression_tree_walker(Node *node,
--- 1570,1578 ----
if (expression_tree_walker((Node *) expr->aggdistinct,
walker, context))
return true;
+ if (expression_tree_walker((Node *) expr->agg_filter,
+ walker, context))
+ return true;
}
break;
case T_WindowFunc:
***************
*** 1580,1585 **** expression_tree_walker(Node *node,
--- 1583,1591 ----
if (expression_tree_walker((Node *) expr->args,
walker, context))
return true;
+ if (expression_tree_walker((Node *) expr->agg_filter,
+ walker, context))
+ return true;
}
break;
case T_ArrayRef:
***************
*** 2079,2084 **** expression_tree_mutator(Node *node,
--- 2085,2091 ----
MUTATE(newnode->args, aggref->args, List *);
MUTATE(newnode->aggorder, aggref->aggorder, List *);
MUTATE(newnode->aggdistinct, aggref->aggdistinct, List *);
+ MUTATE(newnode->agg_filter, aggref->agg_filter, Expr *);
return (Node *) newnode;
}
break;
***************
*** 2089,2094 **** expression_tree_mutator(Node *node,
--- 2096,2102 ----
FLATCOPY(newnode, wfunc, WindowFunc);
MUTATE(newnode->args, wfunc->args, List *);
+ MUTATE(newnode->agg_filter, wfunc->agg_filter, Expr *);
return (Node *) newnode;
}
break;
***************
*** 2948,2953 **** raw_expression_tree_walker(Node *node,
--- 2956,2963 ----
return true;
if (walker(fcall->agg_order, context))
return true;
+ if (walker(fcall->agg_filter, context))
+ return true;
if (walker(fcall->over, context))
return true;
/* function name is deemed uninteresting */
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 956,961 **** _outAggref(StringInfo str, const Aggref *node)
--- 956,962 ----
WRITE_NODE_FIELD(args);
WRITE_NODE_FIELD(aggorder);
WRITE_NODE_FIELD(aggdistinct);
+ WRITE_NODE_FIELD(agg_filter);
WRITE_BOOL_FIELD(aggstar);
WRITE_UINT_FIELD(agglevelsup);
WRITE_LOCATION_FIELD(location);
***************
*** 971,976 **** _outWindowFunc(StringInfo str, const WindowFunc *node)
--- 972,978 ----
WRITE_OID_FIELD(wincollid);
WRITE_OID_FIELD(inputcollid);
WRITE_NODE_FIELD(args);
+ WRITE_NODE_FIELD(agg_filter);
WRITE_UINT_FIELD(winref);
WRITE_BOOL_FIELD(winstar);
WRITE_BOOL_FIELD(winagg);
***************
*** 2081,2086 **** _outFuncCall(StringInfo str, const FuncCall *node)
--- 2083,2089 ----
WRITE_BOOL_FIELD(agg_star);
WRITE_BOOL_FIELD(agg_distinct);
WRITE_BOOL_FIELD(func_variadic);
+ WRITE_NODE_FIELD(agg_filter);
WRITE_NODE_FIELD(over);
WRITE_LOCATION_FIELD(location);
}
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 478,483 **** _readAggref(void)
--- 478,484 ----
READ_NODE_FIELD(args);
READ_NODE_FIELD(aggorder);
READ_NODE_FIELD(aggdistinct);
+ READ_NODE_FIELD(agg_filter);
READ_BOOL_FIELD(aggstar);
READ_UINT_FIELD(agglevelsup);
READ_LOCATION_FIELD(location);
***************
*** 498,503 **** _readWindowFunc(void)
--- 499,505 ----
READ_OID_FIELD(wincollid);
READ_OID_FIELD(inputcollid);
READ_NODE_FIELD(args);
+ READ_NODE_FIELD(agg_filter);
READ_UINT_FIELD(winref);
READ_BOOL_FIELD(winstar);
READ_BOOL_FIELD(winagg);
*** a/src/backend/optimizer/plan/planagg.c
--- b/src/backend/optimizer/plan/planagg.c
***************
*** 313,319 **** find_minmax_aggs_walker(Node *node, List **context)
ListCell *l;
Assert(aggref->agglevelsup == 0);
! if (list_length(aggref->args) != 1 || aggref->aggorder != NIL)
return true; /* it couldn't be MIN/MAX */
/* note: we do not care if DISTINCT is mentioned ... */
curTarget = (TargetEntry *) linitial(aggref->args);
--- 313,319 ----
ListCell *l;
Assert(aggref->agglevelsup == 0);
! if (list_length(aggref->args) != 1 || aggref->aggorder != NIL || aggref->agg_filter != NULL)
return true; /* it couldn't be MIN/MAX */
/* note: we do not care if DISTINCT is mentioned ... */
curTarget = (TargetEntry *) linitial(aggref->args);
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 491,496 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
--- 491,497 ----
opt_frame_clause frame_extent frame_bound
%type <str> opt_existing_window_name
%type <boolean> opt_if_not_exists
+ %type <node> filter_clause
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
***************
*** 537,543 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
EXTENSION EXTERNAL EXTRACT
! FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
FREEZE FROM FULL FUNCTION FUNCTIONS
GLOBAL GRANT GRANTED GREATEST GROUP_P
--- 538,544 ----
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
EXTENSION EXTERNAL EXTRACT
! FALSE_P FAMILY FETCH FILTER FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
FREEZE FROM FULL FUNCTION FUNCTIONS
GLOBAL GRANT GRANTED GREATEST GROUP_P
***************
*** 10952,10958 **** c_expr: columnref { $$ = $1; }
* (Note that many of the special SQL functions wouldn't actually make any
* sense as functional index entries, but we ignore that consideration here.)
*/
! func_expr: func_name '(' ')' over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
--- 10953,10959 ----
* (Note that many of the special SQL functions wouldn't actually make any
* sense as functional index entries, but we ignore that consideration here.)
*/
! func_expr: func_name '(' ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
***************
*** 10961,10984 **** func_expr: func_name '(' ')' over_clause
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
! n->over = $4;
! n->location = @1;
! $$ = (Node *)n;
! }
! | func_name '(' func_arg_list ')' over_clause
! {
! FuncCall *n = makeNode(FuncCall);
! n->funcname = $1;
! n->args = $3;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
n->over = $5;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' VARIADIC func_arg_expr ')' over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
--- 10962,10987 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
! n->agg_filter = $4;
n->over = $5;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' func_arg_list ')' filter_clause over_clause
! {
! FuncCall *n = makeNode(FuncCall);
! n->funcname = $1;
! n->args = $3;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->agg_filter = $5;
! n->over = $6;
! n->location = @1;
! $$ = (Node *)n;
! }
! | func_name '(' VARIADIC func_arg_expr ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
***************
*** 10987,10997 **** func_expr: func_name '(' ')' over_clause
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = TRUE;
! n->over = $6;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' func_arg_list ',' VARIADIC func_arg_expr ')' over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
--- 10990,11001 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = TRUE;
! n->agg_filter = $6;
! n->over = $7;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' func_arg_list ',' VARIADIC func_arg_expr ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
***************
*** 11000,11010 **** func_expr: func_name '(' ')' over_clause
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = TRUE;
! n->over = $8;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' func_arg_list sort_clause ')' over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
--- 11004,11015 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = TRUE;
! n->agg_filter = $8;
! n->over = $9;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' func_arg_list sort_clause ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
***************
*** 11013,11023 **** func_expr: func_name '(' ')' over_clause
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
! n->over = $6;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' ALL func_arg_list opt_sort_clause ')' over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
--- 11018,11029 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
! n->agg_filter = $6;
! n->over = $7;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' ALL func_arg_list opt_sort_clause ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
***************
*** 11030,11040 **** func_expr: func_name '(' ')' over_clause
* for that in FuncCall at the moment.
*/
n->func_variadic = FALSE;
! n->over = $7;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' DISTINCT func_arg_list opt_sort_clause ')' over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
--- 11036,11047 ----
* for that in FuncCall at the moment.
*/
n->func_variadic = FALSE;
! n->agg_filter = $7;
! n->over = $8;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' DISTINCT func_arg_list opt_sort_clause ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
***************
*** 11043,11053 **** func_expr: func_name '(' ')' over_clause
n->agg_star = FALSE;
n->agg_distinct = TRUE;
n->func_variadic = FALSE;
! n->over = $7;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' '*' ')' over_clause
{
/*
* We consider AGGREGATE(*) to invoke a parameterless
--- 11050,11061 ----
n->agg_star = FALSE;
n->agg_distinct = TRUE;
n->func_variadic = FALSE;
! n->agg_filter = $7;
! n->over = $8;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' '*' ')' filter_clause over_clause
{
/*
* We consider AGGREGATE(*) to invoke a parameterless
***************
*** 11066,11072 **** func_expr: func_name '(' ')' over_clause
n->agg_star = TRUE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
! n->over = $5;
n->location = @1;
$$ = (Node *)n;
}
--- 11074,11081 ----
n->agg_star = TRUE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
! n->agg_filter = $5;
! n->over = $6;
n->location = @1;
$$ = (Node *)n;
}
***************
*** 11600,11606 **** xmlexists_argument:
window_clause:
WINDOW window_definition_list { $$ = $2; }
| /*EMPTY*/ { $$ = NIL; }
! ;
window_definition_list:
window_definition { $$ = list_make1($1); }
--- 11609,11615 ----
window_clause:
WINDOW window_definition_list { $$ = $2; }
| /*EMPTY*/ { $$ = NIL; }
! ;
window_definition_list:
window_definition { $$ = list_make1($1); }
***************
*** 11617,11622 **** window_definition:
--- 11626,11636 ----
}
;
+ filter_clause:
+ FILTER '(' WHERE a_expr ')' { $$ = $4; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
over_clause: OVER window_specification
{ $$ = $2; }
| OVER ColId
***************
*** 12834,12839 **** type_func_name_keyword:
--- 12848,12854 ----
| CONCURRENTLY
| CROSS
| CURRENT_SCHEMA
+ | FILTER
| FREEZE
| FULL
| ILIKE
*** a/src/backend/parser/parse_agg.c
--- b/src/backend/parser/parse_agg.c
***************
*** 44,50 **** typedef struct
int sublevels_up;
} check_ungrouped_columns_context;
! static int check_agg_arguments(ParseState *pstate, List *args);
static bool check_agg_arguments_walker(Node *node,
check_agg_arguments_context *context);
static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
--- 44,50 ----
int sublevels_up;
} check_ungrouped_columns_context;
! static int check_agg_arguments(ParseState *pstate, List *args, Expr *filter);
static bool check_agg_arguments_walker(Node *node,
check_agg_arguments_context *context);
static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
***************
*** 160,166 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
* Check the arguments to compute the aggregate's level and detect
* improper nesting.
*/
! min_varlevel = check_agg_arguments(pstate, agg->args);
agg->agglevelsup = min_varlevel;
/* Mark the correct pstate level as having aggregates */
--- 160,166 ----
* Check the arguments to compute the aggregate's level and detect
* improper nesting.
*/
! min_varlevel = check_agg_arguments(pstate, agg->args, agg->agg_filter);
agg->agglevelsup = min_varlevel;
/* Mark the correct pstate level as having aggregates */
***************
*** 207,212 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
--- 207,215 ----
case EXPR_KIND_HAVING:
/* okay */
break;
+ case EXPR_KIND_FILTER:
+ errkind = true;
+ break;
case EXPR_KIND_WINDOW_PARTITION:
/* okay */
break;
***************
*** 309,315 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
* which we can't know until we finish scanning the arguments.
*/
static int
! check_agg_arguments(ParseState *pstate, List *args)
{
int agglevel;
check_agg_arguments_context context;
--- 312,318 ----
* which we can't know until we finish scanning the arguments.
*/
static int
! check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
{
int agglevel;
check_agg_arguments_context context;
***************
*** 323,328 **** check_agg_arguments(ParseState *pstate, List *args)
--- 326,335 ----
check_agg_arguments_walker,
(void *) &context);
+ (void) expression_tree_walker((Node *) filter,
+ check_agg_arguments_walker,
+ (void *) &context);
+
/*
* If we found no vars nor aggs at all, it's a level-zero aggregate;
* otherwise, its level is the minimum of vars or aggs.
***************
*** 481,486 **** transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
--- 488,496 ----
case EXPR_KIND_HAVING:
errkind = true;
break;
+ case EXPR_KIND_FILTER:
+ errkind = true;
+ break;
case EXPR_KIND_WINDOW_PARTITION:
case EXPR_KIND_WINDOW_ORDER:
case EXPR_KIND_WINDOW_FRAME_RANGE:
*** a/src/backend/parser/parse_expr.c
--- b/src/backend/parser/parse_expr.c
***************
*** 22,27 ****
--- 22,28 ----
#include "nodes/nodeFuncs.h"
#include "optimizer/var.h"
#include "parser/analyze.h"
+ #include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
***************
*** 463,469 **** transformIndirection(ParseState *pstate, Node *basenode, List *indirection)
list_make1(n),
list_make1(result),
NIL, false, false, false,
! NULL, true, location);
if (newresult == NULL)
unknown_attribute(pstate, result, strVal(n), location);
result = newresult;
--- 464,470 ----
list_make1(n),
list_make1(result),
NIL, false, false, false,
! NULL, NULL, true, location);
if (newresult == NULL)
unknown_attribute(pstate, result, strVal(n), location);
result = newresult;
***************
*** 631,637 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, true, cref->location);
}
break;
}
--- 632,638 ----
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, NULL, true, cref->location);
}
break;
}
***************
*** 676,682 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, true, cref->location);
}
break;
}
--- 677,683 ----
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, NULL, true, cref->location);
}
break;
}
***************
*** 734,740 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, true, cref->location);
}
break;
}
--- 735,741 ----
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, NULL, true, cref->location);
}
break;
}
***************
*** 1241,1246 **** transformFuncCall(ParseState *pstate, FuncCall *fn)
--- 1242,1248 ----
{
List *targs;
ListCell *args;
+ Expr *tagg_filter;
/* Transform the list of arguments ... */
targs = NIL;
***************
*** 1250,1255 **** transformFuncCall(ParseState *pstate, FuncCall *fn)
--- 1252,1263 ----
(Node *) lfirst(args)));
}
+ /* Transform the aggregate filter using transformWhereClause, to
+ * which FILTER is virually identical... */
+ tagg_filter = NULL;
+ if (fn->agg_filter != NULL)
+ tagg_filter = (Expr *)transformWhereClause(pstate, (Node *)fn->agg_filter, EXPR_KIND_FILTER, "FILTER");
+
/* ... and hand off to ParseFuncOrColumn */
return ParseFuncOrColumn(pstate,
fn->funcname,
***************
*** 1258,1263 **** transformFuncCall(ParseState *pstate, FuncCall *fn)
--- 1266,1272 ----
fn->agg_star,
fn->agg_distinct,
fn->func_variadic,
+ tagg_filter,
fn->over,
false,
fn->location);
***************
*** 1430,1435 **** transformSubLink(ParseState *pstate, SubLink *sublink)
--- 1439,1445 ----
case EXPR_KIND_FROM_FUNCTION:
case EXPR_KIND_WHERE:
case EXPR_KIND_HAVING:
+ case EXPR_KIND_FILTER:
case EXPR_KIND_WINDOW_PARTITION:
case EXPR_KIND_WINDOW_ORDER:
case EXPR_KIND_WINDOW_FRAME_RANGE:
***************
*** 2579,2584 **** ParseExprKindName(ParseExprKind exprKind)
--- 2589,2596 ----
return "WHERE";
case EXPR_KIND_HAVING:
return "HAVING";
+ case EXPR_KIND_FILTER:
+ return "FILTER";
case EXPR_KIND_WINDOW_PARTITION:
return "window PARTITION BY";
case EXPR_KIND_WINDOW_ORDER:
*** a/src/backend/parser/parse_func.c
--- b/src/backend/parser/parse_func.c
***************
*** 63,69 **** Node *
ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
List *agg_order, bool agg_star, bool agg_distinct,
bool func_variadic,
! WindowDef *over, bool is_column, int location)
{
Oid rettype;
Oid funcid;
--- 63,69 ----
ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
List *agg_order, bool agg_star, bool agg_distinct,
bool func_variadic,
! Expr *agg_filter, WindowDef *over, bool is_column, int location)
{
Oid rettype;
Oid funcid;
***************
*** 175,181 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
* wasn't any aggregate or variadic decoration, nor an argument name.
*/
if (nargs == 1 && agg_order == NIL && !agg_star && !agg_distinct &&
! over == NULL && !func_variadic && argnames == NIL &&
list_length(funcname) == 1)
{
Oid argtype = actual_arg_types[0];
--- 175,181 ----
* wasn't any aggregate or variadic decoration, nor an argument name.
*/
if (nargs == 1 && agg_order == NIL && !agg_star && !agg_distinct &&
! agg_filter == NULL && over == NULL && !func_variadic && argnames == NIL &&
list_length(funcname) == 1)
{
Oid argtype = actual_arg_types[0];
***************
*** 251,256 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 251,262 ----
errmsg("ORDER BY specified, but %s is not an aggregate function",
NameListToString(funcname)),
parser_errposition(pstate, location)));
+ if (agg_filter)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("FILTER specified, but %s is not an aggregate function",
+ NameListToString(funcname)),
+ parser_errposition(pstate, location)));
if (over)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
***************
*** 402,407 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 408,415 ----
/* aggcollid and inputcollid will be set by parse_collate.c */
/* args, aggorder, aggdistinct will be set by transformAggregateCall */
aggref->aggstar = agg_star;
+ /* filter */
+ aggref->agg_filter = agg_filter;
/* agglevelsup will be set by transformAggregateCall */
aggref->location = location;
***************
*** 460,465 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 468,474 ----
/* winref will be set by transformWindowFuncCall */
wfunc->winstar = agg_star;
wfunc->winagg = (fdresult == FUNCDETAIL_AGGREGATE);
+ wfunc->agg_filter = agg_filter;
wfunc->location = location;
/*
***************
*** 483,488 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 492,507 ----
parser_errposition(pstate, location)));
/*
+ * Reject window functions which are not aggregates in the
+ * case of FILTER.
+ */
+ if (!wfunc->winagg && agg_filter)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("FILTER is not implemented in non-aggregate window functions",
+ parser_errposition(pstate, location))));
+
+ /*
* ordered aggs not allowed in windows yet
*/
if (agg_order != NIL)
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 7419,7425 **** get_agg_expr(Aggref *aggref, deparse_context *context)
--- 7419,7433 ----
appendStringInfoString(buf, " ORDER BY ");
get_rule_orderby(aggref->aggorder, aggref->args, false, context);
}
+
+ if (aggref->agg_filter != NULL)
+ {
+ appendStringInfoString(buf, ") FILTER (WHERE ");
+ get_rule_expr((Node *)aggref->agg_filter, context, false);
+ }
+
appendStringInfoChar(buf, ')');
+
}
/*
***************
*** 7456,7461 **** get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context)
--- 7464,7476 ----
appendStringInfoChar(buf, '*');
else
get_rule_expr((Node *) wfunc->args, context, true);
+
+ if (wfunc->agg_filter != NULL)
+ {
+ appendStringInfoString(buf, ") FILTER (WHERE ");
+ get_rule_expr((Node *)wfunc->agg_filter, context, false);
+ }
+
appendStringInfoString(buf, ") OVER ");
foreach(l, context->windowClause)
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 579,584 **** typedef struct AggrefExprState
--- 579,585 ----
{
ExprState xprstate;
List *args; /* states of argument expressions */
+ Expr *agg_filter; /* FILTER expression */
int aggno; /* ID number for agg within its plan node */
} AggrefExprState;
***************
*** 590,595 **** typedef struct WindowFuncExprState
--- 591,597 ----
{
ExprState xprstate;
List *args; /* states of argument expressions */
+ Expr *agg_filter; /* FILTER expression */
int wfuncno; /* ID number for wfunc within its plan node */
} WindowFuncExprState;
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 295,300 **** typedef struct FuncCall
--- 295,301 ----
bool agg_star; /* argument was really '*' */
bool agg_distinct; /* arguments were labeled DISTINCT */
bool func_variadic; /* last argument was labeled VARIADIC */
+ Node *agg_filter; /* FILTER clause, if any */
struct WindowDef *over; /* OVER clause, if any */
int location; /* token location, or -1 if unknown */
} FuncCall;
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
***************
*** 241,246 **** typedef struct Aggref
--- 241,247 ----
List *args; /* arguments and sort expressions */
List *aggorder; /* ORDER BY (list of SortGroupClause) */
List *aggdistinct; /* DISTINCT (list of SortGroupClause) */
+ Expr *agg_filter; /* FILTER expression */
bool aggstar; /* TRUE if argument list was really '*' */
Index agglevelsup; /* > 0 if agg belongs to outer query */
int location; /* token location, or -1 if unknown */
***************
*** 257,262 **** typedef struct WindowFunc
--- 258,264 ----
Oid wincollid; /* OID of collation of result */
Oid inputcollid; /* OID of collation that function should use */
List *args; /* arguments to the window function */
+ Expr *agg_filter; /* FILTER expression */
Index winref; /* index of associated WindowClause */
bool winstar; /* TRUE if argument list was really '*' */
bool winagg; /* is function a simple aggregate? */
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 155,160 **** PG_KEYWORD("extract", EXTRACT, COL_NAME_KEYWORD)
--- 155,161 ----
PG_KEYWORD("false", FALSE_P, RESERVED_KEYWORD)
PG_KEYWORD("family", FAMILY, UNRESERVED_KEYWORD)
PG_KEYWORD("fetch", FETCH, RESERVED_KEYWORD)
+ PG_KEYWORD("filter", FILTER, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("first", FIRST_P, UNRESERVED_KEYWORD)
PG_KEYWORD("float", FLOAT_P, COL_NAME_KEYWORD)
PG_KEYWORD("following", FOLLOWING, UNRESERVED_KEYWORD)
*** a/src/include/parser/parse_func.h
--- b/src/include/parser/parse_func.h
***************
*** 46,52 **** extern Node *ParseFuncOrColumn(ParseState *pstate,
List *funcname, List *fargs,
List *agg_order, bool agg_star, bool agg_distinct,
bool func_variadic,
! WindowDef *over, bool is_column, int location);
extern FuncDetailCode func_get_detail(List *funcname,
List *fargs, List *fargnames,
--- 46,52 ----
List *funcname, List *fargs,
List *agg_order, bool agg_star, bool agg_distinct,
bool func_variadic,
! Expr *agg_filter, WindowDef *over, bool is_column, int location);
extern FuncDetailCode func_get_detail(List *funcname,
List *fargs, List *fargnames,
*** a/src/include/parser/parse_node.h
--- b/src/include/parser/parse_node.h
***************
*** 39,44 **** typedef enum ParseExprKind
--- 39,45 ----
EXPR_KIND_FROM_FUNCTION, /* function in FROM clause */
EXPR_KIND_WHERE, /* WHERE */
EXPR_KIND_HAVING, /* HAVING */
+ EXPR_KIND_FILTER, /* FILTER */
EXPR_KIND_WINDOW_PARTITION, /* window definition PARTITION BY */
EXPR_KIND_WINDOW_ORDER, /* window definition ORDER BY */
EXPR_KIND_WINDOW_FRAME_RANGE, /* window frame clause with RANGE */
*** a/src/test/regress/expected/aggregates.out
--- b/src/test/regress/expected/aggregates.out
***************
*** 1154,1156 **** select string_agg(v, decode('ee', 'hex')) from bytea_test_table;
--- 1154,1193 ----
(1 row)
drop table bytea_test_table;
+ -- FILTER tests
+ select min(unique1) filter (where unique1 > 100) from tenk1;
+ min
+ -----
+ 101
+ (1 row)
+
+ select ten, sum(distinct four) filter (where four > 10) from onek a
+ group by ten
+ having exists (select 1 from onek b where sum(distinct a.four) = b.four);
+ ten | sum
+ -----+-----
+ 0 |
+ 2 |
+ 4 |
+ 6 |
+ 8 |
+ (5 rows)
+
+ -- outer-level aggregates
+ select
+ (select max((select i.unique2 from tenk1 i where i.unique1 = o.unique1)) filter (where o.unique1 < 10))
+ from tenk1 o;
+ max
+ ------
+ 9998
+ (1 row)
+
+ -- exercise lots of aggregate parts with FILTER
+ select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1)
+ from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+ generate_series(1,2) i;
+ aggfns
+ ---------------------------
+ {"(2,2,bar)","(3,1,baz)"}
+ (1 row)
+
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
***************
*** 1020,1024 **** SELECT ntile(0) OVER (ORDER BY ten), ten, four FROM tenk1;
--- 1020,1037 ----
ERROR: argument of ntile must be greater than zero
SELECT nth_value(four, 0) OVER (ORDER BY ten), ten, four FROM tenk1;
ERROR: argument of nth_value must be greater than zero
+ -- filter
+ SELECT sum(salary), row_number() OVER (ORDER BY depname), sum(
+ sum(salary) FILTER (WHERE enroll_date > '2007-01-01')
+ ) FILTER (WHERE depname <> 'sales') OVER (ORDER BY depname DESC) AS "filtered_sum",
+ depname
+ FROM empsalary GROUP BY depname;
+ sum | row_number | filtered_sum | depname
+ -------+------------+--------------+-----------
+ 14600 | 3 | | sales
+ 7400 | 2 | 3500 | personnel
+ 25100 | 1 | 22600 | develop
+ (3 rows)
+
-- cleanup
DROP TABLE empsalary;
*** a/src/test/regress/sql/aggregates.sql
--- b/src/test/regress/sql/aggregates.sql
***************
*** 442,444 **** select string_agg(v, NULL) from bytea_test_table;
--- 442,463 ----
select string_agg(v, decode('ee', 'hex')) from bytea_test_table;
drop table bytea_test_table;
+
+ -- FILTER tests
+
+ select min(unique1) filter (where unique1 > 100) from tenk1;
+
+ select ten, sum(distinct four) filter (where four > 10) from onek a
+ group by ten
+ having exists (select 1 from onek b where sum(distinct a.four) = b.four);
+
+ -- outer-level aggregates
+ select
+ (select max((select i.unique2 from tenk1 i where i.unique1 = o.unique1)) filter (where o.unique1 < 10))
+ from tenk1 o;
+
+ -- exercise lots of aggregate parts with FILTER
+
+ select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1)
+ from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+ generate_series(1,2) i;
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
***************
*** 264,268 **** SELECT ntile(0) OVER (ORDER BY ten), ten, four FROM tenk1;
--- 264,276 ----
SELECT nth_value(four, 0) OVER (ORDER BY ten), ten, four FROM tenk1;
+ -- filter
+
+ SELECT sum(salary), row_number() OVER (ORDER BY depname), sum(
+ sum(salary) FILTER (WHERE enroll_date > '2007-01-01')
+ ) FILTER (WHERE depname <> 'sales') OVER (ORDER BY depname DESC) AS "filtered_sum",
+ depname
+ FROM empsalary GROUP BY depname;
+
-- cleanup
DROP TABLE empsalary;
On Tue, Feb 26, 2013 at 01:09:30PM -0800, David Fetter wrote:
On Wed, Feb 13, 2013 at 06:45:31AM -0800, David Fetter wrote:
On Sat, Feb 09, 2013 at 11:59:22PM -0800, David Fetter wrote:
Folks,
Per suggestions and lots of help from Andrew Gierth, please find
attached a patch to clean up the call sites for FuncCall nodes, which
I'd like to expand centrally rather than in each of the 37 (or 38, but
I only redid 37) places where it's called. The remaining one is in
src/backend/nodes/copyfuncs.c, which has to be modified for any
changes in the that struct anyhow.The immediate purpose is two-fold: to reduce some redundancies, which
I believe is worth doing in and of itself, and to prepare for adding
FILTER on aggregates from the spec, and possibly other things in
the <aggregate function> part of the spec.Cheers,
David.Folks,
Please find attached two versions of a patch which provides optional
FILTER clause for aggregates (T612, "Advanced OLAP operations").The first is intended to be applied on top of the previous patch, the
second without it. The first is, I believe, clearer in what it's
doing. Rather than simply mechanically visiting every place a
function call might be constructed, it visits a central one to change
the default, then goes only to the places where it's relevant.The patches are both early WIP as they contain no docs or regression
tests yet.Docs and regression tests added, makeFuncArgs approached dropped for
now, will re-visit later.
Regression tests added to reflect bug fixes in COLLATE.
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
Attachments:
filter_no_mfa_004.difftext/plain; charset=us-asciiDownload
*** a/doc/src/sgml/ref/select.sgml
--- b/doc/src/sgml/ref/select.sgml
***************
*** 595,604 **** GROUP BY <replaceable class="parameter">expression</replaceable> [, ...]
</para>
<para>
! Aggregate functions, if any are used, are computed across all rows
making up each group, producing a separate value for each group
(whereas without <literal>GROUP BY</literal>, an aggregate
produces a single value computed across all the selected rows).
When <literal>GROUP BY</literal> is present, it is not valid for
the <command>SELECT</command> list expressions to refer to
ungrouped columns except within aggregate functions or if the
--- 595,607 ----
</para>
<para>
! In the absence of a <literal>FILTER</literal> clause,
! aggregate functions, if any are used, are computed across all rows
making up each group, producing a separate value for each group
(whereas without <literal>GROUP BY</literal>, an aggregate
produces a single value computed across all the selected rows).
+ When a <literal>FILTER</literal> clause is present, only those
+ rows matching the FILTER clause are included.
When <literal>GROUP BY</literal> is present, it is not valid for
the <command>SELECT</command> list expressions to refer to
ungrouped columns except within aggregate functions or if the
*** a/doc/src/sgml/syntax.sgml
--- b/doc/src/sgml/syntax.sgml
***************
*** 1562,1585 **** sqrt(2)
syntax of an aggregate expression is one of the following:
<synopsis>
! <replaceable>aggregate_name</replaceable> (<replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] )
! <replaceable>aggregate_name</replaceable> (ALL <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] )
! <replaceable>aggregate_name</replaceable> (DISTINCT <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] )
! <replaceable>aggregate_name</replaceable> ( * )
</synopsis>
where <replaceable>aggregate_name</replaceable> is a previously
defined aggregate (possibly qualified with a schema name),
! <replaceable>expression</replaceable> is
! any value expression that does not itself contain an aggregate
! expression or a window function call, and
! <replaceable>order_by_clause</replaceable> is a optional
! <literal>ORDER BY</> clause as described below.
</para>
<para>
! The first form of aggregate expression invokes the aggregate
! once for each input row.
The second form is the same as the first, since
<literal>ALL</literal> is the default.
The third form invokes the aggregate once for each distinct value
--- 1562,1587 ----
syntax of an aggregate expression is one of the following:
<synopsis>
! <replaceable>aggregate_name</replaceable> (<replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
! <replaceable>aggregate_name</replaceable> (ALL <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
! <replaceable>aggregate_name</replaceable> (DISTINCT <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
! <replaceable>aggregate_name</replaceable> ( * ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
</synopsis>
where <replaceable>aggregate_name</replaceable> is a previously
defined aggregate (possibly qualified with a schema name),
! <replaceable>expression</replaceable> is any value expression that
! does not itself contain an aggregate expression or a window
! function call, <replaceable>order_by_clause</replaceable> is a
! optional <literal>ORDER BY</> clause as described below. The
! <replaceable>aggregate_name</replaceable> can also be suffixed
! with <literal>FILTER</literal> as described below.
</para>
<para>
! The first form of aggregate expression invokes the aggregate once
! for each input row, or when a FILTER clause is present, each row
! matching same.
The second form is the same as the first, since
<literal>ALL</literal> is the default.
The third form invokes the aggregate once for each distinct value
***************
*** 1607,1612 **** sqrt(2)
--- 1609,1629 ----
</para>
<para>
+ Adding a FILTER clause to an aggregate specifies which values of
+ the expression being aggregated to evaluate. For example:
+ <programlisting>
+ SELECT
+ count(*) AS unfiltered,
+ count(*) FILTER (WHERE i < 5) AS filtered
+ FROM generate_series(1,10) AS s(i);
+ unfiltered | filtered
+ ------------+----------
+ 10 | 4
+ (1 row)
+ </programlisting>
+ </para>
+
+ <para>
Ordinarily, the input rows are fed to the aggregate function in an
unspecified order. In many cases this does not matter; for example,
<function>min</> produces the same result no matter what order it
***************
*** 1709,1718 **** SELECT string_agg(a ORDER BY a, ',') FROM table; -- incorrect
The syntax of a window function call is one of the following:
<synopsis>
! <replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) OVER ( <replaceable class="parameter">window_definition</replaceable> )
! <replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) OVER <replaceable>window_name</replaceable>
! <replaceable>function_name</replaceable> ( * ) OVER ( <replaceable class="parameter">window_definition</replaceable> )
! <replaceable>function_name</replaceable> ( * ) OVER <replaceable>window_name</replaceable>
</synopsis>
where <replaceable class="parameter">window_definition</replaceable>
has the syntax
--- 1726,1735 ----
The syntax of a window function call is one of the following:
<synopsis>
! <replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER ( <replaceable class="parameter">window_definition</replaceable> )
! <replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER <replaceable>window_name</replaceable>
! <replaceable>function_name</replaceable> ( * ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER ( <replaceable class="parameter">window_definition</replaceable> )
! <replaceable>function_name</replaceable> ( * ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER <replaceable>window_name</replaceable>
</synopsis>
where <replaceable class="parameter">window_definition</replaceable>
has the syntax
***************
*** 1836,1851 **** UNBOUNDED FOLLOWING
The built-in window functions are described in <xref
linkend="functions-window-table">. Other window functions can be added by
the user. Also, any built-in or user-defined aggregate function can be
! used as a window function.
</para>
<para>
! The syntaxes using <literal>*</> are used for calling parameter-less
! aggregate functions as window functions, for example
! <literal>count(*) OVER (PARTITION BY x ORDER BY y)</>.
! The asterisk (<literal>*</>) is customarily not used for non-aggregate window functions.
! Aggregate window functions, unlike normal aggregate functions, do not
! allow <literal>DISTINCT</> or <literal>ORDER BY</> to be used within the
function argument list.
</para>
--- 1853,1870 ----
The built-in window functions are described in <xref
linkend="functions-window-table">. Other window functions can be added by
the user. Also, any built-in or user-defined aggregate function can be
! used as a window function. A <literal>FILTER</literal> clause is
! only valid for aggregate functions used in windowing.
</para>
<para>
! The syntaxes using <literal>*</> are used for calling
! parameter-less aggregate functions as window functions, for
! example <literal>count(*) OVER (PARTITION BY x ORDER BY y)</>.
! The asterisk (<literal>*</>) is customarily not used for
! non-aggregate window functions. Aggregate window functions,
! unlike normal aggregate functions, do not allow
! <literal>DISTINCT</> or <literal>ORDER BY</> to be used within the
function argument list.
</para>
*** a/src/backend/executor/execQual.c
--- b/src/backend/executor/execQual.c
***************
*** 4402,4407 **** ExecInitExpr(Expr *node, PlanState *parent)
--- 4402,4408 ----
astate->args = (List *) ExecInitExpr((Expr *) aggref->args,
parent);
+ astate->agg_filter = ExecInitExpr(aggref->agg_filter, parent);
/*
* Complain if the aggregate's arguments contain any
***************
*** 4440,4445 **** ExecInitExpr(Expr *node, PlanState *parent)
--- 4441,4447 ----
wfstate->args = (List *) ExecInitExpr((Expr *) wfunc->args,
parent);
+ wfstate->agg_filter = ExecInitExpr(wfunc->agg_filter, parent);
/*
* Complain if the windowfunc's arguments contain any
*** a/src/backend/executor/functions.c
--- b/src/backend/executor/functions.c
***************
*** 381,387 **** sql_fn_post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var)
list_make1(subfield),
list_make1(param),
NIL, false, false, false,
! NULL, true, cref->location);
}
return param;
--- 381,387 ----
list_make1(subfield),
list_make1(param),
NIL, false, false, false,
! NULL, NULL, true, cref->location);
}
return param;
*** a/src/backend/executor/nodeAgg.c
--- b/src/backend/executor/nodeAgg.c
***************
*** 488,493 **** advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
--- 488,505 ----
int i;
TupleTableSlot *slot;
+ /* Skip anything FILTERed out */
+ ExprState *filter = peraggstate->aggrefstate->agg_filter;
+ if (filter)
+ {
+ MemoryContext oldcontext = MemoryContextSwitchTo(aggstate->tmpcontext->ecxt_per_tuple_memory);
+ bool isnull;
+ Datum res = ExecEvalExpr(filter, aggstate->tmpcontext, &isnull, NULL);
+ MemoryContextSwitchTo(oldcontext);
+ if (isnull || !DatumGetBool(res))
+ continue;
+ }
+
/* Evaluate the current input expressions for this aggregate */
slot = ExecProject(peraggstate->evalproj, NULL);
*** a/src/backend/executor/nodeWindowAgg.c
--- b/src/backend/executor/nodeWindowAgg.c
***************
*** 227,235 **** advance_windowaggregate(WindowAggState *winstate,
--- 227,248 ----
int i;
MemoryContext oldContext;
ExprContext *econtext = winstate->tmpcontext;
+ ExprState *filter = wfuncstate->agg_filter;
oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+ /* Skip anything FILTERed out */
+ if (filter)
+ {
+ bool isnull;
+ Datum res = ExecEvalExpr(filter, econtext, &isnull, NULL);
+ if (isnull || !DatumGetBool(res))
+ {
+ MemoryContextSwitchTo(oldContext);
+ return;
+ }
+ }
+
/* We start from 1, since the 0th arg will be the transition value */
i = 1;
foreach(arg, wfuncstate->args)
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 1137,1142 **** _copyAggref(const Aggref *from)
--- 1137,1143 ----
COPY_NODE_FIELD(args);
COPY_NODE_FIELD(aggorder);
COPY_NODE_FIELD(aggdistinct);
+ COPY_NODE_FIELD(agg_filter);
COPY_SCALAR_FIELD(aggstar);
COPY_SCALAR_FIELD(agglevelsup);
COPY_LOCATION_FIELD(location);
***************
*** 1157,1162 **** _copyWindowFunc(const WindowFunc *from)
--- 1158,1164 ----
COPY_SCALAR_FIELD(wincollid);
COPY_SCALAR_FIELD(inputcollid);
COPY_NODE_FIELD(args);
+ COPY_NODE_FIELD(agg_filter);
COPY_SCALAR_FIELD(winref);
COPY_SCALAR_FIELD(winstar);
COPY_SCALAR_FIELD(winagg);
***************
*** 2155,2160 **** _copyFuncCall(const FuncCall *from)
--- 2157,2163 ----
COPY_SCALAR_FIELD(agg_star);
COPY_SCALAR_FIELD(agg_distinct);
COPY_SCALAR_FIELD(func_variadic);
+ COPY_NODE_FIELD(agg_filter);
COPY_NODE_FIELD(over);
COPY_LOCATION_FIELD(location);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 196,201 **** _equalAggref(const Aggref *a, const Aggref *b)
--- 196,202 ----
COMPARE_NODE_FIELD(args);
COMPARE_NODE_FIELD(aggorder);
COMPARE_NODE_FIELD(aggdistinct);
+ COMPARE_NODE_FIELD(agg_filter);
COMPARE_SCALAR_FIELD(aggstar);
COMPARE_SCALAR_FIELD(agglevelsup);
COMPARE_LOCATION_FIELD(location);
***************
*** 211,216 **** _equalWindowFunc(const WindowFunc *a, const WindowFunc *b)
--- 212,218 ----
COMPARE_SCALAR_FIELD(wincollid);
COMPARE_SCALAR_FIELD(inputcollid);
COMPARE_NODE_FIELD(args);
+ COMPARE_NODE_FIELD(agg_filter);
COMPARE_SCALAR_FIELD(winref);
COMPARE_SCALAR_FIELD(winstar);
COMPARE_SCALAR_FIELD(winagg);
***************
*** 2009,2014 **** _equalFuncCall(const FuncCall *a, const FuncCall *b)
--- 2011,2017 ----
COMPARE_SCALAR_FIELD(agg_star);
COMPARE_SCALAR_FIELD(agg_distinct);
COMPARE_SCALAR_FIELD(func_variadic);
+ COMPARE_NODE_FIELD(agg_filter);
COMPARE_NODE_FIELD(over);
COMPARE_LOCATION_FIELD(location);
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
***************
*** 1570,1575 **** expression_tree_walker(Node *node,
--- 1570,1578 ----
if (expression_tree_walker((Node *) expr->aggdistinct,
walker, context))
return true;
+ if (expression_tree_walker((Node *) expr->agg_filter,
+ walker, context))
+ return true;
}
break;
case T_WindowFunc:
***************
*** 1580,1585 **** expression_tree_walker(Node *node,
--- 1583,1591 ----
if (expression_tree_walker((Node *) expr->args,
walker, context))
return true;
+ if (expression_tree_walker((Node *) expr->agg_filter,
+ walker, context))
+ return true;
}
break;
case T_ArrayRef:
***************
*** 2079,2084 **** expression_tree_mutator(Node *node,
--- 2085,2091 ----
MUTATE(newnode->args, aggref->args, List *);
MUTATE(newnode->aggorder, aggref->aggorder, List *);
MUTATE(newnode->aggdistinct, aggref->aggdistinct, List *);
+ MUTATE(newnode->agg_filter, aggref->agg_filter, Expr *);
return (Node *) newnode;
}
break;
***************
*** 2089,2094 **** expression_tree_mutator(Node *node,
--- 2096,2102 ----
FLATCOPY(newnode, wfunc, WindowFunc);
MUTATE(newnode->args, wfunc->args, List *);
+ MUTATE(newnode->agg_filter, wfunc->agg_filter, Expr *);
return (Node *) newnode;
}
break;
***************
*** 2951,2956 **** raw_expression_tree_walker(Node *node,
--- 2959,2966 ----
return true;
if (walker(fcall->agg_order, context))
return true;
+ if (walker(fcall->agg_filter, context))
+ return true;
if (walker(fcall->over, context))
return true;
/* function name is deemed uninteresting */
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 958,963 **** _outAggref(StringInfo str, const Aggref *node)
--- 958,964 ----
WRITE_NODE_FIELD(args);
WRITE_NODE_FIELD(aggorder);
WRITE_NODE_FIELD(aggdistinct);
+ WRITE_NODE_FIELD(agg_filter);
WRITE_BOOL_FIELD(aggstar);
WRITE_UINT_FIELD(agglevelsup);
WRITE_LOCATION_FIELD(location);
***************
*** 973,978 **** _outWindowFunc(StringInfo str, const WindowFunc *node)
--- 974,980 ----
WRITE_OID_FIELD(wincollid);
WRITE_OID_FIELD(inputcollid);
WRITE_NODE_FIELD(args);
+ WRITE_NODE_FIELD(agg_filter);
WRITE_UINT_FIELD(winref);
WRITE_BOOL_FIELD(winstar);
WRITE_BOOL_FIELD(winagg);
***************
*** 2083,2088 **** _outFuncCall(StringInfo str, const FuncCall *node)
--- 2085,2091 ----
WRITE_BOOL_FIELD(agg_star);
WRITE_BOOL_FIELD(agg_distinct);
WRITE_BOOL_FIELD(func_variadic);
+ WRITE_NODE_FIELD(agg_filter);
WRITE_NODE_FIELD(over);
WRITE_LOCATION_FIELD(location);
}
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 479,484 **** _readAggref(void)
--- 479,485 ----
READ_NODE_FIELD(args);
READ_NODE_FIELD(aggorder);
READ_NODE_FIELD(aggdistinct);
+ READ_NODE_FIELD(agg_filter);
READ_BOOL_FIELD(aggstar);
READ_UINT_FIELD(agglevelsup);
READ_LOCATION_FIELD(location);
***************
*** 499,504 **** _readWindowFunc(void)
--- 500,506 ----
READ_OID_FIELD(wincollid);
READ_OID_FIELD(inputcollid);
READ_NODE_FIELD(args);
+ READ_NODE_FIELD(agg_filter);
READ_UINT_FIELD(winref);
READ_BOOL_FIELD(winstar);
READ_BOOL_FIELD(winagg);
*** a/src/backend/optimizer/plan/planagg.c
--- b/src/backend/optimizer/plan/planagg.c
***************
*** 313,319 **** find_minmax_aggs_walker(Node *node, List **context)
ListCell *l;
Assert(aggref->agglevelsup == 0);
! if (list_length(aggref->args) != 1 || aggref->aggorder != NIL)
return true; /* it couldn't be MIN/MAX */
/* note: we do not care if DISTINCT is mentioned ... */
curTarget = (TargetEntry *) linitial(aggref->args);
--- 313,319 ----
ListCell *l;
Assert(aggref->agglevelsup == 0);
! if (list_length(aggref->args) != 1 || aggref->aggorder != NIL || aggref->agg_filter != NULL)
return true; /* it couldn't be MIN/MAX */
/* note: we do not care if DISTINCT is mentioned ... */
curTarget = (TargetEntry *) linitial(aggref->args);
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 489,494 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
--- 489,495 ----
opt_frame_clause frame_extent frame_bound
%type <str> opt_existing_window_name
%type <boolean> opt_if_not_exists
+ %type <node> filter_clause
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
***************
*** 535,541 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
EXTENSION EXTERNAL EXTRACT
! FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
FREEZE FROM FULL FUNCTION FUNCTIONS
GLOBAL GRANT GRANTED GREATEST GROUP_P
--- 536,542 ----
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
EXTENSION EXTERNAL EXTRACT
! FALSE_P FAMILY FETCH FILTER FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
FREEZE FROM FULL FUNCTION FUNCTIONS
GLOBAL GRANT GRANTED GREATEST GROUP_P
***************
*** 11087,11093 **** c_expr: columnref { $$ = $1; }
* (Note that many of the special SQL functions wouldn't actually make any
* sense as functional index entries, but we ignore that consideration here.)
*/
! func_expr: func_name '(' ')' over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
--- 11088,11094 ----
* (Note that many of the special SQL functions wouldn't actually make any
* sense as functional index entries, but we ignore that consideration here.)
*/
! func_expr: func_name '(' ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
***************
*** 11096,11119 **** func_expr: func_name '(' ')' over_clause
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
! n->over = $4;
! n->location = @1;
! $$ = (Node *)n;
! }
! | func_name '(' func_arg_list ')' over_clause
! {
! FuncCall *n = makeNode(FuncCall);
! n->funcname = $1;
! n->args = $3;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
n->over = $5;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' VARIADIC func_arg_expr ')' over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
--- 11097,11122 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
! n->agg_filter = $4;
n->over = $5;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' func_arg_list ')' filter_clause over_clause
! {
! FuncCall *n = makeNode(FuncCall);
! n->funcname = $1;
! n->args = $3;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->agg_filter = $5;
! n->over = $6;
! n->location = @1;
! $$ = (Node *)n;
! }
! | func_name '(' VARIADIC func_arg_expr ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
***************
*** 11122,11132 **** func_expr: func_name '(' ')' over_clause
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = TRUE;
! n->over = $6;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' func_arg_list ',' VARIADIC func_arg_expr ')' over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
--- 11125,11136 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = TRUE;
! n->agg_filter = $6;
! n->over = $7;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' func_arg_list ',' VARIADIC func_arg_expr ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
***************
*** 11135,11145 **** func_expr: func_name '(' ')' over_clause
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = TRUE;
! n->over = $8;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' func_arg_list sort_clause ')' over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
--- 11139,11150 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = TRUE;
! n->agg_filter = $8;
! n->over = $9;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' func_arg_list sort_clause ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
***************
*** 11148,11158 **** func_expr: func_name '(' ')' over_clause
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
! n->over = $6;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' ALL func_arg_list opt_sort_clause ')' over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
--- 11153,11164 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
! n->agg_filter = $6;
! n->over = $7;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' ALL func_arg_list opt_sort_clause ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
***************
*** 11165,11175 **** func_expr: func_name '(' ')' over_clause
* for that in FuncCall at the moment.
*/
n->func_variadic = FALSE;
! n->over = $7;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' DISTINCT func_arg_list opt_sort_clause ')' over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
--- 11171,11182 ----
* for that in FuncCall at the moment.
*/
n->func_variadic = FALSE;
! n->agg_filter = $7;
! n->over = $8;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' DISTINCT func_arg_list opt_sort_clause ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
***************
*** 11178,11188 **** func_expr: func_name '(' ')' over_clause
n->agg_star = FALSE;
n->agg_distinct = TRUE;
n->func_variadic = FALSE;
! n->over = $7;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' '*' ')' over_clause
{
/*
* We consider AGGREGATE(*) to invoke a parameterless
--- 11185,11196 ----
n->agg_star = FALSE;
n->agg_distinct = TRUE;
n->func_variadic = FALSE;
! n->agg_filter = $7;
! n->over = $8;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' '*' ')' filter_clause over_clause
{
/*
* We consider AGGREGATE(*) to invoke a parameterless
***************
*** 11201,11207 **** func_expr: func_name '(' ')' over_clause
n->agg_star = TRUE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
! n->over = $5;
n->location = @1;
$$ = (Node *)n;
}
--- 11209,11216 ----
n->agg_star = TRUE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
! n->agg_filter = $5;
! n->over = $6;
n->location = @1;
$$ = (Node *)n;
}
***************
*** 11735,11741 **** xmlexists_argument:
window_clause:
WINDOW window_definition_list { $$ = $2; }
| /*EMPTY*/ { $$ = NIL; }
! ;
window_definition_list:
window_definition { $$ = list_make1($1); }
--- 11744,11750 ----
window_clause:
WINDOW window_definition_list { $$ = $2; }
| /*EMPTY*/ { $$ = NIL; }
! ;
window_definition_list:
window_definition { $$ = list_make1($1); }
***************
*** 11752,11757 **** window_definition:
--- 11761,11771 ----
}
;
+ filter_clause:
+ FILTER '(' WHERE a_expr ')' { $$ = $4; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
over_clause: OVER window_specification
{ $$ = $2; }
| OVER ColId
***************
*** 12972,12977 **** type_func_name_keyword:
--- 12986,12992 ----
| CONCURRENTLY
| CROSS
| CURRENT_SCHEMA
+ | FILTER
| FREEZE
| FULL
| ILIKE
*** a/src/backend/parser/parse_agg.c
--- b/src/backend/parser/parse_agg.c
***************
*** 44,50 **** typedef struct
int sublevels_up;
} check_ungrouped_columns_context;
! static int check_agg_arguments(ParseState *pstate, List *args);
static bool check_agg_arguments_walker(Node *node,
check_agg_arguments_context *context);
static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
--- 44,50 ----
int sublevels_up;
} check_ungrouped_columns_context;
! static int check_agg_arguments(ParseState *pstate, List *args, Expr *filter);
static bool check_agg_arguments_walker(Node *node,
check_agg_arguments_context *context);
static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
***************
*** 160,166 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
* Check the arguments to compute the aggregate's level and detect
* improper nesting.
*/
! min_varlevel = check_agg_arguments(pstate, agg->args);
agg->agglevelsup = min_varlevel;
/* Mark the correct pstate level as having aggregates */
--- 160,166 ----
* Check the arguments to compute the aggregate's level and detect
* improper nesting.
*/
! min_varlevel = check_agg_arguments(pstate, agg->args, agg->agg_filter);
agg->agglevelsup = min_varlevel;
/* Mark the correct pstate level as having aggregates */
***************
*** 207,212 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
--- 207,215 ----
case EXPR_KIND_HAVING:
/* okay */
break;
+ case EXPR_KIND_FILTER:
+ errkind = true;
+ break;
case EXPR_KIND_WINDOW_PARTITION:
/* okay */
break;
***************
*** 309,315 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
* which we can't know until we finish scanning the arguments.
*/
static int
! check_agg_arguments(ParseState *pstate, List *args)
{
int agglevel;
check_agg_arguments_context context;
--- 312,318 ----
* which we can't know until we finish scanning the arguments.
*/
static int
! check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
{
int agglevel;
check_agg_arguments_context context;
***************
*** 323,328 **** check_agg_arguments(ParseState *pstate, List *args)
--- 326,335 ----
check_agg_arguments_walker,
(void *) &context);
+ (void) expression_tree_walker((Node *) filter,
+ check_agg_arguments_walker,
+ (void *) &context);
+
/*
* If we found no vars nor aggs at all, it's a level-zero aggregate;
* otherwise, its level is the minimum of vars or aggs.
***************
*** 481,486 **** transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
--- 488,496 ----
case EXPR_KIND_HAVING:
errkind = true;
break;
+ case EXPR_KIND_FILTER:
+ errkind = true;
+ break;
case EXPR_KIND_WINDOW_PARTITION:
case EXPR_KIND_WINDOW_ORDER:
case EXPR_KIND_WINDOW_FRAME_RANGE:
*** a/src/backend/parser/parse_collate.c
--- b/src/backend/parser/parse_collate.c
***************
*** 575,580 **** assign_collations_walker(Node *node, assign_collations_context *context)
--- 575,584 ----
* the case above for T_TargetEntry will apply
* appropriate checks to agg ORDER BY items.
*
+ * Likewise, we assign collations for the (bool)
+ * expression in agg_filter, independently of
+ * any other args.
+ *
* We need not recurse into the aggorder or
* aggdistinct lists, because those contain only
* SortGroupClause nodes which we need not
***************
*** 595,600 **** assign_collations_walker(Node *node, assign_collations_context *context)
--- 599,620 ----
(void) assign_collations_walker((Node *) tle,
&loccontext);
}
+
+ assign_expr_collations(context->pstate, aggref->agg_filter);
+ }
+ break;
+ case T_WindowFunc:
+ {
+ /*
+ * WindowFunc requires special processing only for
+ * its agg_filter clause, as for aggregates.
+ */
+ WindowFunc *wfunc = (WindowFunc *) node;
+
+ (void) assign_collations_walker((Node *) wfunc->args,
+ &loccontext);
+
+ assign_expr_collations(context->pstate, wfunc->agg_filter);
}
break;
case T_CaseExpr:
*** a/src/backend/parser/parse_expr.c
--- b/src/backend/parser/parse_expr.c
***************
*** 22,27 ****
--- 22,28 ----
#include "nodes/nodeFuncs.h"
#include "optimizer/var.h"
#include "parser/analyze.h"
+ #include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
***************
*** 463,469 **** transformIndirection(ParseState *pstate, Node *basenode, List *indirection)
list_make1(n),
list_make1(result),
NIL, false, false, false,
! NULL, true, location);
if (newresult == NULL)
unknown_attribute(pstate, result, strVal(n), location);
result = newresult;
--- 464,470 ----
list_make1(n),
list_make1(result),
NIL, false, false, false,
! NULL, NULL, true, location);
if (newresult == NULL)
unknown_attribute(pstate, result, strVal(n), location);
result = newresult;
***************
*** 631,637 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, true, cref->location);
}
break;
}
--- 632,638 ----
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, NULL, true, cref->location);
}
break;
}
***************
*** 676,682 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, true, cref->location);
}
break;
}
--- 677,683 ----
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, NULL, true, cref->location);
}
break;
}
***************
*** 734,740 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, true, cref->location);
}
break;
}
--- 735,741 ----
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, NULL, true, cref->location);
}
break;
}
***************
*** 1241,1246 **** transformFuncCall(ParseState *pstate, FuncCall *fn)
--- 1242,1248 ----
{
List *targs;
ListCell *args;
+ Expr *tagg_filter;
/* Transform the list of arguments ... */
targs = NIL;
***************
*** 1250,1255 **** transformFuncCall(ParseState *pstate, FuncCall *fn)
--- 1252,1263 ----
(Node *) lfirst(args)));
}
+ /* Transform the aggregate filter using transformWhereClause, to
+ * which FILTER is virually identical... */
+ tagg_filter = NULL;
+ if (fn->agg_filter != NULL)
+ tagg_filter = (Expr *)transformWhereClause(pstate, (Node *)fn->agg_filter, EXPR_KIND_FILTER, "FILTER");
+
/* ... and hand off to ParseFuncOrColumn */
return ParseFuncOrColumn(pstate,
fn->funcname,
***************
*** 1258,1263 **** transformFuncCall(ParseState *pstate, FuncCall *fn)
--- 1266,1272 ----
fn->agg_star,
fn->agg_distinct,
fn->func_variadic,
+ tagg_filter,
fn->over,
false,
fn->location);
***************
*** 1430,1435 **** transformSubLink(ParseState *pstate, SubLink *sublink)
--- 1439,1445 ----
case EXPR_KIND_FROM_FUNCTION:
case EXPR_KIND_WHERE:
case EXPR_KIND_HAVING:
+ case EXPR_KIND_FILTER:
case EXPR_KIND_WINDOW_PARTITION:
case EXPR_KIND_WINDOW_ORDER:
case EXPR_KIND_WINDOW_FRAME_RANGE:
***************
*** 2579,2584 **** ParseExprKindName(ParseExprKind exprKind)
--- 2589,2596 ----
return "WHERE";
case EXPR_KIND_HAVING:
return "HAVING";
+ case EXPR_KIND_FILTER:
+ return "FILTER";
case EXPR_KIND_WINDOW_PARTITION:
return "window PARTITION BY";
case EXPR_KIND_WINDOW_ORDER:
*** a/src/backend/parser/parse_func.c
--- b/src/backend/parser/parse_func.c
***************
*** 63,69 **** Node *
ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
List *agg_order, bool agg_star, bool agg_distinct,
bool func_variadic,
! WindowDef *over, bool is_column, int location)
{
Oid rettype;
Oid funcid;
--- 63,69 ----
ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
List *agg_order, bool agg_star, bool agg_distinct,
bool func_variadic,
! Expr *agg_filter, WindowDef *over, bool is_column, int location)
{
Oid rettype;
Oid funcid;
***************
*** 175,181 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
* wasn't any aggregate or variadic decoration, nor an argument name.
*/
if (nargs == 1 && agg_order == NIL && !agg_star && !agg_distinct &&
! over == NULL && !func_variadic && argnames == NIL &&
list_length(funcname) == 1)
{
Oid argtype = actual_arg_types[0];
--- 175,181 ----
* wasn't any aggregate or variadic decoration, nor an argument name.
*/
if (nargs == 1 && agg_order == NIL && !agg_star && !agg_distinct &&
! agg_filter == NULL && over == NULL && !func_variadic && argnames == NIL &&
list_length(funcname) == 1)
{
Oid argtype = actual_arg_types[0];
***************
*** 251,256 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 251,262 ----
errmsg("ORDER BY specified, but %s is not an aggregate function",
NameListToString(funcname)),
parser_errposition(pstate, location)));
+ if (agg_filter)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("FILTER specified, but %s is not an aggregate function",
+ NameListToString(funcname)),
+ parser_errposition(pstate, location)));
if (over)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
***************
*** 402,407 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 408,415 ----
/* aggcollid and inputcollid will be set by parse_collate.c */
/* args, aggorder, aggdistinct will be set by transformAggregateCall */
aggref->aggstar = agg_star;
+ /* filter */
+ aggref->agg_filter = agg_filter;
/* agglevelsup will be set by transformAggregateCall */
aggref->location = location;
***************
*** 460,465 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 468,474 ----
/* winref will be set by transformWindowFuncCall */
wfunc->winstar = agg_star;
wfunc->winagg = (fdresult == FUNCDETAIL_AGGREGATE);
+ wfunc->agg_filter = agg_filter;
wfunc->location = location;
/*
***************
*** 483,488 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 492,507 ----
parser_errposition(pstate, location)));
/*
+ * Reject window functions which are not aggregates in the
+ * case of FILTER.
+ */
+ if (!wfunc->winagg && agg_filter)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("FILTER is not implemented in non-aggregate window functions",
+ parser_errposition(pstate, location))));
+
+ /*
* ordered aggs not allowed in windows yet
*/
if (agg_order != NIL)
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 7419,7425 **** get_agg_expr(Aggref *aggref, deparse_context *context)
--- 7419,7433 ----
appendStringInfoString(buf, " ORDER BY ");
get_rule_orderby(aggref->aggorder, aggref->args, false, context);
}
+
+ if (aggref->agg_filter != NULL)
+ {
+ appendStringInfoString(buf, ") FILTER (WHERE ");
+ get_rule_expr((Node *)aggref->agg_filter, context, false);
+ }
+
appendStringInfoChar(buf, ')');
+
}
/*
***************
*** 7456,7461 **** get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context)
--- 7464,7476 ----
appendStringInfoChar(buf, '*');
else
get_rule_expr((Node *) wfunc->args, context, true);
+
+ if (wfunc->agg_filter != NULL)
+ {
+ appendStringInfoString(buf, ") FILTER (WHERE ");
+ get_rule_expr((Node *)wfunc->agg_filter, context, false);
+ }
+
appendStringInfoString(buf, ") OVER ");
foreach(l, context->windowClause)
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 584,589 **** typedef struct AggrefExprState
--- 584,590 ----
{
ExprState xprstate;
List *args; /* states of argument expressions */
+ Expr *agg_filter; /* FILTER expression */
int aggno; /* ID number for agg within its plan node */
} AggrefExprState;
***************
*** 595,600 **** typedef struct WindowFuncExprState
--- 596,602 ----
{
ExprState xprstate;
List *args; /* states of argument expressions */
+ Expr *agg_filter; /* FILTER expression */
int wfuncno; /* ID number for wfunc within its plan node */
} WindowFuncExprState;
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 295,300 **** typedef struct FuncCall
--- 295,301 ----
bool agg_star; /* argument was really '*' */
bool agg_distinct; /* arguments were labeled DISTINCT */
bool func_variadic; /* last argument was labeled VARIADIC */
+ Node *agg_filter; /* FILTER clause, if any */
struct WindowDef *over; /* OVER clause, if any */
int location; /* token location, or -1 if unknown */
} FuncCall;
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
***************
*** 247,252 **** typedef struct Aggref
--- 247,253 ----
List *args; /* arguments and sort expressions */
List *aggorder; /* ORDER BY (list of SortGroupClause) */
List *aggdistinct; /* DISTINCT (list of SortGroupClause) */
+ Expr *agg_filter; /* FILTER expression */
bool aggstar; /* TRUE if argument list was really '*' */
Index agglevelsup; /* > 0 if agg belongs to outer query */
int location; /* token location, or -1 if unknown */
***************
*** 263,268 **** typedef struct WindowFunc
--- 264,270 ----
Oid wincollid; /* OID of collation of result */
Oid inputcollid; /* OID of collation that function should use */
List *args; /* arguments to the window function */
+ Expr *agg_filter; /* FILTER expression */
Index winref; /* index of associated WindowClause */
bool winstar; /* TRUE if argument list was really '*' */
bool winagg; /* is function a simple aggregate? */
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 155,160 **** PG_KEYWORD("extract", EXTRACT, COL_NAME_KEYWORD)
--- 155,161 ----
PG_KEYWORD("false", FALSE_P, RESERVED_KEYWORD)
PG_KEYWORD("family", FAMILY, UNRESERVED_KEYWORD)
PG_KEYWORD("fetch", FETCH, RESERVED_KEYWORD)
+ PG_KEYWORD("filter", FILTER, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("first", FIRST_P, UNRESERVED_KEYWORD)
PG_KEYWORD("float", FLOAT_P, COL_NAME_KEYWORD)
PG_KEYWORD("following", FOLLOWING, UNRESERVED_KEYWORD)
*** a/src/include/parser/parse_func.h
--- b/src/include/parser/parse_func.h
***************
*** 46,52 **** extern Node *ParseFuncOrColumn(ParseState *pstate,
List *funcname, List *fargs,
List *agg_order, bool agg_star, bool agg_distinct,
bool func_variadic,
! WindowDef *over, bool is_column, int location);
extern FuncDetailCode func_get_detail(List *funcname,
List *fargs, List *fargnames,
--- 46,52 ----
List *funcname, List *fargs,
List *agg_order, bool agg_star, bool agg_distinct,
bool func_variadic,
! Expr *agg_filter, WindowDef *over, bool is_column, int location);
extern FuncDetailCode func_get_detail(List *funcname,
List *fargs, List *fargnames,
*** a/src/include/parser/parse_node.h
--- b/src/include/parser/parse_node.h
***************
*** 39,44 **** typedef enum ParseExprKind
--- 39,45 ----
EXPR_KIND_FROM_FUNCTION, /* function in FROM clause */
EXPR_KIND_WHERE, /* WHERE */
EXPR_KIND_HAVING, /* HAVING */
+ EXPR_KIND_FILTER, /* FILTER */
EXPR_KIND_WINDOW_PARTITION, /* window definition PARTITION BY */
EXPR_KIND_WINDOW_ORDER, /* window definition ORDER BY */
EXPR_KIND_WINDOW_FRAME_RANGE, /* window frame clause with RANGE */
*** a/src/test/regress/expected/aggregates.out
--- b/src/test/regress/expected/aggregates.out
***************
*** 1154,1156 **** select string_agg(v, decode('ee', 'hex')) from bytea_test_table;
--- 1154,1215 ----
(1 row)
drop table bytea_test_table;
+ -- FILTER tests
+ select min(unique1) filter (where unique1 > 100) from tenk1;
+ min
+ -----
+ 101
+ (1 row)
+
+ select ten, sum(distinct four) filter (where four::text ~ '123') from onek a
+ group by ten;
+ ten | sum
+ -----+-----
+ 0 |
+ 1 |
+ 2 |
+ 3 |
+ 4 |
+ 5 |
+ 6 |
+ 7 |
+ 8 |
+ 9 |
+ (10 rows)
+
+ select ten, sum(distinct four) filter (where four > 10) from onek a
+ group by ten
+ having exists (select 1 from onek b where sum(distinct a.four) = b.four);
+ ten | sum
+ -----+-----
+ 0 |
+ 2 |
+ 4 |
+ 6 |
+ 8 |
+ (5 rows)
+
+ select max(foo COLLATE "C") filter (where (bar collate "POSIX") > '0') from (values ('a', 'b')) AS v(foo,bar);
+ max
+ -----
+ a
+ (1 row)
+
+ -- outer-level aggregates
+ select
+ (select max((select i.unique2 from tenk1 i where i.unique1 = o.unique1)) filter (where o.unique1 < 10))
+ from tenk1 o;
+ max
+ ------
+ 9998
+ (1 row)
+
+ -- exercise lots of aggregate parts with FILTER
+ select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1)
+ from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+ generate_series(1,2) i;
+ aggfns
+ ---------------------------
+ {"(2,2,bar)","(3,1,baz)"}
+ (1 row)
+
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
***************
*** 1020,1024 **** SELECT ntile(0) OVER (ORDER BY ten), ten, four FROM tenk1;
--- 1020,1037 ----
ERROR: argument of ntile must be greater than zero
SELECT nth_value(four, 0) OVER (ORDER BY ten), ten, four FROM tenk1;
ERROR: argument of nth_value must be greater than zero
+ -- filter
+ SELECT sum(salary), row_number() OVER (ORDER BY depname), sum(
+ sum(salary) FILTER (WHERE enroll_date > '2007-01-01')
+ ) FILTER (WHERE depname <> 'sales') OVER (ORDER BY depname DESC) AS "filtered_sum",
+ depname
+ FROM empsalary GROUP BY depname;
+ sum | row_number | filtered_sum | depname
+ -------+------------+--------------+-----------
+ 14600 | 3 | | sales
+ 7400 | 2 | 3500 | personnel
+ 25100 | 1 | 22600 | develop
+ (3 rows)
+
-- cleanup
DROP TABLE empsalary;
*** a/src/test/regress/sql/aggregates.sql
--- b/src/test/regress/sql/aggregates.sql
***************
*** 442,444 **** select string_agg(v, NULL) from bytea_test_table;
--- 442,468 ----
select string_agg(v, decode('ee', 'hex')) from bytea_test_table;
drop table bytea_test_table;
+
+ -- FILTER tests
+
+ select min(unique1) filter (where unique1 > 100) from tenk1;
+
+ select ten, sum(distinct four) filter (where four::text ~ '123') from onek a
+ group by ten;
+
+ select ten, sum(distinct four) filter (where four > 10) from onek a
+ group by ten
+ having exists (select 1 from onek b where sum(distinct a.four) = b.four);
+
+ select max(foo COLLATE "C") filter (where (bar collate "POSIX") > '0') from (values ('a', 'b')) AS v(foo,bar);
+
+ -- outer-level aggregates
+ select
+ (select max((select i.unique2 from tenk1 i where i.unique1 = o.unique1)) filter (where o.unique1 < 10))
+ from tenk1 o;
+
+ -- exercise lots of aggregate parts with FILTER
+
+ select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1)
+ from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+ generate_series(1,2) i;
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
***************
*** 264,268 **** SELECT ntile(0) OVER (ORDER BY ten), ten, four FROM tenk1;
--- 264,276 ----
SELECT nth_value(four, 0) OVER (ORDER BY ten), ten, four FROM tenk1;
+ -- filter
+
+ SELECT sum(salary), row_number() OVER (ORDER BY depname), sum(
+ sum(salary) FILTER (WHERE enroll_date > '2007-01-01')
+ ) FILTER (WHERE depname <> 'sales') OVER (ORDER BY depname DESC) AS "filtered_sum",
+ depname
+ FROM empsalary GROUP BY depname;
+
-- cleanup
DROP TABLE empsalary;
On Sun, Apr 28, 2013 at 01:29:41PM -0700, David Fetter wrote:
On Tue, Feb 26, 2013 at 01:09:30PM -0800, David Fetter wrote:
On Wed, Feb 13, 2013 at 06:45:31AM -0800, David Fetter wrote:
On Sat, Feb 09, 2013 at 11:59:22PM -0800, David Fetter wrote:
Folks,
Per suggestions and lots of help from Andrew Gierth, please find
attached a patch to clean up the call sites for FuncCall nodes, which
I'd like to expand centrally rather than in each of the 37 (or 38, but
I only redid 37) places where it's called. The remaining one is in
src/backend/nodes/copyfuncs.c, which has to be modified for any
changes in the that struct anyhow.The immediate purpose is two-fold: to reduce some redundancies, which
I believe is worth doing in and of itself, and to prepare for adding
FILTER on aggregates from the spec, and possibly other things in
the <aggregate function> part of the spec.Cheers,
David.Folks,
Please find attached two versions of a patch which provides optional
FILTER clause for aggregates (T612, "Advanced OLAP operations").The first is intended to be applied on top of the previous patch, the
second without it. The first is, I believe, clearer in what it's
doing. Rather than simply mechanically visiting every place a
function call might be constructed, it visits a central one to change
the default, then goes only to the places where it's relevant.The patches are both early WIP as they contain no docs or regression
tests yet.Docs and regression tests added, makeFuncArgs approached dropped for
now, will re-visit later.Regression tests added to reflect bug fixes in COLLATE.
Rebased vs. master.
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
Attachments:
filter_005.difftext/plain; charset=us-asciiDownload
*** a/doc/src/sgml/ref/select.sgml
--- b/doc/src/sgml/ref/select.sgml
***************
*** 594,603 **** GROUP BY <replaceable class="parameter">expression</replaceable> [, ...]
</para>
<para>
! Aggregate functions, if any are used, are computed across all rows
making up each group, producing a separate value for each group
(whereas without <literal>GROUP BY</literal>, an aggregate
produces a single value computed across all the selected rows).
When <literal>GROUP BY</literal> is present, it is not valid for
the <command>SELECT</command> list expressions to refer to
ungrouped columns except within aggregate functions or if the
--- 594,606 ----
</para>
<para>
! In the absence of a <literal>FILTER</literal> clause,
! aggregate functions, if any are used, are computed across all rows
making up each group, producing a separate value for each group
(whereas without <literal>GROUP BY</literal>, an aggregate
produces a single value computed across all the selected rows).
+ When a <literal>FILTER</literal> clause is present, only those
+ rows matching the FILTER clause are included.
When <literal>GROUP BY</literal> is present, it is not valid for
the <command>SELECT</command> list expressions to refer to
ungrouped columns except within aggregate functions or if the
*** a/doc/src/sgml/syntax.sgml
--- b/doc/src/sgml/syntax.sgml
***************
*** 1562,1585 **** sqrt(2)
syntax of an aggregate expression is one of the following:
<synopsis>
! <replaceable>aggregate_name</replaceable> (<replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] )
! <replaceable>aggregate_name</replaceable> (ALL <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] )
! <replaceable>aggregate_name</replaceable> (DISTINCT <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] )
! <replaceable>aggregate_name</replaceable> ( * )
</synopsis>
where <replaceable>aggregate_name</replaceable> is a previously
defined aggregate (possibly qualified with a schema name),
! <replaceable>expression</replaceable> is
! any value expression that does not itself contain an aggregate
! expression or a window function call, and
! <replaceable>order_by_clause</replaceable> is a optional
! <literal>ORDER BY</> clause as described below.
</para>
<para>
! The first form of aggregate expression invokes the aggregate
! once for each input row.
The second form is the same as the first, since
<literal>ALL</literal> is the default.
The third form invokes the aggregate once for each distinct value
--- 1562,1587 ----
syntax of an aggregate expression is one of the following:
<synopsis>
! <replaceable>aggregate_name</replaceable> (<replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
! <replaceable>aggregate_name</replaceable> (ALL <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
! <replaceable>aggregate_name</replaceable> (DISTINCT <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
! <replaceable>aggregate_name</replaceable> ( * ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
</synopsis>
where <replaceable>aggregate_name</replaceable> is a previously
defined aggregate (possibly qualified with a schema name),
! <replaceable>expression</replaceable> is any value expression that
! does not itself contain an aggregate expression or a window
! function call, <replaceable>order_by_clause</replaceable> is a
! optional <literal>ORDER BY</> clause as described below. The
! <replaceable>aggregate_name</replaceable> can also be suffixed
! with <literal>FILTER</literal> as described below.
</para>
<para>
! The first form of aggregate expression invokes the aggregate once
! for each input row, or when a FILTER clause is present, each row
! matching same.
The second form is the same as the first, since
<literal>ALL</literal> is the default.
The third form invokes the aggregate once for each distinct value
***************
*** 1607,1612 **** sqrt(2)
--- 1609,1629 ----
</para>
<para>
+ Adding a FILTER clause to an aggregate specifies which values of
+ the expression being aggregated to evaluate. For example:
+ <programlisting>
+ SELECT
+ count(*) AS unfiltered,
+ count(*) FILTER (WHERE i < 5) AS filtered
+ FROM generate_series(1,10) AS s(i);
+ unfiltered | filtered
+ ------------+----------
+ 10 | 4
+ (1 row)
+ </programlisting>
+ </para>
+
+ <para>
Ordinarily, the input rows are fed to the aggregate function in an
unspecified order. In many cases this does not matter; for example,
<function>min</> produces the same result no matter what order it
***************
*** 1709,1718 **** SELECT string_agg(a ORDER BY a, ',') FROM table; -- incorrect
The syntax of a window function call is one of the following:
<synopsis>
! <replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) OVER ( <replaceable class="parameter">window_definition</replaceable> )
! <replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) OVER <replaceable>window_name</replaceable>
! <replaceable>function_name</replaceable> ( * ) OVER ( <replaceable class="parameter">window_definition</replaceable> )
! <replaceable>function_name</replaceable> ( * ) OVER <replaceable>window_name</replaceable>
</synopsis>
where <replaceable class="parameter">window_definition</replaceable>
has the syntax
--- 1726,1735 ----
The syntax of a window function call is one of the following:
<synopsis>
! <replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER ( <replaceable class="parameter">window_definition</replaceable> )
! <replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER <replaceable>window_name</replaceable>
! <replaceable>function_name</replaceable> ( * ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER ( <replaceable class="parameter">window_definition</replaceable> )
! <replaceable>function_name</replaceable> ( * ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER <replaceable>window_name</replaceable>
</synopsis>
where <replaceable class="parameter">window_definition</replaceable>
has the syntax
***************
*** 1836,1851 **** UNBOUNDED FOLLOWING
The built-in window functions are described in <xref
linkend="functions-window-table">. Other window functions can be added by
the user. Also, any built-in or user-defined aggregate function can be
! used as a window function.
</para>
<para>
! The syntaxes using <literal>*</> are used for calling parameter-less
! aggregate functions as window functions, for example
! <literal>count(*) OVER (PARTITION BY x ORDER BY y)</>.
! The asterisk (<literal>*</>) is customarily not used for non-aggregate window functions.
! Aggregate window functions, unlike normal aggregate functions, do not
! allow <literal>DISTINCT</> or <literal>ORDER BY</> to be used within the
function argument list.
</para>
--- 1853,1870 ----
The built-in window functions are described in <xref
linkend="functions-window-table">. Other window functions can be added by
the user. Also, any built-in or user-defined aggregate function can be
! used as a window function. A <literal>FILTER</literal> clause is
! only valid for aggregate functions used in windowing.
</para>
<para>
! The syntaxes using <literal>*</> are used for calling
! parameter-less aggregate functions as window functions, for
! example <literal>count(*) OVER (PARTITION BY x ORDER BY y)</>.
! The asterisk (<literal>*</>) is customarily not used for
! non-aggregate window functions. Aggregate window functions,
! unlike normal aggregate functions, do not allow
! <literal>DISTINCT</> or <literal>ORDER BY</> to be used within the
function argument list.
</para>
*** a/src/backend/executor/execQual.c
--- b/src/backend/executor/execQual.c
***************
*** 4410,4415 **** ExecInitExpr(Expr *node, PlanState *parent)
--- 4410,4416 ----
astate->args = (List *) ExecInitExpr((Expr *) aggref->args,
parent);
+ astate->agg_filter = ExecInitExpr(aggref->agg_filter, parent);
/*
* Complain if the aggregate's arguments contain any
***************
*** 4448,4453 **** ExecInitExpr(Expr *node, PlanState *parent)
--- 4449,4455 ----
wfstate->args = (List *) ExecInitExpr((Expr *) wfunc->args,
parent);
+ wfstate->agg_filter = ExecInitExpr(wfunc->agg_filter, parent);
/*
* Complain if the windowfunc's arguments contain any
*** a/src/backend/executor/functions.c
--- b/src/backend/executor/functions.c
***************
*** 381,387 **** sql_fn_post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var)
list_make1(subfield),
list_make1(param),
NIL, false, false, false,
! NULL, true, cref->location);
}
return param;
--- 381,387 ----
list_make1(subfield),
list_make1(param),
NIL, false, false, false,
! NULL, NULL, true, cref->location);
}
return param;
*** a/src/backend/executor/nodeAgg.c
--- b/src/backend/executor/nodeAgg.c
***************
*** 488,493 **** advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
--- 488,505 ----
int i;
TupleTableSlot *slot;
+ /* Skip anything FILTERed out */
+ ExprState *filter = peraggstate->aggrefstate->agg_filter;
+ if (filter)
+ {
+ MemoryContext oldcontext = MemoryContextSwitchTo(aggstate->tmpcontext->ecxt_per_tuple_memory);
+ bool isnull;
+ Datum res = ExecEvalExpr(filter, aggstate->tmpcontext, &isnull, NULL);
+ MemoryContextSwitchTo(oldcontext);
+ if (isnull || !DatumGetBool(res))
+ continue;
+ }
+
/* Evaluate the current input expressions for this aggregate */
slot = ExecProject(peraggstate->evalproj, NULL);
*** a/src/backend/executor/nodeWindowAgg.c
--- b/src/backend/executor/nodeWindowAgg.c
***************
*** 227,235 **** advance_windowaggregate(WindowAggState *winstate,
--- 227,248 ----
int i;
MemoryContext oldContext;
ExprContext *econtext = winstate->tmpcontext;
+ ExprState *filter = wfuncstate->agg_filter;
oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+ /* Skip anything FILTERed out */
+ if (filter)
+ {
+ bool isnull;
+ Datum res = ExecEvalExpr(filter, econtext, &isnull, NULL);
+ if (isnull || !DatumGetBool(res))
+ {
+ MemoryContextSwitchTo(oldContext);
+ return;
+ }
+ }
+
/* We start from 1, since the 0th arg will be the transition value */
i = 1;
foreach(arg, wfuncstate->args)
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 1137,1142 **** _copyAggref(const Aggref *from)
--- 1137,1143 ----
COPY_NODE_FIELD(args);
COPY_NODE_FIELD(aggorder);
COPY_NODE_FIELD(aggdistinct);
+ COPY_NODE_FIELD(agg_filter);
COPY_SCALAR_FIELD(aggstar);
COPY_SCALAR_FIELD(agglevelsup);
COPY_LOCATION_FIELD(location);
***************
*** 1157,1162 **** _copyWindowFunc(const WindowFunc *from)
--- 1158,1164 ----
COPY_SCALAR_FIELD(wincollid);
COPY_SCALAR_FIELD(inputcollid);
COPY_NODE_FIELD(args);
+ COPY_NODE_FIELD(agg_filter);
COPY_SCALAR_FIELD(winref);
COPY_SCALAR_FIELD(winstar);
COPY_SCALAR_FIELD(winagg);
***************
*** 2155,2160 **** _copyFuncCall(const FuncCall *from)
--- 2157,2163 ----
COPY_SCALAR_FIELD(agg_star);
COPY_SCALAR_FIELD(agg_distinct);
COPY_SCALAR_FIELD(func_variadic);
+ COPY_NODE_FIELD(agg_filter);
COPY_NODE_FIELD(over);
COPY_LOCATION_FIELD(location);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 196,201 **** _equalAggref(const Aggref *a, const Aggref *b)
--- 196,202 ----
COMPARE_NODE_FIELD(args);
COMPARE_NODE_FIELD(aggorder);
COMPARE_NODE_FIELD(aggdistinct);
+ COMPARE_NODE_FIELD(agg_filter);
COMPARE_SCALAR_FIELD(aggstar);
COMPARE_SCALAR_FIELD(agglevelsup);
COMPARE_LOCATION_FIELD(location);
***************
*** 211,216 **** _equalWindowFunc(const WindowFunc *a, const WindowFunc *b)
--- 212,218 ----
COMPARE_SCALAR_FIELD(wincollid);
COMPARE_SCALAR_FIELD(inputcollid);
COMPARE_NODE_FIELD(args);
+ COMPARE_NODE_FIELD(agg_filter);
COMPARE_SCALAR_FIELD(winref);
COMPARE_SCALAR_FIELD(winstar);
COMPARE_SCALAR_FIELD(winagg);
***************
*** 1995,2000 **** _equalFuncCall(const FuncCall *a, const FuncCall *b)
--- 1997,2003 ----
COMPARE_SCALAR_FIELD(agg_star);
COMPARE_SCALAR_FIELD(agg_distinct);
COMPARE_SCALAR_FIELD(func_variadic);
+ COMPARE_NODE_FIELD(agg_filter);
COMPARE_NODE_FIELD(over);
COMPARE_LOCATION_FIELD(location);
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
***************
*** 1570,1575 **** expression_tree_walker(Node *node,
--- 1570,1578 ----
if (expression_tree_walker((Node *) expr->aggdistinct,
walker, context))
return true;
+ if (expression_tree_walker((Node *) expr->agg_filter,
+ walker, context))
+ return true;
}
break;
case T_WindowFunc:
***************
*** 1580,1585 **** expression_tree_walker(Node *node,
--- 1583,1591 ----
if (expression_tree_walker((Node *) expr->args,
walker, context))
return true;
+ if (expression_tree_walker((Node *) expr->agg_filter,
+ walker, context))
+ return true;
}
break;
case T_ArrayRef:
***************
*** 2079,2084 **** expression_tree_mutator(Node *node,
--- 2085,2091 ----
MUTATE(newnode->args, aggref->args, List *);
MUTATE(newnode->aggorder, aggref->aggorder, List *);
MUTATE(newnode->aggdistinct, aggref->aggdistinct, List *);
+ MUTATE(newnode->agg_filter, aggref->agg_filter, Expr *);
return (Node *) newnode;
}
break;
***************
*** 2089,2094 **** expression_tree_mutator(Node *node,
--- 2096,2102 ----
FLATCOPY(newnode, wfunc, WindowFunc);
MUTATE(newnode->args, wfunc->args, List *);
+ MUTATE(newnode->agg_filter, wfunc->agg_filter, Expr *);
return (Node *) newnode;
}
break;
***************
*** 2951,2956 **** raw_expression_tree_walker(Node *node,
--- 2959,2966 ----
return true;
if (walker(fcall->agg_order, context))
return true;
+ if (walker(fcall->agg_filter, context))
+ return true;
if (walker(fcall->over, context))
return true;
/* function name is deemed uninteresting */
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 958,963 **** _outAggref(StringInfo str, const Aggref *node)
--- 958,964 ----
WRITE_NODE_FIELD(args);
WRITE_NODE_FIELD(aggorder);
WRITE_NODE_FIELD(aggdistinct);
+ WRITE_NODE_FIELD(agg_filter);
WRITE_BOOL_FIELD(aggstar);
WRITE_UINT_FIELD(agglevelsup);
WRITE_LOCATION_FIELD(location);
***************
*** 973,978 **** _outWindowFunc(StringInfo str, const WindowFunc *node)
--- 974,980 ----
WRITE_OID_FIELD(wincollid);
WRITE_OID_FIELD(inputcollid);
WRITE_NODE_FIELD(args);
+ WRITE_NODE_FIELD(agg_filter);
WRITE_UINT_FIELD(winref);
WRITE_BOOL_FIELD(winstar);
WRITE_BOOL_FIELD(winagg);
***************
*** 2083,2088 **** _outFuncCall(StringInfo str, const FuncCall *node)
--- 2085,2091 ----
WRITE_BOOL_FIELD(agg_star);
WRITE_BOOL_FIELD(agg_distinct);
WRITE_BOOL_FIELD(func_variadic);
+ WRITE_NODE_FIELD(agg_filter);
WRITE_NODE_FIELD(over);
WRITE_LOCATION_FIELD(location);
}
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 479,484 **** _readAggref(void)
--- 479,485 ----
READ_NODE_FIELD(args);
READ_NODE_FIELD(aggorder);
READ_NODE_FIELD(aggdistinct);
+ READ_NODE_FIELD(agg_filter);
READ_BOOL_FIELD(aggstar);
READ_UINT_FIELD(agglevelsup);
READ_LOCATION_FIELD(location);
***************
*** 499,504 **** _readWindowFunc(void)
--- 500,506 ----
READ_OID_FIELD(wincollid);
READ_OID_FIELD(inputcollid);
READ_NODE_FIELD(args);
+ READ_NODE_FIELD(agg_filter);
READ_UINT_FIELD(winref);
READ_BOOL_FIELD(winstar);
READ_BOOL_FIELD(winagg);
*** a/src/backend/optimizer/plan/planagg.c
--- b/src/backend/optimizer/plan/planagg.c
***************
*** 314,320 **** find_minmax_aggs_walker(Node *node, List **context)
ListCell *l;
Assert(aggref->agglevelsup == 0);
! if (list_length(aggref->args) != 1 || aggref->aggorder != NIL)
return true; /* it couldn't be MIN/MAX */
/* note: we do not care if DISTINCT is mentioned ... */
curTarget = (TargetEntry *) linitial(aggref->args);
--- 314,320 ----
ListCell *l;
Assert(aggref->agglevelsup == 0);
! if (list_length(aggref->args) != 1 || aggref->aggorder != NIL || aggref->agg_filter != NULL)
return true; /* it couldn't be MIN/MAX */
/* note: we do not care if DISTINCT is mentioned ... */
curTarget = (TargetEntry *) linitial(aggref->args);
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 490,495 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
--- 490,496 ----
opt_frame_clause frame_extent frame_bound
%type <str> opt_existing_window_name
%type <boolean> opt_if_not_exists
+ %type <node> filter_clause
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
***************
*** 536,542 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
EXTENSION EXTERNAL EXTRACT
! FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
FREEZE FROM FULL FUNCTION FUNCTIONS
GLOBAL GRANT GRANTED GREATEST GROUP_P
--- 537,543 ----
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
EXTENSION EXTERNAL EXTRACT
! FALSE_P FAMILY FETCH FILTER FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
FREEZE FROM FULL FUNCTION FUNCTIONS
GLOBAL GRANT GRANTED GREATEST GROUP_P
***************
*** 11087,11093 **** c_expr: columnref { $$ = $1; }
* (Note that many of the special SQL functions wouldn't actually make any
* sense as functional index entries, but we ignore that consideration here.)
*/
! func_expr: func_name '(' ')' over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
--- 11088,11094 ----
* (Note that many of the special SQL functions wouldn't actually make any
* sense as functional index entries, but we ignore that consideration here.)
*/
! func_expr: func_name '(' ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
***************
*** 11096,11119 **** func_expr: func_name '(' ')' over_clause
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
! n->over = $4;
! n->location = @1;
! $$ = (Node *)n;
! }
! | func_name '(' func_arg_list ')' over_clause
! {
! FuncCall *n = makeNode(FuncCall);
! n->funcname = $1;
! n->args = $3;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
n->over = $5;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' VARIADIC func_arg_expr ')' over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
--- 11097,11122 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
! n->agg_filter = $4;
n->over = $5;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' func_arg_list ')' filter_clause over_clause
! {
! FuncCall *n = makeNode(FuncCall);
! n->funcname = $1;
! n->args = $3;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->agg_filter = $5;
! n->over = $6;
! n->location = @1;
! $$ = (Node *)n;
! }
! | func_name '(' VARIADIC func_arg_expr ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
***************
*** 11122,11132 **** func_expr: func_name '(' ')' over_clause
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = TRUE;
! n->over = $6;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' func_arg_list ',' VARIADIC func_arg_expr ')' over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
--- 11125,11136 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = TRUE;
! n->agg_filter = $6;
! n->over = $7;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' func_arg_list ',' VARIADIC func_arg_expr ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
***************
*** 11135,11145 **** func_expr: func_name '(' ')' over_clause
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = TRUE;
! n->over = $8;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' func_arg_list sort_clause ')' over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
--- 11139,11150 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = TRUE;
! n->agg_filter = $8;
! n->over = $9;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' func_arg_list sort_clause ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
***************
*** 11148,11158 **** func_expr: func_name '(' ')' over_clause
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
! n->over = $6;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' ALL func_arg_list opt_sort_clause ')' over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
--- 11153,11164 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
! n->agg_filter = $6;
! n->over = $7;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' ALL func_arg_list opt_sort_clause ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
***************
*** 11165,11175 **** func_expr: func_name '(' ')' over_clause
* for that in FuncCall at the moment.
*/
n->func_variadic = FALSE;
! n->over = $7;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' DISTINCT func_arg_list opt_sort_clause ')' over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
--- 11171,11182 ----
* for that in FuncCall at the moment.
*/
n->func_variadic = FALSE;
! n->agg_filter = $7;
! n->over = $8;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' DISTINCT func_arg_list opt_sort_clause ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
***************
*** 11178,11188 **** func_expr: func_name '(' ')' over_clause
n->agg_star = FALSE;
n->agg_distinct = TRUE;
n->func_variadic = FALSE;
! n->over = $7;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' '*' ')' over_clause
{
/*
* We consider AGGREGATE(*) to invoke a parameterless
--- 11185,11196 ----
n->agg_star = FALSE;
n->agg_distinct = TRUE;
n->func_variadic = FALSE;
! n->agg_filter = $7;
! n->over = $8;
n->location = @1;
$$ = (Node *)n;
}
! | func_name '(' '*' ')' filter_clause over_clause
{
/*
* We consider AGGREGATE(*) to invoke a parameterless
***************
*** 11201,11207 **** func_expr: func_name '(' ')' over_clause
n->agg_star = TRUE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
! n->over = $5;
n->location = @1;
$$ = (Node *)n;
}
--- 11209,11216 ----
n->agg_star = TRUE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
! n->agg_filter = $5;
! n->over = $6;
n->location = @1;
$$ = (Node *)n;
}
***************
*** 11735,11741 **** xmlexists_argument:
window_clause:
WINDOW window_definition_list { $$ = $2; }
| /*EMPTY*/ { $$ = NIL; }
! ;
window_definition_list:
window_definition { $$ = list_make1($1); }
--- 11744,11750 ----
window_clause:
WINDOW window_definition_list { $$ = $2; }
| /*EMPTY*/ { $$ = NIL; }
! ;
window_definition_list:
window_definition { $$ = list_make1($1); }
***************
*** 11752,11757 **** window_definition:
--- 11761,11771 ----
}
;
+ filter_clause:
+ FILTER '(' WHERE a_expr ')' { $$ = $4; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
over_clause: OVER window_specification
{ $$ = $2; }
| OVER ColId
***************
*** 12980,12985 **** type_func_name_keyword:
--- 12994,13000 ----
| CONCURRENTLY
| CROSS
| CURRENT_SCHEMA
+ | FILTER
| FREEZE
| FULL
| ILIKE
*** a/src/backend/parser/parse_agg.c
--- b/src/backend/parser/parse_agg.c
***************
*** 44,50 **** typedef struct
int sublevels_up;
} check_ungrouped_columns_context;
! static int check_agg_arguments(ParseState *pstate, List *args);
static bool check_agg_arguments_walker(Node *node,
check_agg_arguments_context *context);
static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
--- 44,50 ----
int sublevels_up;
} check_ungrouped_columns_context;
! static int check_agg_arguments(ParseState *pstate, List *args, Expr *filter);
static bool check_agg_arguments_walker(Node *node,
check_agg_arguments_context *context);
static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
***************
*** 160,166 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
* Check the arguments to compute the aggregate's level and detect
* improper nesting.
*/
! min_varlevel = check_agg_arguments(pstate, agg->args);
agg->agglevelsup = min_varlevel;
/* Mark the correct pstate level as having aggregates */
--- 160,166 ----
* Check the arguments to compute the aggregate's level and detect
* improper nesting.
*/
! min_varlevel = check_agg_arguments(pstate, agg->args, agg->agg_filter);
agg->agglevelsup = min_varlevel;
/* Mark the correct pstate level as having aggregates */
***************
*** 207,212 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
--- 207,215 ----
case EXPR_KIND_HAVING:
/* okay */
break;
+ case EXPR_KIND_FILTER:
+ errkind = true;
+ break;
case EXPR_KIND_WINDOW_PARTITION:
/* okay */
break;
***************
*** 309,315 **** transformAggregateCall(ParseState *pstate, Aggref *agg,
* which we can't know until we finish scanning the arguments.
*/
static int
! check_agg_arguments(ParseState *pstate, List *args)
{
int agglevel;
check_agg_arguments_context context;
--- 312,318 ----
* which we can't know until we finish scanning the arguments.
*/
static int
! check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
{
int agglevel;
check_agg_arguments_context context;
***************
*** 323,328 **** check_agg_arguments(ParseState *pstate, List *args)
--- 326,335 ----
check_agg_arguments_walker,
(void *) &context);
+ (void) expression_tree_walker((Node *) filter,
+ check_agg_arguments_walker,
+ (void *) &context);
+
/*
* If we found no vars nor aggs at all, it's a level-zero aggregate;
* otherwise, its level is the minimum of vars or aggs.
***************
*** 481,486 **** transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
--- 488,496 ----
case EXPR_KIND_HAVING:
errkind = true;
break;
+ case EXPR_KIND_FILTER:
+ errkind = true;
+ break;
case EXPR_KIND_WINDOW_PARTITION:
case EXPR_KIND_WINDOW_ORDER:
case EXPR_KIND_WINDOW_FRAME_RANGE:
*** a/src/backend/parser/parse_collate.c
--- b/src/backend/parser/parse_collate.c
***************
*** 575,580 **** assign_collations_walker(Node *node, assign_collations_context *context)
--- 575,584 ----
* the case above for T_TargetEntry will apply
* appropriate checks to agg ORDER BY items.
*
+ * Likewise, we assign collations for the (bool)
+ * expression in agg_filter, independently of
+ * any other args.
+ *
* We need not recurse into the aggorder or
* aggdistinct lists, because those contain only
* SortGroupClause nodes which we need not
***************
*** 595,600 **** assign_collations_walker(Node *node, assign_collations_context *context)
--- 599,620 ----
(void) assign_collations_walker((Node *) tle,
&loccontext);
}
+
+ assign_expr_collations(context->pstate, (Node *) aggref->agg_filter);
+ }
+ break;
+ case T_WindowFunc:
+ {
+ /*
+ * WindowFunc requires special processing only for
+ * its agg_filter clause, as for aggregates.
+ */
+ WindowFunc *wfunc = (WindowFunc *) node;
+
+ (void) assign_collations_walker((Node *) wfunc->args,
+ &loccontext);
+
+ assign_expr_collations(context->pstate, (Node *) wfunc->agg_filter);
}
break;
case T_CaseExpr:
*** a/src/backend/parser/parse_expr.c
--- b/src/backend/parser/parse_expr.c
***************
*** 22,27 ****
--- 22,28 ----
#include "nodes/nodeFuncs.h"
#include "optimizer/var.h"
#include "parser/analyze.h"
+ #include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
***************
*** 463,469 **** transformIndirection(ParseState *pstate, Node *basenode, List *indirection)
list_make1(n),
list_make1(result),
NIL, false, false, false,
! NULL, true, location);
if (newresult == NULL)
unknown_attribute(pstate, result, strVal(n), location);
result = newresult;
--- 464,470 ----
list_make1(n),
list_make1(result),
NIL, false, false, false,
! NULL, NULL, true, location);
if (newresult == NULL)
unknown_attribute(pstate, result, strVal(n), location);
result = newresult;
***************
*** 631,637 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, true, cref->location);
}
break;
}
--- 632,638 ----
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, NULL, true, cref->location);
}
break;
}
***************
*** 676,682 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, true, cref->location);
}
break;
}
--- 677,683 ----
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, NULL, true, cref->location);
}
break;
}
***************
*** 734,740 **** transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, true, cref->location);
}
break;
}
--- 735,741 ----
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
! NULL, NULL, true, cref->location);
}
break;
}
***************
*** 1241,1246 **** transformFuncCall(ParseState *pstate, FuncCall *fn)
--- 1242,1248 ----
{
List *targs;
ListCell *args;
+ Expr *tagg_filter;
/* Transform the list of arguments ... */
targs = NIL;
***************
*** 1250,1255 **** transformFuncCall(ParseState *pstate, FuncCall *fn)
--- 1252,1263 ----
(Node *) lfirst(args)));
}
+ /* Transform the aggregate filter using transformWhereClause, to
+ * which FILTER is virually identical... */
+ tagg_filter = NULL;
+ if (fn->agg_filter != NULL)
+ tagg_filter = (Expr *)transformWhereClause(pstate, (Node *)fn->agg_filter, EXPR_KIND_FILTER, "FILTER");
+
/* ... and hand off to ParseFuncOrColumn */
return ParseFuncOrColumn(pstate,
fn->funcname,
***************
*** 1258,1263 **** transformFuncCall(ParseState *pstate, FuncCall *fn)
--- 1266,1272 ----
fn->agg_star,
fn->agg_distinct,
fn->func_variadic,
+ tagg_filter,
fn->over,
false,
fn->location);
***************
*** 1430,1435 **** transformSubLink(ParseState *pstate, SubLink *sublink)
--- 1439,1445 ----
case EXPR_KIND_FROM_FUNCTION:
case EXPR_KIND_WHERE:
case EXPR_KIND_HAVING:
+ case EXPR_KIND_FILTER:
case EXPR_KIND_WINDOW_PARTITION:
case EXPR_KIND_WINDOW_ORDER:
case EXPR_KIND_WINDOW_FRAME_RANGE:
***************
*** 2579,2584 **** ParseExprKindName(ParseExprKind exprKind)
--- 2589,2596 ----
return "WHERE";
case EXPR_KIND_HAVING:
return "HAVING";
+ case EXPR_KIND_FILTER:
+ return "FILTER";
case EXPR_KIND_WINDOW_PARTITION:
return "window PARTITION BY";
case EXPR_KIND_WINDOW_ORDER:
*** a/src/backend/parser/parse_func.c
--- b/src/backend/parser/parse_func.c
***************
*** 63,69 **** Node *
ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
List *agg_order, bool agg_star, bool agg_distinct,
bool func_variadic,
! WindowDef *over, bool is_column, int location)
{
Oid rettype;
Oid funcid;
--- 63,69 ----
ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
List *agg_order, bool agg_star, bool agg_distinct,
bool func_variadic,
! Expr *agg_filter, WindowDef *over, bool is_column, int location)
{
Oid rettype;
Oid funcid;
***************
*** 175,181 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
* wasn't any aggregate or variadic decoration, nor an argument name.
*/
if (nargs == 1 && agg_order == NIL && !agg_star && !agg_distinct &&
! over == NULL && !func_variadic && argnames == NIL &&
list_length(funcname) == 1)
{
Oid argtype = actual_arg_types[0];
--- 175,181 ----
* wasn't any aggregate or variadic decoration, nor an argument name.
*/
if (nargs == 1 && agg_order == NIL && !agg_star && !agg_distinct &&
! agg_filter == NULL && over == NULL && !func_variadic && argnames == NIL &&
list_length(funcname) == 1)
{
Oid argtype = actual_arg_types[0];
***************
*** 251,256 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 251,262 ----
errmsg("ORDER BY specified, but %s is not an aggregate function",
NameListToString(funcname)),
parser_errposition(pstate, location)));
+ if (agg_filter)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("FILTER specified, but %s is not an aggregate function",
+ NameListToString(funcname)),
+ parser_errposition(pstate, location)));
if (over)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
***************
*** 402,407 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 408,415 ----
/* aggcollid and inputcollid will be set by parse_collate.c */
/* args, aggorder, aggdistinct will be set by transformAggregateCall */
aggref->aggstar = agg_star;
+ /* filter */
+ aggref->agg_filter = agg_filter;
/* agglevelsup will be set by transformAggregateCall */
aggref->location = location;
***************
*** 460,465 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 468,474 ----
/* winref will be set by transformWindowFuncCall */
wfunc->winstar = agg_star;
wfunc->winagg = (fdresult == FUNCDETAIL_AGGREGATE);
+ wfunc->agg_filter = agg_filter;
wfunc->location = location;
/*
***************
*** 483,488 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
--- 492,507 ----
parser_errposition(pstate, location)));
/*
+ * Reject window functions which are not aggregates in the
+ * case of FILTER.
+ */
+ if (!wfunc->winagg && agg_filter)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("FILTER is not implemented in non-aggregate window functions"),
+ parser_errposition(pstate, location)));
+
+ /*
* ordered aggs not allowed in windows yet
*/
if (agg_order != NIL)
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 7424,7430 **** get_agg_expr(Aggref *aggref, deparse_context *context)
--- 7424,7438 ----
appendStringInfoString(buf, " ORDER BY ");
get_rule_orderby(aggref->aggorder, aggref->args, false, context);
}
+
+ if (aggref->agg_filter != NULL)
+ {
+ appendStringInfoString(buf, ") FILTER (WHERE ");
+ get_rule_expr((Node *)aggref->agg_filter, context, false);
+ }
+
appendStringInfoChar(buf, ')');
+
}
/*
***************
*** 7461,7466 **** get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context)
--- 7469,7481 ----
appendStringInfoChar(buf, '*');
else
get_rule_expr((Node *) wfunc->args, context, true);
+
+ if (wfunc->agg_filter != NULL)
+ {
+ appendStringInfoString(buf, ") FILTER (WHERE ");
+ get_rule_expr((Node *)wfunc->agg_filter, context, false);
+ }
+
appendStringInfoString(buf, ") OVER ");
foreach(l, context->windowClause)
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 584,589 **** typedef struct AggrefExprState
--- 584,590 ----
{
ExprState xprstate;
List *args; /* states of argument expressions */
+ ExprState *agg_filter; /* FILTER expression */
int aggno; /* ID number for agg within its plan node */
} AggrefExprState;
***************
*** 595,600 **** typedef struct WindowFuncExprState
--- 596,602 ----
{
ExprState xprstate;
List *args; /* states of argument expressions */
+ ExprState *agg_filter; /* FILTER expression */
int wfuncno; /* ID number for wfunc within its plan node */
} WindowFuncExprState;
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 295,300 **** typedef struct FuncCall
--- 295,301 ----
bool agg_star; /* argument was really '*' */
bool agg_distinct; /* arguments were labeled DISTINCT */
bool func_variadic; /* last argument was labeled VARIADIC */
+ Node *agg_filter; /* FILTER clause, if any */
struct WindowDef *over; /* OVER clause, if any */
int location; /* token location, or -1 if unknown */
} FuncCall;
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
***************
*** 247,252 **** typedef struct Aggref
--- 247,253 ----
List *args; /* arguments and sort expressions */
List *aggorder; /* ORDER BY (list of SortGroupClause) */
List *aggdistinct; /* DISTINCT (list of SortGroupClause) */
+ Expr *agg_filter; /* FILTER expression */
bool aggstar; /* TRUE if argument list was really '*' */
Index agglevelsup; /* > 0 if agg belongs to outer query */
int location; /* token location, or -1 if unknown */
***************
*** 263,268 **** typedef struct WindowFunc
--- 264,270 ----
Oid wincollid; /* OID of collation of result */
Oid inputcollid; /* OID of collation that function should use */
List *args; /* arguments to the window function */
+ Expr *agg_filter; /* FILTER expression */
Index winref; /* index of associated WindowClause */
bool winstar; /* TRUE if argument list was really '*' */
bool winagg; /* is function a simple aggregate? */
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 155,160 **** PG_KEYWORD("extract", EXTRACT, COL_NAME_KEYWORD)
--- 155,161 ----
PG_KEYWORD("false", FALSE_P, RESERVED_KEYWORD)
PG_KEYWORD("family", FAMILY, UNRESERVED_KEYWORD)
PG_KEYWORD("fetch", FETCH, RESERVED_KEYWORD)
+ PG_KEYWORD("filter", FILTER, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("first", FIRST_P, UNRESERVED_KEYWORD)
PG_KEYWORD("float", FLOAT_P, COL_NAME_KEYWORD)
PG_KEYWORD("following", FOLLOWING, UNRESERVED_KEYWORD)
*** a/src/include/parser/parse_func.h
--- b/src/include/parser/parse_func.h
***************
*** 46,52 **** extern Node *ParseFuncOrColumn(ParseState *pstate,
List *funcname, List *fargs,
List *agg_order, bool agg_star, bool agg_distinct,
bool func_variadic,
! WindowDef *over, bool is_column, int location);
extern FuncDetailCode func_get_detail(List *funcname,
List *fargs, List *fargnames,
--- 46,52 ----
List *funcname, List *fargs,
List *agg_order, bool agg_star, bool agg_distinct,
bool func_variadic,
! Expr *agg_filter, WindowDef *over, bool is_column, int location);
extern FuncDetailCode func_get_detail(List *funcname,
List *fargs, List *fargnames,
*** a/src/include/parser/parse_node.h
--- b/src/include/parser/parse_node.h
***************
*** 39,44 **** typedef enum ParseExprKind
--- 39,45 ----
EXPR_KIND_FROM_FUNCTION, /* function in FROM clause */
EXPR_KIND_WHERE, /* WHERE */
EXPR_KIND_HAVING, /* HAVING */
+ EXPR_KIND_FILTER, /* FILTER */
EXPR_KIND_WINDOW_PARTITION, /* window definition PARTITION BY */
EXPR_KIND_WINDOW_ORDER, /* window definition ORDER BY */
EXPR_KIND_WINDOW_FRAME_RANGE, /* window frame clause with RANGE */
*** a/src/test/regress/expected/aggregates.out
--- b/src/test/regress/expected/aggregates.out
***************
*** 1154,1156 **** select string_agg(v, decode('ee', 'hex')) from bytea_test_table;
--- 1154,1215 ----
(1 row)
drop table bytea_test_table;
+ -- FILTER tests
+ select min(unique1) filter (where unique1 > 100) from tenk1;
+ min
+ -----
+ 101
+ (1 row)
+
+ select ten, sum(distinct four) filter (where four::text ~ '123') from onek a
+ group by ten;
+ ten | sum
+ -----+-----
+ 0 |
+ 1 |
+ 2 |
+ 3 |
+ 4 |
+ 5 |
+ 6 |
+ 7 |
+ 8 |
+ 9 |
+ (10 rows)
+
+ select ten, sum(distinct four) filter (where four > 10) from onek a
+ group by ten
+ having exists (select 1 from onek b where sum(distinct a.four) = b.four);
+ ten | sum
+ -----+-----
+ 0 |
+ 2 |
+ 4 |
+ 6 |
+ 8 |
+ (5 rows)
+
+ select max(foo COLLATE "C") filter (where (bar collate "POSIX") > '0') from (values ('a', 'b')) AS v(foo,bar);
+ max
+ -----
+ a
+ (1 row)
+
+ -- outer-level aggregates
+ select
+ (select max((select i.unique2 from tenk1 i where i.unique1 = o.unique1)) filter (where o.unique1 < 10))
+ from tenk1 o;
+ max
+ ------
+ 9998
+ (1 row)
+
+ -- exercise lots of aggregate parts with FILTER
+ select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1)
+ from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+ generate_series(1,2) i;
+ aggfns
+ ---------------------------
+ {"(2,2,bar)","(3,1,baz)"}
+ (1 row)
+
*** a/src/test/regress/expected/window.out
--- b/src/test/regress/expected/window.out
***************
*** 1020,1024 **** SELECT ntile(0) OVER (ORDER BY ten), ten, four FROM tenk1;
--- 1020,1037 ----
ERROR: argument of ntile must be greater than zero
SELECT nth_value(four, 0) OVER (ORDER BY ten), ten, four FROM tenk1;
ERROR: argument of nth_value must be greater than zero
+ -- filter
+ SELECT sum(salary), row_number() OVER (ORDER BY depname), sum(
+ sum(salary) FILTER (WHERE enroll_date > '2007-01-01')
+ ) FILTER (WHERE depname <> 'sales') OVER (ORDER BY depname DESC) AS "filtered_sum",
+ depname
+ FROM empsalary GROUP BY depname;
+ sum | row_number | filtered_sum | depname
+ -------+------------+--------------+-----------
+ 14600 | 3 | | sales
+ 7400 | 2 | 3500 | personnel
+ 25100 | 1 | 22600 | develop
+ (3 rows)
+
-- cleanup
DROP TABLE empsalary;
*** a/src/test/regress/sql/aggregates.sql
--- b/src/test/regress/sql/aggregates.sql
***************
*** 442,444 **** select string_agg(v, NULL) from bytea_test_table;
--- 442,468 ----
select string_agg(v, decode('ee', 'hex')) from bytea_test_table;
drop table bytea_test_table;
+
+ -- FILTER tests
+
+ select min(unique1) filter (where unique1 > 100) from tenk1;
+
+ select ten, sum(distinct four) filter (where four::text ~ '123') from onek a
+ group by ten;
+
+ select ten, sum(distinct four) filter (where four > 10) from onek a
+ group by ten
+ having exists (select 1 from onek b where sum(distinct a.four) = b.four);
+
+ select max(foo COLLATE "C") filter (where (bar collate "POSIX") > '0') from (values ('a', 'b')) AS v(foo,bar);
+
+ -- outer-level aggregates
+ select
+ (select max((select i.unique2 from tenk1 i where i.unique1 = o.unique1)) filter (where o.unique1 < 10))
+ from tenk1 o;
+
+ -- exercise lots of aggregate parts with FILTER
+
+ select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1)
+ from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+ generate_series(1,2) i;
*** a/src/test/regress/sql/window.sql
--- b/src/test/regress/sql/window.sql
***************
*** 264,268 **** SELECT ntile(0) OVER (ORDER BY ten), ten, four FROM tenk1;
--- 264,276 ----
SELECT nth_value(four, 0) OVER (ORDER BY ten), ten, four FROM tenk1;
+ -- filter
+
+ SELECT sum(salary), row_number() OVER (ORDER BY depname), sum(
+ sum(salary) FILTER (WHERE enroll_date > '2007-01-01')
+ ) FILTER (WHERE depname <> 'sales') OVER (ORDER BY depname DESC) AS "filtered_sum",
+ depname
+ FROM empsalary GROUP BY depname;
+
-- cleanup
DROP TABLE empsalary;
On Mon, Feb 11, 2013 at 10:48:38AM -0800, David Fetter wrote:
On Sun, Feb 10, 2013 at 10:09:19AM -0500, Tom Lane wrote:
David Fetter <david@fetter.org> writes:
Per suggestions and lots of help from Andrew Gierth, please find
attached a patch to clean up the call sites for FuncCall nodes, which
I'd like to expand centrally rather than in each of the 37 (or 38, but
I only redid 37) places where it's called. The remaining one is in
src/backend/nodes/copyfuncs.c, which has to be modified for any
changes in the that struct anyhow.TBH, I don't think this is an improvement.
The problem with adding a new field to any struct is that you have to
run around and examine (and, usually, modify) every place that
manufactures that type of struct. With a makeFuncCall defined like
this, you'd still have to do that; it would just become a lot easier
to forget to do so.If the subroutine were defined like most other makeXXX subroutines,
ie you have to supply *all* the fields, that argument would go away,
but the notational advantage is then dubious.The bigger-picture point is that you're proposing to make the coding
conventions for building FuncCalls different from what they are for
any other grammar node. I don't think that's a great idea; it will
mostly foster confusion.The major difference between FuncCalls and others is that `while most
raw-parsetree nodes are constructed only in their own syntax
productions, FuncCall is constructed in many places unrelated to
actual function call syntax.This really will make things a good bit easier on our successors.
Here's a rebased patch with comments illustrating how best to employ.
In my previous message, I characterized the difference between
FuncCalls and other raw-parsetree nodes. Is there some flaw in that
logic? If there isn't, is there some reason not to treat them in a
less redundant, more uniform manner as this patch does?
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
Attachments:
makeFuncArgs_002.difftext/plain; charset=us-asciiDownload
*** a/src/backend/nodes/makefuncs.c
--- b/src/backend/nodes/makefuncs.c
***************
*** 508,510 **** makeDefElemExtended(char *nameSpace, char *name, Node *arg,
--- 508,535 ----
return res;
}
+
+ /*
+ * makeFuncCall -
+ *
+ * Initialize a FuncCall struct with the information every caller must
+ * supply. Any non-default parameters have to be handled by the
+ * caller.
+ *
+ */
+
+ FuncCall *
+ makeFuncCall(List *name, List *args, int location)
+ {
+ FuncCall *n = makeNode(FuncCall);
+ n->funcname = name;
+ n->args = args;
+ n->location = location;
+ n->agg_order = NIL;
+ n->agg_star = FALSE;
+ n->agg_distinct = FALSE;
+ n->func_variadic = FALSE;
+ n->over = NULL;
+ return n;
+ }
+
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 10487,10502 **** a_expr: c_expr { $$ = $1; }
}
| a_expr AT TIME ZONE a_expr %prec AT
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("timezone");
! n->args = list_make2($5, $1);
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @2;
! $$ = (Node *) n;
}
/*
* These operators must be called out explicitly in order to make use
--- 10487,10495 ----
}
| a_expr AT TIME ZONE a_expr %prec AT
{
! $$ = (Node *) makeFuncCall(SystemFuncName("timezone"),
! list_make2($5, $1),
! @2);
}
/*
* These operators must be called out explicitly in order to make use
***************
*** 10548,10660 **** a_expr: c_expr { $$ = $1; }
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~", $1, $3, @2); }
| a_expr LIKE a_expr ESCAPE a_expr
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("like_escape");
! n->args = list_make2($3, $5);
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @2;
! $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~", $1, (Node *) n, @2);
}
| a_expr NOT LIKE a_expr
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~", $1, $4, @2); }
| a_expr NOT LIKE a_expr ESCAPE a_expr
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("like_escape");
! n->args = list_make2($4, $6);
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @2;
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~", $1, (Node *) n, @2);
}
| a_expr ILIKE a_expr
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~*", $1, $3, @2); }
| a_expr ILIKE a_expr ESCAPE a_expr
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("like_escape");
! n->args = list_make2($3, $5);
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @2;
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~*", $1, (Node *) n, @2);
}
| a_expr NOT ILIKE a_expr
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~*", $1, $4, @2); }
| a_expr NOT ILIKE a_expr ESCAPE a_expr
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("like_escape");
! n->args = list_make2($4, $6);
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @2;
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~*", $1, (Node *) n, @2);
}
| a_expr SIMILAR TO a_expr %prec SIMILAR
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("similar_escape");
! n->args = list_make2($4, makeNullAConst(-1));
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @2;
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~", $1, (Node *) n, @2);
}
| a_expr SIMILAR TO a_expr ESCAPE a_expr
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("similar_escape");
! n->args = list_make2($4, $6);
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @2;
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~", $1, (Node *) n, @2);
}
| a_expr NOT SIMILAR TO a_expr %prec SIMILAR
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("similar_escape");
! n->args = list_make2($5, makeNullAConst(-1));
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @2;
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~", $1, (Node *) n, @2);
}
| a_expr NOT SIMILAR TO a_expr ESCAPE a_expr
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("similar_escape");
! n->args = list_make2($5, $7);
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @2;
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~", $1, (Node *) n, @2);
}
--- 10541,10606 ----
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~", $1, $3, @2); }
| a_expr LIKE a_expr ESCAPE a_expr
{
! FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
! list_make2($3, $5),
! @2);
! $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~", $1, (Node *) n, @2);
!
}
| a_expr NOT LIKE a_expr
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~", $1, $4, @2); }
| a_expr NOT LIKE a_expr ESCAPE a_expr
{
! FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
! list_make2($4, $6),
! @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~", $1, (Node *) n, @2);
}
| a_expr ILIKE a_expr
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~*", $1, $3, @2); }
| a_expr ILIKE a_expr ESCAPE a_expr
{
! FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
! list_make2($3, $5),
! @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~*", $1, (Node *) n, @2);
}
| a_expr NOT ILIKE a_expr
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~*", $1, $4, @2); }
| a_expr NOT ILIKE a_expr ESCAPE a_expr
{
! FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
! list_make2($4, $6),
! @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~*", $1, (Node *) n, @2);
}
| a_expr SIMILAR TO a_expr %prec SIMILAR
{
! FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
! list_make2($4, makeNullAConst(-1)),
! @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~", $1, (Node *) n, @2);
}
| a_expr SIMILAR TO a_expr ESCAPE a_expr
{
! FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
! list_make2($4, $6),
! @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~", $1, (Node *) n, @2);
}
| a_expr NOT SIMILAR TO a_expr %prec SIMILAR
{
! FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
! list_make2($5, makeNullAConst(-1)),
! @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~", $1, (Node *) n, @2);
}
| a_expr NOT SIMILAR TO a_expr ESCAPE a_expr
{
! FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
! list_make2($5, $7),
! @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~", $1, (Node *) n, @2);
}
***************
*** 11089,11185 **** c_expr: columnref { $$ = $1; }
*/
func_expr: func_name '(' ')' over_clause
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = $1;
! n->args = NIL;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
n->over = $4;
- n->location = @1;
$$ = (Node *)n;
}
| func_name '(' func_arg_list ')' over_clause
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = $1;
! n->args = $3;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
n->over = $5;
- n->location = @1;
$$ = (Node *)n;
}
| func_name '(' VARIADIC func_arg_expr ')' over_clause
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = $1;
! n->args = list_make1($4);
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
n->func_variadic = TRUE;
n->over = $6;
- n->location = @1;
$$ = (Node *)n;
}
| func_name '(' func_arg_list ',' VARIADIC func_arg_expr ')' over_clause
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = $1;
! n->args = lappend($3, $6);
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
n->func_variadic = TRUE;
n->over = $8;
- n->location = @1;
$$ = (Node *)n;
}
| func_name '(' func_arg_list sort_clause ')' over_clause
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = $1;
! n->args = $3;
n->agg_order = $4;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
n->over = $6;
- n->location = @1;
$$ = (Node *)n;
}
| func_name '(' ALL func_arg_list opt_sort_clause ')' over_clause
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = $1;
! n->args = $4;
n->agg_order = $5;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
/* Ideally we'd mark the FuncCall node to indicate
* "must be an aggregate", but there's no provision
* for that in FuncCall at the moment.
*/
- n->func_variadic = FALSE;
n->over = $7;
- n->location = @1;
$$ = (Node *)n;
}
| func_name '(' DISTINCT func_arg_list opt_sort_clause ')' over_clause
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = $1;
! n->args = $4;
n->agg_order = $5;
- n->agg_star = FALSE;
n->agg_distinct = TRUE;
- n->func_variadic = FALSE;
n->over = $7;
- n->location = @1;
$$ = (Node *)n;
}
| func_name '(' '*' ')' over_clause
--- 11035,11088 ----
*/
func_expr: func_name '(' ')' over_clause
{
! FuncCall *n = makeFuncCall($1, NIL, @1);
n->over = $4;
$$ = (Node *)n;
}
| func_name '(' func_arg_list ')' over_clause
{
! FuncCall *n = makeFuncCall($1, $3, @1);
n->over = $5;
$$ = (Node *)n;
}
| func_name '(' VARIADIC func_arg_expr ')' over_clause
{
! FuncCall *n = makeFuncCall($1, list_make1($4), @1);
n->func_variadic = TRUE;
n->over = $6;
$$ = (Node *)n;
}
| func_name '(' func_arg_list ',' VARIADIC func_arg_expr ')' over_clause
{
! FuncCall *n = makeFuncCall($1, lappend($3, $6), @1);
n->func_variadic = TRUE;
n->over = $8;
$$ = (Node *)n;
}
| func_name '(' func_arg_list sort_clause ')' over_clause
{
! FuncCall *n = makeFuncCall($1, $3, @1);
n->agg_order = $4;
n->over = $6;
$$ = (Node *)n;
}
| func_name '(' ALL func_arg_list opt_sort_clause ')' over_clause
{
! FuncCall *n = makeFuncCall($1, $4, @1);
n->agg_order = $5;
/* Ideally we'd mark the FuncCall node to indicate
* "must be an aggregate", but there's no provision
* for that in FuncCall at the moment.
*/
n->over = $7;
$$ = (Node *)n;
}
| func_name '(' DISTINCT func_arg_list opt_sort_clause ')' over_clause
{
! FuncCall *n = makeFuncCall($1, $4, @1);
n->agg_order = $5;
n->agg_distinct = TRUE;
n->over = $7;
$$ = (Node *)n;
}
| func_name '(' '*' ')' over_clause
***************
*** 11194,11222 **** func_expr: func_name '(' ')' over_clause
* so that later processing can detect what the argument
* really was.
*/
! FuncCall *n = makeNode(FuncCall);
! n->funcname = $1;
! n->args = NIL;
! n->agg_order = NIL;
n->agg_star = TRUE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
n->over = $5;
- n->location = @1;
$$ = (Node *)n;
}
| COLLATION FOR '(' a_expr ')'
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("pg_collation_for");
! n->args = list_make1($4);
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| CURRENT_DATE
{
--- 11097,11112 ----
* so that later processing can detect what the argument
* really was.
*/
! FuncCall *n = makeFuncCall($1, NIL, @1);
n->agg_star = TRUE;
n->over = $5;
$$ = (Node *)n;
}
| COLLATION FOR '(' a_expr ')'
{
! $$ = (Node *) makeFuncCall(SystemFuncName("pg_collation_for"),
! list_make1($4),
! @1);
}
| CURRENT_DATE
{
***************
*** 11268,11283 **** func_expr: func_name '(' ')' over_clause
* Translate as "now()", since we have a function that
* does exactly what is needed.
*/
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("now");
! n->args = NIL;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| CURRENT_TIMESTAMP '(' Iconst ')'
{
--- 11158,11164 ----
* Translate as "now()", since we have a function that
* does exactly what is needed.
*/
! $$ = (Node *) makeFuncCall(SystemFuncName("now"), NIL, @1);
}
| CURRENT_TIMESTAMP '(' Iconst ')'
{
***************
*** 11340,11435 **** func_expr: func_name '(' ')' over_clause
}
| CURRENT_ROLE
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("current_user");
! n->args = NIL;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| CURRENT_USER
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("current_user");
! n->args = NIL;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| SESSION_USER
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("session_user");
! n->args = NIL;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| USER
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("current_user");
! n->args = NIL;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| CURRENT_CATALOG
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("current_database");
! n->args = NIL;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| CURRENT_SCHEMA
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("current_schema");
! n->args = NIL;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| CAST '(' a_expr AS Typename ')'
{ $$ = makeTypeCast($3, $5, @1); }
| EXTRACT '(' extract_list ')'
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("date_part");
! n->args = $3;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| OVERLAY '(' overlay_list ')'
{
--- 11221,11253 ----
}
| CURRENT_ROLE
{
! $$ = (Node *) makeFuncCall(SystemFuncName("current_user"), NIL, @1);
}
| CURRENT_USER
{
! $$ = (Node *) makeFuncCall(SystemFuncName("current_user"), NIL, @1);
}
| SESSION_USER
{
! $$ = (Node *) makeFuncCall(SystemFuncName("session_user"), NIL, @1);
}
| USER
{
! $$ = (Node *) makeFuncCall(SystemFuncName("current_user"), NIL, @1);
}
| CURRENT_CATALOG
{
! $$ = (Node *) makeFuncCall(SystemFuncName("current_database"), NIL, @1);
}
| CURRENT_SCHEMA
{
! $$ = (Node *) makeFuncCall(SystemFuncName("current_schema"), NIL, @1);
}
| CAST '(' a_expr AS Typename ')'
{ $$ = makeTypeCast($3, $5, @1); }
| EXTRACT '(' extract_list ')'
{
! $$ = (Node *) makeFuncCall(SystemFuncName("date_part"), $3, @1);
}
| OVERLAY '(' overlay_list ')'
{
***************
*** 11438,11483 **** func_expr: func_name '(' ')' over_clause
* overlay(A PLACING B FROM C) is converted to
* overlay(A, B, C)
*/
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("overlay");
! n->args = $3;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| POSITION '(' position_list ')'
{
/* position(A in B) is converted to position(B, A) */
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("position");
! n->args = $3;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| SUBSTRING '(' substr_list ')'
{
/* substring(A from B for C) is converted to
* substring(A, B, C) - thomas 2000-11-28
*/
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("substring");
! n->args = $3;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| TREAT '(' a_expr AS Typename ')'
{
--- 11256,11274 ----
* overlay(A PLACING B FROM C) is converted to
* overlay(A, B, C)
*/
! $$ = (Node *) makeFuncCall(SystemFuncName("overlay"), $3, @1);
}
| POSITION '(' position_list ')'
{
/* position(A in B) is converted to position(B, A) */
! $$ = (Node *) makeFuncCall(SystemFuncName("position"), $3, @1);
}
| SUBSTRING '(' substr_list ')'
{
/* substring(A from B for C) is converted to
* substring(A, B, C) - thomas 2000-11-28
*/
! $$ = (Node *) makeFuncCall(SystemFuncName("substring"), $3, @1);
}
| TREAT '(' a_expr AS Typename ')'
{
***************
*** 11486,11560 **** func_expr: func_name '(' ')' over_clause
* In SQL99, this is intended for use with structured UDTs,
* but let's make this a generally useful form allowing stronger
* coercions than are handled by implicit casting.
! */
! FuncCall *n = makeNode(FuncCall);
! /* Convert SystemTypeName() to SystemFuncName() even though
* at the moment they result in the same thing.
*/
! n->funcname = SystemFuncName(((Value *)llast($5->names))->val.str);
! n->args = list_make1($3);
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| TRIM '(' BOTH trim_list ')'
{
/* various trim expressions are defined in SQL
* - thomas 1997-07-19
*/
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("btrim");
! n->args = $4;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| TRIM '(' LEADING trim_list ')'
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("ltrim");
! n->args = $4;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| TRIM '(' TRAILING trim_list ')'
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("rtrim");
! n->args = $4;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| TRIM '(' trim_list ')'
{
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("btrim");
! n->args = $3;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| NULLIF '(' a_expr ',' a_expr ')'
{
--- 11277,11308 ----
* In SQL99, this is intended for use with structured UDTs,
* but let's make this a generally useful form allowing stronger
* coercions than are handled by implicit casting.
! *
! * Convert SystemTypeName() to SystemFuncName() even though
* at the moment they result in the same thing.
*/
! $$ = (Node *) makeFuncCall(SystemFuncName(((Value *)llast($5->names))->val.str),
! list_make1($3),
! @1);
}
| TRIM '(' BOTH trim_list ')'
{
/* various trim expressions are defined in SQL
* - thomas 1997-07-19
*/
! $$ = (Node *) makeFuncCall(SystemFuncName("btrim"), $4, @1);
}
| TRIM '(' LEADING trim_list ')'
{
! $$ = (Node *) makeFuncCall(SystemFuncName("ltrim"), $4, @1);
}
| TRIM '(' TRAILING trim_list ')'
{
! $$ = (Node *) makeFuncCall(SystemFuncName("rtrim"), $4, @1);
}
| TRIM '(' trim_list ')'
{
! $$ = (Node *) makeFuncCall(SystemFuncName("btrim"), $3, @1);
}
| NULLIF '(' a_expr ',' a_expr ')'
{
***************
*** 11607,11622 **** func_expr: func_name '(' ')' over_clause
{
/* xmlexists(A PASSING [BY REF] B [BY REF]) is
* converted to xmlexists(A, B)*/
! FuncCall *n = makeNode(FuncCall);
! n->funcname = SystemFuncName("xmlexists");
! n->args = list_make2($3, $4);
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
}
| XMLFOREST '(' xml_attribute_list ')'
{
--- 11355,11361 ----
{
/* xmlexists(A PASSING [BY REF] B [BY REF]) is
* converted to xmlexists(A, B)*/
! $$ = (Node *) makeFuncCall(SystemFuncName("xmlexists"), list_make2($3, $4), @1);
}
| XMLFOREST '(' xml_attribute_list ')'
{
***************
*** 13272,13280 **** makeBoolAConst(bool state, int location)
static FuncCall *
makeOverlaps(List *largs, List *rargs, int location, core_yyscan_t yyscanner)
{
! FuncCall *n = makeNode(FuncCall);
!
! n->funcname = SystemFuncName("overlaps");
if (list_length(largs) == 1)
largs = lappend(largs, largs);
else if (list_length(largs) != 2)
--- 13011,13017 ----
static FuncCall *
makeOverlaps(List *largs, List *rargs, int location, core_yyscan_t yyscanner)
{
! FuncCall *n;
if (list_length(largs) == 1)
largs = lappend(largs, largs);
else if (list_length(largs) != 2)
***************
*** 13289,13301 **** makeOverlaps(List *largs, List *rargs, int location, core_yyscan_t yyscanner)
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("wrong number of parameters on right side of OVERLAPS expression"),
parser_errposition(location)));
! n->args = list_concat(largs, rargs);
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->over = NULL;
! n->location = location;
return n;
}
--- 13026,13032 ----
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("wrong number of parameters on right side of OVERLAPS expression"),
parser_errposition(location)));
! n = makeFuncCall(SystemFuncName("overlaps"), list_concat(largs, rargs), location);
return n;
}
*** a/src/backend/parser/parse_utilcmd.c
--- b/src/backend/parser/parse_utilcmd.c
***************
*** 448,463 **** transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
castnode->typeName = SystemTypeName("regclass");
castnode->arg = (Node *) snamenode;
castnode->location = -1;
! funccallnode = makeNode(FuncCall);
! funccallnode->funcname = SystemFuncName("nextval");
! funccallnode->args = list_make1(castnode);
! funccallnode->agg_order = NIL;
! funccallnode->agg_star = false;
! funccallnode->agg_distinct = false;
! funccallnode->func_variadic = false;
! funccallnode->over = NULL;
! funccallnode->location = -1;
!
constraint = makeNode(Constraint);
constraint->contype = CONSTR_DEFAULT;
constraint->location = -1;
--- 448,456 ----
castnode->typeName = SystemTypeName("regclass");
castnode->arg = (Node *) snamenode;
castnode->location = -1;
! funccallnode = makeFuncCall(SystemFuncName("nextval"),
! list_make1(castnode),
! -1);
constraint = makeNode(Constraint);
constraint->contype = CONSTR_DEFAULT;
constraint->location = -1;
*** a/src/include/nodes/makefuncs.h
--- b/src/include/nodes/makefuncs.h
***************
*** 75,80 **** extern TypeName *makeTypeNameFromOid(Oid typeOid, int32 typmod);
--- 75,82 ----
extern FuncExpr *makeFuncExpr(Oid funcid, Oid rettype, List *args,
Oid funccollid, Oid inputcollid, CoercionForm fformat);
+ extern FuncCall *makeFuncCall(List *name, List *args, int location);
+
extern DefElem *makeDefElem(char *name, Node *arg);
extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
DefElemAction defaction);
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 285,290 **** typedef struct CollateClause
--- 285,295 ----
* construct *must* be an aggregate call. Otherwise, it might be either an
* aggregate or some other kind of function. However, if OVER is present
* it had better be an aggregate or window function.
+ *
+ * Normally, you'd initialize this via makeFuncCall() and then only
+ * change the parts of the struct its defaults don't match afterwards
+ * if needed.
+ *
*/
typedef struct FuncCall
{
* David Fetter (david@fetter.org) wrote:
On Mon, Feb 11, 2013 at 10:48:38AM -0800, David Fetter wrote:
On Sun, Feb 10, 2013 at 10:09:19AM -0500, Tom Lane wrote:
David Fetter <david@fetter.org> writes:
Per suggestions and lots of help from Andrew Gierth, please find
attached a patch to clean up the call sites for FuncCall nodes, which
I'd like to expand centrally rather than in each of the 37 (or 38, but
I only redid 37) places where it's called. The remaining one is in
src/backend/nodes/copyfuncs.c, which has to be modified for any
changes in the that struct anyhow.TBH, I don't think this is an improvement.
The problem with adding a new field to any struct is that you have to
run around and examine (and, usually, modify) every place that
manufactures that type of struct. With a makeFuncCall defined like
this, you'd still have to do that; it would just become a lot easier
to forget to do so.
I don't really see how finding all callers of makeFuncCall is
particularly harder than finding the callers of makeNode(Func). If
there were cases where we still wanted to use makeNode(Func), perhaps
that would be annoying since you'd have to look for both- but, iiuc,
this patch changes all of the callers to use makeFuncCall and it seems
reasonable for all callers to do so in the future as well.
It looks to me like the advantage of this patch is exactly that you
*don't* have to run around and modify things which are completely
unrelated to the new feature being added (eg: FILTER). Add the new
field, set up the default/no-op case in makeFuncCall() and then only
change those places that actually need to worry about your new field.
If the subroutine were defined like most other makeXXX subroutines,
ie you have to supply *all* the fields, that argument would go away,
but the notational advantage is then dubious.
Having to supply all the fields certainly wouldn't make things any
better. Providing the base set of fields which are required to be set
for any FuncCall node does make sense though, which is what the patch
does. The rest of the fields are all special cases for which a default
value works perfectly fine (when the field isn't involved in the
specific case being handled).
The bigger-picture point is that you're proposing to make the coding
conventions for building FuncCalls different from what they are for
any other grammar node. I don't think that's a great idea; it will
mostly foster confusion.The major difference between FuncCalls and others is that `while most
raw-parsetree nodes are constructed only in their own syntax
productions, FuncCall is constructed in many places unrelated to
actual function call syntax.
Yeah, FuncCall's are already rather special and they're built all over
the place.
That's my 2c on it anyhow. I don't see it as some kind of major
milestone but it looks like improvement to me and likely to make things
a bit easier on patch authors and reviewers who otherwise have to ponder
a bunch of repeated 'x->q = false;' statements in areas which are
completely unrelated to the new feature itself.
Thanks,
Stephen
On 17 June 2013 06:36, David Fetter <david@fetter.org> wrote:
Please find attached two versions of a patch which provides optional
FILTER clause for aggregates (T612, "Advanced OLAP operations").The first is intended to be applied on top of the previous patch, the
second without it. The first is, I believe, clearer in what it's
doing. Rather than simply mechanically visiting every place a
function call might be constructed, it visits a central one to change
the default, then goes only to the places where it's relevant.The patches are both early WIP as they contain no docs or regression
tests yet.Docs and regression tests added, makeFuncArgs approached dropped for
now, will re-visit later.Regression tests added to reflect bug fixes in COLLATE.
Rebased vs. master.
Hi,
I've been looking at this patch, which adds support for the SQL
standard feature of applying a filter to rows used in an aggregate.
The syntax matches the spec:
agg_fn ( <args> [ ORDER BY <sort_clause> ] ) [ FILTER ( WHERE <qual> ) ]
and this patch makes FILTER a new reserved keyword, usable as a
function or type, but not in other contexts, e.g., as a table name or
alias.
I'm not sure if that might be a problem for some people, but I can't
see any way round it, since otherwise there would be no way for the
parser to distinguish a filter clause from an alias expression.
The feature appears to work per-spec, and the code and doc changes
look reasonable. However, it looks a little light on regression tests,
and so I think some necessary changes have been overlooked.
In my testing of sub-queries in the FILTER clause (an extension to the
spec), I was able to produce the following error:
CREATE TABLE t1(a1 int);
CREATE TABLE t2(a2 int);
INSERT INTO t1 SELECT * FROM generate_series(1,10);
INSERT INTO t2 SELECT * FROM generate_series(3,6);
SELECT array_agg(a1) FILTER (WHERE a1 IN (SELECT a2 FROM t2)) FROM t1;
ERROR: plan should not reference subplan's variable
which looks to be related to subselect.c's handling of sub-queries in
aggregates.
Regards,
Dean
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Jun 20, 2013 at 08:59:27PM +0100, Dean Rasheed wrote:
On 17 June 2013 06:36, David Fetter <david@fetter.org> wrote:
Please find attached two versions of a patch which provides optional
FILTER clause for aggregates (T612, "Advanced OLAP operations").The first is intended to be applied on top of the previous patch, the
second without it. The first is, I believe, clearer in what it's
doing. Rather than simply mechanically visiting every place a
function call might be constructed, it visits a central one to change
the default, then goes only to the places where it's relevant.The patches are both early WIP as they contain no docs or regression
tests yet.Docs and regression tests added, makeFuncArgs approached dropped for
now, will re-visit later.Regression tests added to reflect bug fixes in COLLATE.
Rebased vs. master.
Hi,
I've been looking at this patch, which adds support for the SQL
standard feature of applying a filter to rows used in an aggregate.
The syntax matches the spec:agg_fn ( <args> [ ORDER BY <sort_clause> ] ) [ FILTER ( WHERE <qual> ) ]
and this patch makes FILTER a new reserved keyword, usable as a
function or type, but not in other contexts, e.g., as a table name or
alias.I'm not sure if that might be a problem for some people, but I can't
see any way round it, since otherwise there would be no way for the
parser to distinguish a filter clause from an alias expression.The feature appears to work per-spec, and the code and doc changes
look reasonable. However, it looks a little light on regression tests,
What tests do you think should be there that aren't?
and so I think some necessary changes have been overlooked.
In my testing of sub-queries in the FILTER clause (an extension to the
spec), I was able to produce the following error:
Per the spec,
B) A <filter clause> shall not contain a <query expression>, a <window function>, or an outer reference.
I'd be happy to see about adding one despite this, though. That they
didn't figure out how doesn't mean we shouldn't eventually, ideally in
time for 9.4.
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
David Fetter escribi�:
On Thu, Jun 20, 2013 at 08:59:27PM +0100, Dean Rasheed wrote:
In my testing of sub-queries in the FILTER clause (an extension to the
spec), I was able to produce the following error:Per the spec,
B) A <filter clause> shall not contain a <query expression>, a <window function>, or an outer reference.
If this is not allowed, I think there should be a clearer error message.
What Dean showed is an internal (not user-facing) error message.
--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Jun 21, 2013 at 12:10:25AM -0400, Alvaro Herrera wrote:
David Fetter escribi�:
On Thu, Jun 20, 2013 at 08:59:27PM +0100, Dean Rasheed wrote:
In my testing of sub-queries in the FILTER clause (an extension
to the spec), I was able to produce the following error:Per the spec,
B) A <filter clause> shall not contain a <query expression>, a
<window function>, or an outer reference.If this is not allowed, I think there should be a clearer error
message. What Dean showed is an internal (not user-facing) error
message.
Folding to lower isn't allowed by the spec either, and then there's
the matter of arrays...
Before going to lots of trouble to figure out how to throw an error
that says, "only the spec and no further," I'd like to investigate how
to make this work.
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Jun 21, 2013 at 12:10:25AM -0400, Alvaro Herrera wrote:
David Fetter escribi�:
On Thu, Jun 20, 2013 at 08:59:27PM +0100, Dean Rasheed wrote:
In my testing of sub-queries in the FILTER clause (an extension to the
spec), I was able to produce the following error:Per the spec,
B) A <filter clause> shall not contain a <query expression>, a <window function>, or an outer reference.
If this is not allowed, I think there should be a clearer error message.
What Dean showed is an internal (not user-facing) error message.
Please find attached a patch which allows subqueries in the FILTER
clause and adds regression testing for same.
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
Attachments:
filter_006.difftext/plain; charset=us-asciiDownload
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 68309ba..b289a3a 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -594,10 +594,13 @@ GROUP BY <replaceable class="parameter">expression</replaceable> [, ...]
</para>
<para>
- Aggregate functions, if any are used, are computed across all rows
+ In the absence of a <literal>FILTER</literal> clause,
+ aggregate functions, if any are used, are computed across all rows
making up each group, producing a separate value for each group
(whereas without <literal>GROUP BY</literal>, an aggregate
produces a single value computed across all the selected rows).
+ When a <literal>FILTER</literal> clause is present, only those
+ rows matching the FILTER clause are included.
When <literal>GROUP BY</literal> is present, it is not valid for
the <command>SELECT</command> list expressions to refer to
ungrouped columns except within aggregate functions or if the
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index b139212..c4d5f33 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -1562,24 +1562,26 @@ sqrt(2)
syntax of an aggregate expression is one of the following:
<synopsis>
-<replaceable>aggregate_name</replaceable> (<replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] )
-<replaceable>aggregate_name</replaceable> (ALL <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] )
-<replaceable>aggregate_name</replaceable> (DISTINCT <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] )
-<replaceable>aggregate_name</replaceable> ( * )
+<replaceable>aggregate_name</replaceable> (<replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
+<replaceable>aggregate_name</replaceable> (ALL <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
+<replaceable>aggregate_name</replaceable> (DISTINCT <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
+<replaceable>aggregate_name</replaceable> ( * ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
</synopsis>
where <replaceable>aggregate_name</replaceable> is a previously
defined aggregate (possibly qualified with a schema name),
- <replaceable>expression</replaceable> is
- any value expression that does not itself contain an aggregate
- expression or a window function call, and
- <replaceable>order_by_clause</replaceable> is a optional
- <literal>ORDER BY</> clause as described below.
+ <replaceable>expression</replaceable> is any value expression that
+ does not itself contain an aggregate expression or a window
+ function call, <replaceable>order_by_clause</replaceable> is a
+ optional <literal>ORDER BY</> clause as described below. The
+ <replaceable>aggregate_name</replaceable> can also be suffixed
+ with <literal>FILTER</literal> as described below.
</para>
<para>
- The first form of aggregate expression invokes the aggregate
- once for each input row.
+ The first form of aggregate expression invokes the aggregate once
+ for each input row, or when a FILTER clause is present, each row
+ matching same.
The second form is the same as the first, since
<literal>ALL</literal> is the default.
The third form invokes the aggregate once for each distinct value
@@ -1607,6 +1609,21 @@ sqrt(2)
</para>
<para>
+ Adding a FILTER clause to an aggregate specifies which values of
+ the expression being aggregated to evaluate. For example:
+<programlisting>
+SELECT
+ count(*) AS unfiltered,
+ count(*) FILTER (WHERE i < 5) AS filtered
+FROM generate_series(1,10) AS s(i);
+ unfiltered | filtered
+------------+----------
+ 10 | 4
+(1 row)
+</programlisting>
+ </para>
+
+ <para>
Ordinarily, the input rows are fed to the aggregate function in an
unspecified order. In many cases this does not matter; for example,
<function>min</> produces the same result no matter what order it
@@ -1709,10 +1726,10 @@ SELECT string_agg(a ORDER BY a, ',') FROM table; -- incorrect
The syntax of a window function call is one of the following:
<synopsis>
-<replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) OVER ( <replaceable class="parameter">window_definition</replaceable> )
-<replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) OVER <replaceable>window_name</replaceable>
-<replaceable>function_name</replaceable> ( * ) OVER ( <replaceable class="parameter">window_definition</replaceable> )
-<replaceable>function_name</replaceable> ( * ) OVER <replaceable>window_name</replaceable>
+<replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER ( <replaceable class="parameter">window_definition</replaceable> )
+<replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER <replaceable>window_name</replaceable>
+<replaceable>function_name</replaceable> ( * ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER ( <replaceable class="parameter">window_definition</replaceable> )
+<replaceable>function_name</replaceable> ( * ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER <replaceable>window_name</replaceable>
</synopsis>
where <replaceable class="parameter">window_definition</replaceable>
has the syntax
@@ -1836,16 +1853,18 @@ UNBOUNDED FOLLOWING
The built-in window functions are described in <xref
linkend="functions-window-table">. Other window functions can be added by
the user. Also, any built-in or user-defined aggregate function can be
- used as a window function.
+ used as a window function. A <literal>FILTER</literal> clause is
+ only valid for aggregate functions used in windowing.
</para>
<para>
- The syntaxes using <literal>*</> are used for calling parameter-less
- aggregate functions as window functions, for example
- <literal>count(*) OVER (PARTITION BY x ORDER BY y)</>.
- The asterisk (<literal>*</>) is customarily not used for non-aggregate window functions.
- Aggregate window functions, unlike normal aggregate functions, do not
- allow <literal>DISTINCT</> or <literal>ORDER BY</> to be used within the
+ The syntaxes using <literal>*</> are used for calling
+ parameter-less aggregate functions as window functions, for
+ example <literal>count(*) OVER (PARTITION BY x ORDER BY y)</>.
+ The asterisk (<literal>*</>) is customarily not used for
+ non-aggregate window functions. Aggregate window functions,
+ unlike normal aggregate functions, do not allow
+ <literal>DISTINCT</> or <literal>ORDER BY</> to be used within the
function argument list.
</para>
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 1388183..34dbef9 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -4410,6 +4410,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
astate->args = (List *) ExecInitExpr((Expr *) aggref->args,
parent);
+ astate->agg_filter = ExecInitExpr(aggref->agg_filter, parent);
/*
* Complain if the aggregate's arguments contain any
@@ -4448,6 +4449,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
wfstate->args = (List *) ExecInitExpr((Expr *) wfunc->args,
parent);
+ wfstate->agg_filter = ExecInitExpr(wfunc->agg_filter, parent);
/*
* Complain if the windowfunc's arguments contain any
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 12e1b8e..a43fdf2 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -381,7 +381,7 @@ sql_fn_post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var)
list_make1(subfield),
list_make1(param),
NIL, false, false, false,
- NULL, true, cref->location);
+ NULL, NULL, true, cref->location);
}
return param;
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index c741131..19105d2 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -488,6 +488,18 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
int i;
TupleTableSlot *slot;
+ /* Skip anything FILTERed out */
+ ExprState *filter = peraggstate->aggrefstate->agg_filter;
+ if (filter)
+ {
+ MemoryContext oldcontext = MemoryContextSwitchTo(aggstate->tmpcontext->ecxt_per_tuple_memory);
+ bool isnull;
+ Datum res = ExecEvalExpr(filter, aggstate->tmpcontext, &isnull, NULL);
+ MemoryContextSwitchTo(oldcontext);
+ if (isnull || !DatumGetBool(res))
+ continue;
+ }
+
/* Evaluate the current input expressions for this aggregate */
slot = ExecProject(peraggstate->evalproj, NULL);
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index d9f0e79..c00b058 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -227,9 +227,22 @@ advance_windowaggregate(WindowAggState *winstate,
int i;
MemoryContext oldContext;
ExprContext *econtext = winstate->tmpcontext;
+ ExprState *filter = wfuncstate->agg_filter;
oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+ /* Skip anything FILTERed out */
+ if (filter)
+ {
+ bool isnull;
+ Datum res = ExecEvalExpr(filter, econtext, &isnull, NULL);
+ if (isnull || !DatumGetBool(res))
+ {
+ MemoryContextSwitchTo(oldContext);
+ return;
+ }
+ }
+
/* We start from 1, since the 0th arg will be the transition value */
i = 1;
foreach(arg, wfuncstate->args)
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index b5b8d63..050fc83 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1137,6 +1137,7 @@ _copyAggref(const Aggref *from)
COPY_NODE_FIELD(args);
COPY_NODE_FIELD(aggorder);
COPY_NODE_FIELD(aggdistinct);
+ COPY_NODE_FIELD(agg_filter);
COPY_SCALAR_FIELD(aggstar);
COPY_SCALAR_FIELD(agglevelsup);
COPY_LOCATION_FIELD(location);
@@ -1157,6 +1158,7 @@ _copyWindowFunc(const WindowFunc *from)
COPY_SCALAR_FIELD(wincollid);
COPY_SCALAR_FIELD(inputcollid);
COPY_NODE_FIELD(args);
+ COPY_NODE_FIELD(agg_filter);
COPY_SCALAR_FIELD(winref);
COPY_SCALAR_FIELD(winstar);
COPY_SCALAR_FIELD(winagg);
@@ -2155,6 +2157,7 @@ _copyFuncCall(const FuncCall *from)
COPY_SCALAR_FIELD(agg_star);
COPY_SCALAR_FIELD(agg_distinct);
COPY_SCALAR_FIELD(func_variadic);
+ COPY_NODE_FIELD(agg_filter);
COPY_NODE_FIELD(over);
COPY_LOCATION_FIELD(location);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 3f96595..e1f63f1 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -196,6 +196,7 @@ _equalAggref(const Aggref *a, const Aggref *b)
COMPARE_NODE_FIELD(args);
COMPARE_NODE_FIELD(aggorder);
COMPARE_NODE_FIELD(aggdistinct);
+ COMPARE_NODE_FIELD(agg_filter);
COMPARE_SCALAR_FIELD(aggstar);
COMPARE_SCALAR_FIELD(agglevelsup);
COMPARE_LOCATION_FIELD(location);
@@ -211,6 +212,7 @@ _equalWindowFunc(const WindowFunc *a, const WindowFunc *b)
COMPARE_SCALAR_FIELD(wincollid);
COMPARE_SCALAR_FIELD(inputcollid);
COMPARE_NODE_FIELD(args);
+ COMPARE_NODE_FIELD(agg_filter);
COMPARE_SCALAR_FIELD(winref);
COMPARE_SCALAR_FIELD(winstar);
COMPARE_SCALAR_FIELD(winagg);
@@ -1995,6 +1997,7 @@ _equalFuncCall(const FuncCall *a, const FuncCall *b)
COMPARE_SCALAR_FIELD(agg_star);
COMPARE_SCALAR_FIELD(agg_distinct);
COMPARE_SCALAR_FIELD(func_variadic);
+ COMPARE_NODE_FIELD(agg_filter);
COMPARE_NODE_FIELD(over);
COMPARE_LOCATION_FIELD(location);
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 42d6621..932d4dd 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1570,6 +1570,8 @@ expression_tree_walker(Node *node,
if (expression_tree_walker((Node *) expr->aggdistinct,
walker, context))
return true;
+ if (walker((Node *) expr->agg_filter, context))
+ return true;
}
break;
case T_WindowFunc:
@@ -1580,6 +1582,9 @@ expression_tree_walker(Node *node,
if (expression_tree_walker((Node *) expr->args,
walker, context))
return true;
+ if (expression_tree_walker((Node *) expr->agg_filter,
+ walker, context))
+ return true;
}
break;
case T_ArrayRef:
@@ -2079,6 +2084,7 @@ expression_tree_mutator(Node *node,
MUTATE(newnode->args, aggref->args, List *);
MUTATE(newnode->aggorder, aggref->aggorder, List *);
MUTATE(newnode->aggdistinct, aggref->aggdistinct, List *);
+ MUTATE(newnode->agg_filter, aggref->agg_filter, Expr *);
return (Node *) newnode;
}
break;
@@ -2089,6 +2095,7 @@ expression_tree_mutator(Node *node,
FLATCOPY(newnode, wfunc, WindowFunc);
MUTATE(newnode->args, wfunc->args, List *);
+ MUTATE(newnode->agg_filter, wfunc->agg_filter, Expr *);
return (Node *) newnode;
}
break;
@@ -2951,6 +2958,8 @@ raw_expression_tree_walker(Node *node,
return true;
if (walker(fcall->agg_order, context))
return true;
+ if (walker(fcall->agg_filter, context))
+ return true;
if (walker(fcall->over, context))
return true;
/* function name is deemed uninteresting */
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b2183f4..cc09a9a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -958,6 +958,7 @@ _outAggref(StringInfo str, const Aggref *node)
WRITE_NODE_FIELD(args);
WRITE_NODE_FIELD(aggorder);
WRITE_NODE_FIELD(aggdistinct);
+ WRITE_NODE_FIELD(agg_filter);
WRITE_BOOL_FIELD(aggstar);
WRITE_UINT_FIELD(agglevelsup);
WRITE_LOCATION_FIELD(location);
@@ -973,6 +974,7 @@ _outWindowFunc(StringInfo str, const WindowFunc *node)
WRITE_OID_FIELD(wincollid);
WRITE_OID_FIELD(inputcollid);
WRITE_NODE_FIELD(args);
+ WRITE_NODE_FIELD(agg_filter);
WRITE_UINT_FIELD(winref);
WRITE_BOOL_FIELD(winstar);
WRITE_BOOL_FIELD(winagg);
@@ -2083,6 +2085,7 @@ _outFuncCall(StringInfo str, const FuncCall *node)
WRITE_BOOL_FIELD(agg_star);
WRITE_BOOL_FIELD(agg_distinct);
WRITE_BOOL_FIELD(func_variadic);
+ WRITE_NODE_FIELD(agg_filter);
WRITE_NODE_FIELD(over);
WRITE_LOCATION_FIELD(location);
}
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3a16e9d..c9824b2 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -479,6 +479,7 @@ _readAggref(void)
READ_NODE_FIELD(args);
READ_NODE_FIELD(aggorder);
READ_NODE_FIELD(aggdistinct);
+ READ_NODE_FIELD(agg_filter);
READ_BOOL_FIELD(aggstar);
READ_UINT_FIELD(agglevelsup);
READ_LOCATION_FIELD(location);
@@ -499,6 +500,7 @@ _readWindowFunc(void)
READ_OID_FIELD(wincollid);
READ_OID_FIELD(inputcollid);
READ_NODE_FIELD(args);
+ READ_NODE_FIELD(agg_filter);
READ_UINT_FIELD(winref);
READ_BOOL_FIELD(winstar);
READ_BOOL_FIELD(winagg);
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
index 090ae0b..627eb4d 100644
--- a/src/backend/optimizer/plan/planagg.c
+++ b/src/backend/optimizer/plan/planagg.c
@@ -314,7 +314,7 @@ find_minmax_aggs_walker(Node *node, List **context)
ListCell *l;
Assert(aggref->agglevelsup == 0);
- if (list_length(aggref->args) != 1 || aggref->aggorder != NIL)
+ if (list_length(aggref->args) != 1 || aggref->aggorder != NIL || aggref->agg_filter != NULL)
return true; /* it couldn't be MIN/MAX */
/* note: we do not care if DISTINCT is mentioned ... */
curTarget = (TargetEntry *) linitial(aggref->args);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5094226..5e73bab 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -490,6 +490,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
opt_frame_clause frame_extent frame_bound
%type <str> opt_existing_window_name
%type <boolean> opt_if_not_exists
+%type <node> filter_clause
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -536,7 +537,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
EXTENSION EXTERNAL EXTRACT
- FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
+ FALSE_P FAMILY FETCH FILTER FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
FREEZE FROM FULL FUNCTION FUNCTIONS
GLOBAL GRANT GRANTED GREATEST GROUP_P
@@ -11087,7 +11088,7 @@ c_expr: columnref { $$ = $1; }
* (Note that many of the special SQL functions wouldn't actually make any
* sense as functional index entries, but we ignore that consideration here.)
*/
-func_expr: func_name '(' ')' over_clause
+func_expr: func_name '(' ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
@@ -11096,24 +11097,26 @@ func_expr: func_name '(' ')' over_clause
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
- n->over = $4;
- n->location = @1;
- $$ = (Node *)n;
- }
- | func_name '(' func_arg_list ')' over_clause
- {
- FuncCall *n = makeNode(FuncCall);
- n->funcname = $1;
- n->args = $3;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
+ n->agg_filter = $4;
n->over = $5;
n->location = @1;
$$ = (Node *)n;
}
- | func_name '(' VARIADIC func_arg_expr ')' over_clause
+ | func_name '(' func_arg_list ')' filter_clause over_clause
+ {
+ FuncCall *n = makeNode(FuncCall);
+ n->funcname = $1;
+ n->args = $3;
+ n->agg_order = NIL;
+ n->agg_star = FALSE;
+ n->agg_distinct = FALSE;
+ n->func_variadic = FALSE;
+ n->agg_filter = $5;
+ n->over = $6;
+ n->location = @1;
+ $$ = (Node *)n;
+ }
+ | func_name '(' VARIADIC func_arg_expr ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
@@ -11122,11 +11125,12 @@ func_expr: func_name '(' ')' over_clause
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = TRUE;
- n->over = $6;
+ n->agg_filter = $6;
+ n->over = $7;
n->location = @1;
$$ = (Node *)n;
}
- | func_name '(' func_arg_list ',' VARIADIC func_arg_expr ')' over_clause
+ | func_name '(' func_arg_list ',' VARIADIC func_arg_expr ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
@@ -11135,11 +11139,12 @@ func_expr: func_name '(' ')' over_clause
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = TRUE;
- n->over = $8;
+ n->agg_filter = $8;
+ n->over = $9;
n->location = @1;
$$ = (Node *)n;
}
- | func_name '(' func_arg_list sort_clause ')' over_clause
+ | func_name '(' func_arg_list sort_clause ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
@@ -11148,11 +11153,12 @@ func_expr: func_name '(' ')' over_clause
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
- n->over = $6;
+ n->agg_filter = $6;
+ n->over = $7;
n->location = @1;
$$ = (Node *)n;
}
- | func_name '(' ALL func_arg_list opt_sort_clause ')' over_clause
+ | func_name '(' ALL func_arg_list opt_sort_clause ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
@@ -11165,11 +11171,12 @@ func_expr: func_name '(' ')' over_clause
* for that in FuncCall at the moment.
*/
n->func_variadic = FALSE;
- n->over = $7;
+ n->agg_filter = $7;
+ n->over = $8;
n->location = @1;
$$ = (Node *)n;
}
- | func_name '(' DISTINCT func_arg_list opt_sort_clause ')' over_clause
+ | func_name '(' DISTINCT func_arg_list opt_sort_clause ')' filter_clause over_clause
{
FuncCall *n = makeNode(FuncCall);
n->funcname = $1;
@@ -11178,11 +11185,12 @@ func_expr: func_name '(' ')' over_clause
n->agg_star = FALSE;
n->agg_distinct = TRUE;
n->func_variadic = FALSE;
- n->over = $7;
+ n->agg_filter = $7;
+ n->over = $8;
n->location = @1;
$$ = (Node *)n;
}
- | func_name '(' '*' ')' over_clause
+ | func_name '(' '*' ')' filter_clause over_clause
{
/*
* We consider AGGREGATE(*) to invoke a parameterless
@@ -11201,7 +11209,8 @@ func_expr: func_name '(' ')' over_clause
n->agg_star = TRUE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
- n->over = $5;
+ n->agg_filter = $5;
+ n->over = $6;
n->location = @1;
$$ = (Node *)n;
}
@@ -11735,7 +11744,7 @@ xmlexists_argument:
window_clause:
WINDOW window_definition_list { $$ = $2; }
| /*EMPTY*/ { $$ = NIL; }
- ;
+ ;
window_definition_list:
window_definition { $$ = list_make1($1); }
@@ -11752,6 +11761,11 @@ window_definition:
}
;
+filter_clause:
+ FILTER '(' WHERE a_expr ')' { $$ = $4; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
over_clause: OVER window_specification
{ $$ = $2; }
| OVER ColId
@@ -12980,6 +12994,7 @@ type_func_name_keyword:
| CONCURRENTLY
| CROSS
| CURRENT_SCHEMA
+ | FILTER
| FREEZE
| FULL
| ILIKE
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 7380618..e506797 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -44,7 +44,7 @@ typedef struct
int sublevels_up;
} check_ungrouped_columns_context;
-static int check_agg_arguments(ParseState *pstate, List *args);
+static int check_agg_arguments(ParseState *pstate, List *args, Expr *filter);
static bool check_agg_arguments_walker(Node *node,
check_agg_arguments_context *context);
static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
@@ -160,7 +160,7 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
* Check the arguments to compute the aggregate's level and detect
* improper nesting.
*/
- min_varlevel = check_agg_arguments(pstate, agg->args);
+ min_varlevel = check_agg_arguments(pstate, agg->args, agg->agg_filter);
agg->agglevelsup = min_varlevel;
/* Mark the correct pstate level as having aggregates */
@@ -207,6 +207,9 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
case EXPR_KIND_HAVING:
/* okay */
break;
+ case EXPR_KIND_FILTER:
+ errkind = true;
+ break;
case EXPR_KIND_WINDOW_PARTITION:
/* okay */
break;
@@ -309,7 +312,7 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
* which we can't know until we finish scanning the arguments.
*/
static int
-check_agg_arguments(ParseState *pstate, List *args)
+check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
{
int agglevel;
check_agg_arguments_context context;
@@ -323,6 +326,10 @@ check_agg_arguments(ParseState *pstate, List *args)
check_agg_arguments_walker,
(void *) &context);
+ (void) expression_tree_walker((Node *) filter,
+ check_agg_arguments_walker,
+ (void *) &context);
+
/*
* If we found no vars nor aggs at all, it's a level-zero aggregate;
* otherwise, its level is the minimum of vars or aggs.
@@ -481,6 +488,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_HAVING:
errkind = true;
break;
+ case EXPR_KIND_FILTER:
+ errkind = true;
+ break;
case EXPR_KIND_WINDOW_PARTITION:
case EXPR_KIND_WINDOW_ORDER:
case EXPR_KIND_WINDOW_FRAME_RANGE:
diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c
index 80f6ac7..b84f2bd 100644
--- a/src/backend/parser/parse_collate.c
+++ b/src/backend/parser/parse_collate.c
@@ -575,6 +575,10 @@ assign_collations_walker(Node *node, assign_collations_context *context)
* the case above for T_TargetEntry will apply
* appropriate checks to agg ORDER BY items.
*
+ * Likewise, we assign collations for the (bool)
+ * expression in agg_filter, independently of
+ * any other args.
+ *
* We need not recurse into the aggorder or
* aggdistinct lists, because those contain only
* SortGroupClause nodes which we need not
@@ -595,6 +599,22 @@ assign_collations_walker(Node *node, assign_collations_context *context)
(void) assign_collations_walker((Node *) tle,
&loccontext);
}
+
+ assign_expr_collations(context->pstate, (Node *) aggref->agg_filter);
+ }
+ break;
+ case T_WindowFunc:
+ {
+ /*
+ * WindowFunc requires special processing only for
+ * its agg_filter clause, as for aggregates.
+ */
+ WindowFunc *wfunc = (WindowFunc *) node;
+
+ (void) assign_collations_walker((Node *) wfunc->args,
+ &loccontext);
+
+ assign_expr_collations(context->pstate, (Node *) wfunc->agg_filter);
}
break;
case T_CaseExpr:
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 06f6512..2272965 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -22,6 +22,7 @@
#include "nodes/nodeFuncs.h"
#include "optimizer/var.h"
#include "parser/analyze.h"
+#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
@@ -463,7 +464,7 @@ transformIndirection(ParseState *pstate, Node *basenode, List *indirection)
list_make1(n),
list_make1(result),
NIL, false, false, false,
- NULL, true, location);
+ NULL, NULL, true, location);
if (newresult == NULL)
unknown_attribute(pstate, result, strVal(n), location);
result = newresult;
@@ -631,7 +632,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
- NULL, true, cref->location);
+ NULL, NULL, true, cref->location);
}
break;
}
@@ -676,7 +677,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
- NULL, true, cref->location);
+ NULL, NULL, true, cref->location);
}
break;
}
@@ -734,7 +735,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
- NULL, true, cref->location);
+ NULL, NULL, true, cref->location);
}
break;
}
@@ -1241,6 +1242,7 @@ transformFuncCall(ParseState *pstate, FuncCall *fn)
{
List *targs;
ListCell *args;
+ Expr *tagg_filter;
/* Transform the list of arguments ... */
targs = NIL;
@@ -1250,6 +1252,12 @@ transformFuncCall(ParseState *pstate, FuncCall *fn)
(Node *) lfirst(args)));
}
+ /* Transform the aggregate filter using transformWhereClause, to
+ * which FILTER is virually identical... */
+ tagg_filter = NULL;
+ if (fn->agg_filter != NULL)
+ tagg_filter = (Expr *)transformWhereClause(pstate, (Node *)fn->agg_filter, EXPR_KIND_FILTER, "FILTER");
+
/* ... and hand off to ParseFuncOrColumn */
return ParseFuncOrColumn(pstate,
fn->funcname,
@@ -1258,6 +1266,7 @@ transformFuncCall(ParseState *pstate, FuncCall *fn)
fn->agg_star,
fn->agg_distinct,
fn->func_variadic,
+ tagg_filter,
fn->over,
false,
fn->location);
@@ -1430,6 +1439,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_FROM_FUNCTION:
case EXPR_KIND_WHERE:
case EXPR_KIND_HAVING:
+ case EXPR_KIND_FILTER:
case EXPR_KIND_WINDOW_PARTITION:
case EXPR_KIND_WINDOW_ORDER:
case EXPR_KIND_WINDOW_FRAME_RANGE:
@@ -2579,6 +2589,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "WHERE";
case EXPR_KIND_HAVING:
return "HAVING";
+ case EXPR_KIND_FILTER:
+ return "FILTER";
case EXPR_KIND_WINDOW_PARTITION:
return "window PARTITION BY";
case EXPR_KIND_WINDOW_ORDER:
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index ae7d195..75c740e 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -63,7 +63,7 @@ Node *
ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
List *agg_order, bool agg_star, bool agg_distinct,
bool func_variadic,
- WindowDef *over, bool is_column, int location)
+ Expr *agg_filter, WindowDef *over, bool is_column, int location)
{
Oid rettype;
Oid funcid;
@@ -175,7 +175,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
* wasn't any aggregate or variadic decoration, nor an argument name.
*/
if (nargs == 1 && agg_order == NIL && !agg_star && !agg_distinct &&
- over == NULL && !func_variadic && argnames == NIL &&
+ agg_filter == NULL && over == NULL && !func_variadic && argnames == NIL &&
list_length(funcname) == 1)
{
Oid argtype = actual_arg_types[0];
@@ -251,6 +251,12 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
errmsg("ORDER BY specified, but %s is not an aggregate function",
NameListToString(funcname)),
parser_errposition(pstate, location)));
+ if (agg_filter)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("FILTER specified, but %s is not an aggregate function",
+ NameListToString(funcname)),
+ parser_errposition(pstate, location)));
if (over)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -402,6 +408,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
/* aggcollid and inputcollid will be set by parse_collate.c */
/* args, aggorder, aggdistinct will be set by transformAggregateCall */
aggref->aggstar = agg_star;
+ /* filter */
+ aggref->agg_filter = agg_filter;
/* agglevelsup will be set by transformAggregateCall */
aggref->location = location;
@@ -460,6 +468,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
/* winref will be set by transformWindowFuncCall */
wfunc->winstar = agg_star;
wfunc->winagg = (fdresult == FUNCDETAIL_AGGREGATE);
+ wfunc->agg_filter = agg_filter;
wfunc->location = location;
/*
@@ -483,6 +492,16 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
parser_errposition(pstate, location)));
/*
+ * Reject window functions which are not aggregates in the
+ * case of FILTER.
+ */
+ if (!wfunc->winagg && agg_filter)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("FILTER is not implemented in non-aggregate window functions"),
+ parser_errposition(pstate, location)));
+
+ /*
* ordered aggs not allowed in windows yet
*/
if (agg_order != NIL)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index a1ed781..bd3050a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -7424,7 +7424,15 @@ get_agg_expr(Aggref *aggref, deparse_context *context)
appendStringInfoString(buf, " ORDER BY ");
get_rule_orderby(aggref->aggorder, aggref->args, false, context);
}
+
+ if (aggref->agg_filter != NULL)
+ {
+ appendStringInfoString(buf, ") FILTER (WHERE ");
+ get_rule_expr((Node *)aggref->agg_filter, context, false);
+ }
+
appendStringInfoChar(buf, ')');
+
}
/*
@@ -7461,6 +7469,13 @@ get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context)
appendStringInfoChar(buf, '*');
else
get_rule_expr((Node *) wfunc->args, context, true);
+
+ if (wfunc->agg_filter != NULL)
+ {
+ appendStringInfoString(buf, ") FILTER (WHERE ");
+ get_rule_expr((Node *)wfunc->agg_filter, context, false);
+ }
+
appendStringInfoString(buf, ") OVER ");
foreach(l, context->windowClause)
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 4f77016..a3e6b05 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -584,6 +584,7 @@ typedef struct AggrefExprState
{
ExprState xprstate;
List *args; /* states of argument expressions */
+ ExprState *agg_filter; /* FILTER expression */
int aggno; /* ID number for agg within its plan node */
} AggrefExprState;
@@ -595,6 +596,7 @@ typedef struct WindowFuncExprState
{
ExprState xprstate;
List *args; /* states of argument expressions */
+ ExprState *agg_filter; /* FILTER expression */
int wfuncno; /* ID number for wfunc within its plan node */
} WindowFuncExprState;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6723647..a5ec069 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -295,6 +295,7 @@ typedef struct FuncCall
bool agg_star; /* argument was really '*' */
bool agg_distinct; /* arguments were labeled DISTINCT */
bool func_variadic; /* last argument was labeled VARIADIC */
+ Node *agg_filter; /* FILTER clause, if any */
struct WindowDef *over; /* OVER clause, if any */
int location; /* token location, or -1 if unknown */
} FuncCall;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 75b716a..9f7111e 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -247,6 +247,7 @@ typedef struct Aggref
List *args; /* arguments and sort expressions */
List *aggorder; /* ORDER BY (list of SortGroupClause) */
List *aggdistinct; /* DISTINCT (list of SortGroupClause) */
+ Expr *agg_filter; /* FILTER expression */
bool aggstar; /* TRUE if argument list was really '*' */
Index agglevelsup; /* > 0 if agg belongs to outer query */
int location; /* token location, or -1 if unknown */
@@ -263,6 +264,7 @@ typedef struct WindowFunc
Oid wincollid; /* OID of collation of result */
Oid inputcollid; /* OID of collation that function should use */
List *args; /* arguments to the window function */
+ Expr *agg_filter; /* FILTER expression */
Index winref; /* index of associated WindowClause */
bool winstar; /* TRUE if argument list was really '*' */
bool winagg; /* is function a simple aggregate? */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 68a13b7..4e9677e 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -155,6 +155,7 @@ PG_KEYWORD("extract", EXTRACT, COL_NAME_KEYWORD)
PG_KEYWORD("false", FALSE_P, RESERVED_KEYWORD)
PG_KEYWORD("family", FAMILY, UNRESERVED_KEYWORD)
PG_KEYWORD("fetch", FETCH, RESERVED_KEYWORD)
+PG_KEYWORD("filter", FILTER, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("first", FIRST_P, UNRESERVED_KEYWORD)
PG_KEYWORD("float", FLOAT_P, COL_NAME_KEYWORD)
PG_KEYWORD("following", FOLLOWING, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_func.h b/src/include/parser/parse_func.h
index 6e09dc4..13efb57 100644
--- a/src/include/parser/parse_func.h
+++ b/src/include/parser/parse_func.h
@@ -46,7 +46,7 @@ extern Node *ParseFuncOrColumn(ParseState *pstate,
List *funcname, List *fargs,
List *agg_order, bool agg_star, bool agg_distinct,
bool func_variadic,
- WindowDef *over, bool is_column, int location);
+ Expr *agg_filter, WindowDef *over, bool is_column, int location);
extern FuncDetailCode func_get_detail(List *funcname,
List *fargs, List *fargnames,
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 49ca764..bea3b07 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -39,6 +39,7 @@ typedef enum ParseExprKind
EXPR_KIND_FROM_FUNCTION, /* function in FROM clause */
EXPR_KIND_WHERE, /* WHERE */
EXPR_KIND_HAVING, /* HAVING */
+ EXPR_KIND_FILTER, /* FILTER */
EXPR_KIND_WINDOW_PARTITION, /* window definition PARTITION BY */
EXPR_KIND_WINDOW_ORDER, /* window definition ORDER BY */
EXPR_KIND_WINDOW_FRAME_RANGE, /* window frame clause with RANGE */
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index d379c0d..9359811 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1154,3 +1154,69 @@ select string_agg(v, decode('ee', 'hex')) from bytea_test_table;
(1 row)
drop table bytea_test_table;
+-- FILTER tests
+select min(unique1) filter (where unique1 > 100) from tenk1;
+ min
+-----
+ 101
+(1 row)
+
+select ten, sum(distinct four) filter (where four::text ~ '123') from onek a
+group by ten;
+ ten | sum
+-----+-----
+ 0 |
+ 1 |
+ 2 |
+ 3 |
+ 4 |
+ 5 |
+ 6 |
+ 7 |
+ 8 |
+ 9 |
+(10 rows)
+
+select ten, sum(distinct four) filter (where four > 10) from onek a
+group by ten
+having exists (select 1 from onek b where sum(distinct a.four) = b.four);
+ ten | sum
+-----+-----
+ 0 |
+ 2 |
+ 4 |
+ 6 |
+ 8 |
+(5 rows)
+
+select max(foo COLLATE "C") filter (where (bar collate "POSIX") > '0') from (values ('a', 'b')) AS v(foo,bar);
+ max
+-----
+ a
+(1 row)
+
+-- outer-level aggregates
+select
+ (select max((select i.unique2 from tenk1 i where i.unique1 = o.unique1)) filter (where o.unique1 < 10))
+from tenk1 o;
+ max
+------
+ 9998
+(1 row)
+
+-- non-standard-conforming FILTER clause containing subquery
+select sum(unique1) FILTER (WHERE unique1 IN (SELECT unique1 FROM onek where unique1 < 100)) FROM tenk1;
+ sum
+------
+ 4950
+(1 row)
+
+-- exercise lots of aggregate parts with FILTER
+select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1)
+ from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+ generate_series(1,2) i;
+ aggfns
+---------------------------
+ {"(2,2,bar)","(3,1,baz)"}
+(1 row)
+
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 752c7b4..2727fa7 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -1020,5 +1020,18 @@ SELECT ntile(0) OVER (ORDER BY ten), ten, four FROM tenk1;
ERROR: argument of ntile must be greater than zero
SELECT nth_value(four, 0) OVER (ORDER BY ten), ten, four FROM tenk1;
ERROR: argument of nth_value must be greater than zero
+-- filter
+SELECT sum(salary), row_number() OVER (ORDER BY depname), sum(
+ sum(salary) FILTER (WHERE enroll_date > '2007-01-01')
+) FILTER (WHERE depname <> 'sales') OVER (ORDER BY depname DESC) AS "filtered_sum",
+ depname
+FROM empsalary GROUP BY depname;
+ sum | row_number | filtered_sum | depname
+-------+------------+--------------+-----------
+ 14600 | 3 | | sales
+ 7400 | 2 | 3500 | personnel
+ 25100 | 1 | 22600 | develop
+(3 rows)
+
-- cleanup
DROP TABLE empsalary;
diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql
index 38d4757..da0bd65 100644
--- a/src/test/regress/sql/aggregates.sql
+++ b/src/test/regress/sql/aggregates.sql
@@ -442,3 +442,31 @@ select string_agg(v, NULL) from bytea_test_table;
select string_agg(v, decode('ee', 'hex')) from bytea_test_table;
drop table bytea_test_table;
+
+-- FILTER tests
+
+select min(unique1) filter (where unique1 > 100) from tenk1;
+
+select ten, sum(distinct four) filter (where four::text ~ '123') from onek a
+group by ten;
+
+select ten, sum(distinct four) filter (where four > 10) from onek a
+group by ten
+having exists (select 1 from onek b where sum(distinct a.four) = b.four);
+
+select max(foo COLLATE "C") filter (where (bar collate "POSIX") > '0') from (values ('a', 'b')) AS v(foo,bar);
+
+-- outer-level aggregates
+select
+ (select max((select i.unique2 from tenk1 i where i.unique1 = o.unique1)) filter (where o.unique1 < 10))
+from tenk1 o;
+
+-- non-standard-conforming FILTER clause containing subquery
+
+select sum(unique1) FILTER (WHERE unique1 IN (SELECT unique1 FROM onek where unique1 < 100)) FROM tenk1;
+
+-- exercise lots of aggregate parts with FILTER
+
+select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1)
+ from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+ generate_series(1,2) i;
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 769be0f..6ee3696 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -264,5 +264,13 @@ SELECT ntile(0) OVER (ORDER BY ten), ten, four FROM tenk1;
SELECT nth_value(four, 0) OVER (ORDER BY ten), ten, four FROM tenk1;
+-- filter
+
+SELECT sum(salary), row_number() OVER (ORDER BY depname), sum(
+ sum(salary) FILTER (WHERE enroll_date > '2007-01-01')
+) FILTER (WHERE depname <> 'sales') OVER (ORDER BY depname DESC) AS "filtered_sum",
+ depname
+FROM empsalary GROUP BY depname;
+
-- cleanup
DROP TABLE empsalary;
On 21 June 2013 05:01, David Fetter <david@fetter.org> wrote:
What tests do you think should be there that aren't?
I think I expected to see more tests related to some of the specific
code changes, such as
CREATE TABLE t AS SELECT * FROM generate_series(1,10) t(x);
-- Should fail (filter can't be used for non-aggregates)
SELECT abs(x) FILTER (WHERE x > 5) FROM t;
-- Should fail (filter clause can't contain aggregates)
SELECT array_agg(x) FILTER (WHERE x > AVG(x)) t;
-- OK (aggregate in filter sub-query)
SELECT array_agg(x) FILTER (WHERE x > (SELECT AVG(x) FROM t)) FROM t;
-- OK (trivial sub-query)
SELECT array_agg(x) FILTER (WHERE (SELECT x > 5)) FROM t;
-- OK
SELECT array_agg(x) FILTER (WHERE (SELECT x > (SELECT AVG(x) FROM t))) FROM t;
-- OK
SELECT array_agg(x) FILTER (WHERE (SELECT (SELECT t.x > AVG(t2.x) FROM
t as t2))) FROM t;
-- Should fail
SELECT array_agg(x) FILTER (WHERE (SELECT (SELECT 5 > AVG(t.x) FROM t
as t2))) FROM t;
-- OK (filtered aggregate in window context)
SELECT x, AVG(x) OVER(ORDER BY x), AVG(x) FILTER (WHERE x > 5)
OVER(ORDER BY x) FROM t;
-- Should fail (non-aggregate window function with filter)
SELECT x, rank() OVER(ORDER BY x), rank() FILTER (WHERE x > 5)
OVER(ORDER BY x) FROM t;
-- View definition test
CREATE VIEW v AS SELECT array_agg(x) FILTER (WHERE (SELECT (SELECT t.x
AVG(t2.x) FROM t as t2))) FROM t;
\d+ v
etc...
You could probably dream up better examples --- I just felt that the
current tests don't give much coverage of the code changes.
Regards,
Dean
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 21 June 2013 06:16, David Fetter <david@fetter.org> wrote:
On Fri, Jun 21, 2013 at 12:10:25AM -0400, Alvaro Herrera wrote:
David Fetter escribió:
On Thu, Jun 20, 2013 at 08:59:27PM +0100, Dean Rasheed wrote:
In my testing of sub-queries in the FILTER clause (an extension to the
spec), I was able to produce the following error:Per the spec,
B) A <filter clause> shall not contain a <query expression>, a <window function>, or an outer reference.
If this is not allowed, I think there should be a clearer error message.
What Dean showed is an internal (not user-facing) error message.Please find attached a patch which allows subqueries in the FILTER
clause and adds regression testing for same.
Nice!
I should have pointed out that it was already working for some
sub-queries, just not all. It's good to see that it was basically just
a one-line fix, because I think it would have been a shame to not
support sub-queries.
I hope to take another look at it over the weekend.
Regards,
Dean
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 21 June 2013 10:02, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
On 21 June 2013 06:16, David Fetter <david@fetter.org> wrote:
On Fri, Jun 21, 2013 at 12:10:25AM -0400, Alvaro Herrera wrote:
David Fetter escribió:
On Thu, Jun 20, 2013 at 08:59:27PM +0100, Dean Rasheed wrote:
In my testing of sub-queries in the FILTER clause (an extension to the
spec), I was able to produce the following error:Per the spec,
B) A <filter clause> shall not contain a <query expression>, a <window function>, or an outer reference.
If this is not allowed, I think there should be a clearer error message.
What Dean showed is an internal (not user-facing) error message.Please find attached a patch which allows subqueries in the FILTER
clause and adds regression testing for same.Nice!
I should have pointed out that it was already working for some
sub-queries, just not all. It's good to see that it was basically just
a one-line fix, because I think it would have been a shame to not
support sub-queries.I hope to take another look at it over the weekend.
I'm still not happy that this patch is making FILTER a new reserved
keyword, because I think it is a common enough English word (and an
obscure enough SQL keyword) that people may well have used it for
table names or aliases, and so their code will break.
There is another thread discussing a similar issue with lead/lag ignore nulls:
/messages/by-id/CAOdE5WQnfR737OkxNXuLWnwpL7=OUssC-63ijP2=2RnRTw8emQ@mail.gmail.com
Playing around with the idea proposed there, it seems that it is
possible to make FILTER (and also OVER) unreserved keywords, by
splitting out the production of aggregate/window functions from normal
functions in gram.y. Aggregate/window functions are not allowed in
index_elem or func_table constructs, but they may appear in c_expr's.
That resolves the shift/reduce conflicts that would otherwise occur
from subsequent alias clauses, allowing FILTER and OVER to be
unreserved.
There is a drawback --- certain error messages become less specific,
for example: "SELECT * FROM rank() OVER(ORDER BY random());" is now a
syntax error, rather than the more useful error saying that window
functions aren't allowed in the FROM clause. That doesn't seem like
such a common case though.
What do you think?
Regards,
Dean
Attachments:
make-filter-unreserved.patchapplication/octet-stream; name=make-filter-unreserved.patchDownload
*** ./src/include/parser/kwlist.h.orig 2013-06-23 13:40:32.000000000 +0100
--- ./src/include/parser/kwlist.h 2013-06-23 13:41:29.000000000 +0100
***************
*** 155,161 ****
PG_KEYWORD("false", FALSE_P, RESERVED_KEYWORD)
PG_KEYWORD("family", FAMILY, UNRESERVED_KEYWORD)
PG_KEYWORD("fetch", FETCH, RESERVED_KEYWORD)
! PG_KEYWORD("filter", FILTER, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("first", FIRST_P, UNRESERVED_KEYWORD)
PG_KEYWORD("float", FLOAT_P, COL_NAME_KEYWORD)
PG_KEYWORD("following", FOLLOWING, UNRESERVED_KEYWORD)
--- 155,161 ----
PG_KEYWORD("false", FALSE_P, RESERVED_KEYWORD)
PG_KEYWORD("family", FAMILY, UNRESERVED_KEYWORD)
PG_KEYWORD("fetch", FETCH, RESERVED_KEYWORD)
! PG_KEYWORD("filter", FILTER, UNRESERVED_KEYWORD)
PG_KEYWORD("first", FIRST_P, UNRESERVED_KEYWORD)
PG_KEYWORD("float", FLOAT_P, COL_NAME_KEYWORD)
PG_KEYWORD("following", FOLLOWING, UNRESERVED_KEYWORD)
***************
*** 271,277 ****
PG_KEYWORD("order", ORDER, RESERVED_KEYWORD)
PG_KEYWORD("out", OUT_P, COL_NAME_KEYWORD)
PG_KEYWORD("outer", OUTER_P, TYPE_FUNC_NAME_KEYWORD)
! PG_KEYWORD("over", OVER, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("overlaps", OVERLAPS, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("overlay", OVERLAY, COL_NAME_KEYWORD)
PG_KEYWORD("owned", OWNED, UNRESERVED_KEYWORD)
--- 271,277 ----
PG_KEYWORD("order", ORDER, RESERVED_KEYWORD)
PG_KEYWORD("out", OUT_P, COL_NAME_KEYWORD)
PG_KEYWORD("outer", OUTER_P, TYPE_FUNC_NAME_KEYWORD)
! PG_KEYWORD("over", OVER, UNRESERVED_KEYWORD)
PG_KEYWORD("overlaps", OVERLAPS, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("overlay", OVERLAY, COL_NAME_KEYWORD)
PG_KEYWORD("owned", OWNED, UNRESERVED_KEYWORD)
*** ./src/test/regress/expected/window.out.orig 2013-06-23 13:30:04.000000000 +0100
--- ./src/test/regress/expected/window.out 2013-06-23 13:47:09.000000000 +0100
***************
*** 989,997 ****
LINE 1: SELECT rank() OVER (ORDER BY 1), count(*) FROM empsalary GRO...
^
SELECT * FROM rank() OVER (ORDER BY random());
! ERROR: window functions are not allowed in functions in FROM
LINE 1: SELECT * FROM rank() OVER (ORDER BY random());
! ^
DELETE FROM empsalary WHERE (rank() OVER (ORDER BY random())) > 10;
ERROR: window functions are not allowed in WHERE
LINE 1: DELETE FROM empsalary WHERE (rank() OVER (ORDER BY random())...
--- 989,997 ----
LINE 1: SELECT rank() OVER (ORDER BY 1), count(*) FROM empsalary GRO...
^
SELECT * FROM rank() OVER (ORDER BY random());
! ERROR: syntax error at or near "ORDER"
LINE 1: SELECT * FROM rank() OVER (ORDER BY random());
! ^
DELETE FROM empsalary WHERE (rank() OVER (ORDER BY random())) > 10;
ERROR: window functions are not allowed in WHERE
LINE 1: DELETE FROM empsalary WHERE (rank() OVER (ORDER BY random())...
*** ./src/backend/parser/gram.y.orig 2013-06-23 09:29:01.000000000 +0100
--- ./src/backend/parser/gram.y 2013-06-23 13:39:49.000000000 +0100
***************
*** 403,409 ****
%type <node> def_arg columnElem where_clause where_or_current_clause
a_expr b_expr c_expr func_expr AexprConst indirection_el
columnref in_expr having_clause func_table array_expr
! ExclusionWhereClause
%type <list> ExclusionConstraintList ExclusionConstraintElem
%type <list> func_arg_list
%type <node> func_arg_expr
--- 403,409 ----
%type <node> def_arg columnElem where_clause where_or_current_clause
a_expr b_expr c_expr func_expr AexprConst indirection_el
columnref in_expr having_clause func_table array_expr
! ExclusionWhereClause agg_func_expr func_call builtin_func
%type <list> ExclusionConstraintList ExclusionConstraintElem
%type <list> func_arg_list
%type <node> func_arg_expr
***************
*** 10495,10500 ****
--- 10495,10501 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @2;
$$ = (Node *) n;
***************
*** 10556,10561 ****
--- 10557,10563 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @2;
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~", $1, (Node *) n, @2);
***************
*** 10571,10576 ****
--- 10573,10579 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @2;
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~", $1, (Node *) n, @2);
***************
*** 10586,10591 ****
--- 10589,10595 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @2;
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~*", $1, (Node *) n, @2);
***************
*** 10601,10606 ****
--- 10605,10611 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @2;
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~*", $1, (Node *) n, @2);
***************
*** 10615,10620 ****
--- 10620,10626 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @2;
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~", $1, (Node *) n, @2);
***************
*** 10628,10633 ****
--- 10634,10640 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @2;
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~", $1, (Node *) n, @2);
***************
*** 10641,10646 ****
--- 10648,10654 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @2;
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~", $1, (Node *) n, @2);
***************
*** 10654,10659 ****
--- 10662,10668 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @2;
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~", $1, (Node *) n, @2);
***************
*** 11006,11012 ****
}
| case_expr
{ $$ = $1; }
! | func_expr
{ $$ = $1; }
| select_with_parens %prec UMINUS
{
--- 11015,11021 ----
}
| case_expr
{ $$ = $1; }
! | agg_func_expr
{ $$ = $1; }
| select_with_parens %prec UMINUS
{
***************
*** 11087,11147 ****
* legal in the backwards-compatible functional-index syntax for CREATE INDEX.
* (Note that many of the special SQL functions wouldn't actually make any
* sense as functional index entries, but we ignore that consideration here.)
! */
! func_expr: func_name '(' ')' filter_clause over_clause
! {
! FuncCall *n = makeNode(FuncCall);
! n->funcname = $1;
! n->args = NIL;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->agg_filter = $4;
! n->over = $5;
! n->location = @1;
! $$ = (Node *)n;
! }
! | func_name '(' func_arg_list ')' filter_clause over_clause
! {
! FuncCall *n = makeNode(FuncCall);
! n->funcname = $1;
! n->args = $3;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->agg_filter = $5;
! n->over = $6;
! n->location = @1;
! $$ = (Node *)n;
! }
! | func_name '(' VARIADIC func_arg_expr ')' filter_clause over_clause
! {
! FuncCall *n = makeNode(FuncCall);
! n->funcname = $1;
! n->args = list_make1($4);
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = TRUE;
! n->agg_filter = $6;
! n->over = $7;
! n->location = @1;
! $$ = (Node *)n;
! }
! | func_name '(' func_arg_list ',' VARIADIC func_arg_expr ')' filter_clause over_clause
! {
! FuncCall *n = makeNode(FuncCall);
! n->funcname = $1;
! n->args = lappend($3, $6);
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = TRUE;
! n->agg_filter = $8;
! n->over = $9;
! n->location = @1;
$$ = (Node *)n;
}
| func_name '(' func_arg_list sort_clause ')' filter_clause over_clause
--- 11096,11120 ----
* legal in the backwards-compatible functional-index syntax for CREATE INDEX.
* (Note that many of the special SQL functions wouldn't actually make any
* sense as functional index entries, but we ignore that consideration here.)
! *
! * agg_func_expr is a superset of func_expr that includes all supported
! * aggregate and window function syntaxes. We allow that in c_expr, but not
! * in index_elem or func_table, since aggregates and window functions aren't
! * allowed in index expressions or in the FROM or USING clause of a query.
! * This separation allows us to keep FILTER and OVER as unreserved keywords
! * without shift/reduce conflicts that would otherwise result from subsequent
! * alias clauses.
! */
! func_expr: func_call { $$ = $1; }
! | builtin_func { $$ = $1; }
! ;
!
! agg_func_expr:
! func_call filter_clause over_clause
! {
! FuncCall *n = (FuncCall *)$1;
! n->agg_filter = $2;
! n->over = $3;
$$ = (Node *)n;
}
| func_name '(' func_arg_list sort_clause ')' filter_clause over_clause
***************
*** 11214,11220 ****
n->location = @1;
$$ = (Node *)n;
}
! | COLLATION FOR '(' a_expr ')'
{
FuncCall *n = makeNode(FuncCall);
n->funcname = SystemFuncName("pg_collation_for");
--- 11187,11255 ----
n->location = @1;
$$ = (Node *)n;
}
! | builtin_func { $$ = $1; }
! ;
!
! func_call: func_name '(' ')'
! {
! FuncCall *n = makeNode(FuncCall);
! n->funcname = $1;
! n->args = NIL;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->agg_filter = NULL;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
! }
! | func_name '(' func_arg_list ')'
! {
! FuncCall *n = makeNode(FuncCall);
! n->funcname = $1;
! n->args = $3;
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = FALSE;
! n->agg_filter = NULL;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
! }
! | func_name '(' VARIADIC func_arg_expr ')'
! {
! FuncCall *n = makeNode(FuncCall);
! n->funcname = $1;
! n->args = list_make1($4);
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = TRUE;
! n->agg_filter = NULL;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
! }
! | func_name '(' func_arg_list ',' VARIADIC func_arg_expr ')'
! {
! FuncCall *n = makeNode(FuncCall);
! n->funcname = $1;
! n->args = lappend($3, $6);
! n->agg_order = NIL;
! n->agg_star = FALSE;
! n->agg_distinct = FALSE;
! n->func_variadic = TRUE;
! n->agg_filter = NULL;
! n->over = NULL;
! n->location = @1;
! $$ = (Node *)n;
! }
! ;
!
! builtin_func:
! COLLATION FOR '(' a_expr ')'
{
FuncCall *n = makeNode(FuncCall);
n->funcname = SystemFuncName("pg_collation_for");
***************
*** 11223,11228 ****
--- 11258,11264 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @1;
$$ = (Node *)n;
***************
*** 11284,11289 ****
--- 11320,11326 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @1;
$$ = (Node *)n;
***************
*** 11356,11361 ****
--- 11393,11399 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @1;
$$ = (Node *)n;
***************
*** 11369,11374 ****
--- 11407,11413 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @1;
$$ = (Node *)n;
***************
*** 11382,11387 ****
--- 11421,11427 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @1;
$$ = (Node *)n;
***************
*** 11395,11400 ****
--- 11435,11441 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @1;
$$ = (Node *)n;
***************
*** 11408,11413 ****
--- 11449,11455 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @1;
$$ = (Node *)n;
***************
*** 11421,11426 ****
--- 11463,11469 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @1;
$$ = (Node *)n;
***************
*** 11436,11441 ****
--- 11479,11485 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @1;
$$ = (Node *)n;
***************
*** 11454,11459 ****
--- 11498,11504 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @1;
$$ = (Node *)n;
***************
*** 11468,11473 ****
--- 11513,11519 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @1;
$$ = (Node *)n;
***************
*** 11484,11489 ****
--- 11530,11536 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @1;
$$ = (Node *)n;
***************
*** 11506,11511 ****
--- 11553,11559 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @1;
$$ = (Node *)n;
***************
*** 11522,11527 ****
--- 11570,11576 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @1;
$$ = (Node *)n;
***************
*** 11535,11540 ****
--- 11584,11590 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @1;
$$ = (Node *)n;
***************
*** 11548,11553 ****
--- 11598,11604 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @1;
$$ = (Node *)n;
***************
*** 11561,11566 ****
--- 11612,11618 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @1;
$$ = (Node *)n;
***************
*** 11623,11628 ****
--- 11675,11681 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @1;
$$ = (Node *)n;
***************
*** 12740,12745 ****
--- 12793,12799 ----
| EXTENSION
| EXTERNAL
| FAMILY
+ | FILTER
| FIRST_P
| FOLLOWING
| FORCE
***************
*** 12808,12813 ****
--- 12862,12868 ----
| OPERATOR
| OPTION
| OPTIONS
+ | OVER
| OWNED
| OWNER
| PARSER
***************
*** 12994,13000 ****
| CONCURRENTLY
| CROSS
| CURRENT_SCHEMA
- | FILTER
| FREEZE
| FULL
| ILIKE
--- 13049,13054 ----
***************
*** 13007,13013 ****
| NATURAL
| NOTNULL
| OUTER_P
- | OVER
| OVERLAPS
| RIGHT
| SIMILAR
--- 13061,13066 ----
***************
*** 13309,13314 ****
--- 13362,13368 ----
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = location;
return n;
Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
I'm still not happy that this patch is making FILTER a new reserved
keyword, because I think it is a common enough English word (and an
obscure enough SQL keyword) that people may well have used it for
table names or aliases, and so their code will break.
Well, if it is a useful capability with a standard syntax, I think
we should go with the standard syntax even if it might break
application code that uses, as unquoted identifiers, words reserved
by the SQL standard. Of course, non-reserved is better than
reserved if we can manage it.
Playing around with the idea proposed there, it seems that it is
possible to make FILTER (and also OVER) unreserved keywords, by
splitting out the production of aggregate/window functions from normal
functions in gram.y. Aggregate/window functions are not allowed in
index_elem or func_table constructs, but they may appear in c_expr's.
That resolves the shift/reduce conflicts that would otherwise occur
from subsequent alias clauses, allowing FILTER and OVER to be
unreserved.There is a drawback --- certain error messages become less specific,
for example: "SELECT * FROM rank() OVER(ORDER BY random());" is now a
syntax error, rather than the more useful error saying that window
functions aren't allowed in the FROM clause. That doesn't seem like
such a common case though.What do you think?
I think it is OK if that gets a syntax error. If that's the "worst
case" I like this approach.
This also helps keep down the size of the generated parse tables,
doesn't it?
--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sun, Jun 23, 2013 at 07:44:26AM -0700, Kevin Grittner wrote:
Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
I'm still not happy that this patch is making FILTER a new reserved
keyword, because I think it is a common enough English word (and an
obscure enough SQL keyword) that people may well have used it for
table names or aliases, and so their code will break.Well, if it is a useful capability with a standard syntax, I think
we should go with the standard syntax even if it might break
application code that uses, as unquoted identifiers, words reserved
by the SQL standard.� Of course, non-reserved is better than
reserved if we can manage it.
I'm not entirely sure that I agree with this. The SQL standard
doesn't go adding keywords willy-nilly, or at least hasn't over a
fairly long stretch of time. I can get precise numbers on this if
needed. So far, only Tom and Greg have weighed in, Greg's response
being here:
/messages/by-id/CAM-w4HOd3N_ozMygs==Lm5+hU8yQKKaYutGjiNp6z2hAzDrSTA@mail.gmail.com
Playing around with the idea proposed there, it seems that it is
possible to make FILTER (and also OVER) unreserved keywords, by
splitting out the production of aggregate/window functions from normal
functions in gram.y. Aggregate/window functions are not allowed in
index_elem or func_table constructs, but they may appear in c_expr's.
That resolves the shift/reduce conflicts that would otherwise occur
from subsequent alias clauses, allowing FILTER and OVER to be
unreserved.There is a drawback --- certain error messages become less specific,
for example: "SELECT * FROM rank() OVER(ORDER BY random());" is now a
syntax error, rather than the more useful error saying that window
functions aren't allowed in the FROM clause. That doesn't seem like
such a common case though.What do you think?
I think it is OK if that gets a syntax error.� If that's the "worst
case" I like this approach.
I think reducing the usefulness of error messages is something we need
to think extremely hard about before we do. Is there maybe a way to
keep the error messages even if by some magic we manage to unreserve
the words?
This also helps keep down the size of the generated parse tables,
doesn't it?
Could well. I suspect we may need to rethink the whole way we do
grammar at some point, but that's for a later discussion when I (or
someone else) has something choate to say about it.
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
David Fetter <david@fetter.org> writes:
On Sun, Jun 23, 2013 at 07:44:26AM -0700, Kevin Grittner wrote:
I think it is OK if that gets a syntax error.� If that's the "worst
case" I like this approach.
I think reducing the usefulness of error messages is something we need
to think extremely hard about before we do. Is there maybe a way to
keep the error messages even if by some magic we manage to unreserve
the words?
Of the alternatives discussed so far, I don't really like anything
better than adding another special case to base_yylex(). Robert opined
in the other thread about RESPECT NULLS that the potential failure cases
with that approach are harder to reason about, which is true, but that
doesn't mean that we should accept failures we know of because there
might be failures we don't know of.
One thing that struck me while thinking about this is that it seems
like we've been going about it wrong in base_yylex() in any case.
For example, because we fold WITH followed by TIME into a special token
WITH_TIME, we will fail on a statement beginning
WITH time AS ...
even though "time" is a legal ColId. But suppose that instead of
merging the two tokens into one, we just changed the first token into
something special; that is, base_yylex would return a special token
WITH_FOLLOWED_BY_TIME and then TIME. We could then fix the above
problem by allowing either WITH or WITH_FOLLOWED_BY_TIME as the leading
keyword of a statement; and similarly for the few other places where
WITH can be followed by an arbitrary identifier.
Going on the same principle, we could probably let FILTER be an
unreserved keyword while FILTER_FOLLOWED_BY_PAREN could be a
type_func_name_keyword. (I've not tried this though.)
This idea doesn't help much for OVER because one of the alternatives for
over_clause is "OVER ColId", and I doubt we want to have base_yylex know
all the alternatives for ColId. I also had no great success with the
NULLS FIRST/LAST case: AFAICT the substitute token for NULLS still has
to be fully reserved, meaning that something like "select nulls last"
still doesn't work without quoting. We could maybe fix that with enough
denormalization of the index_elem productions, but it'd be ugly.
Could well. I suspect we may need to rethink the whole way we do
grammar at some point, but that's for a later discussion when I (or
someone else) has something choate to say about it.
It'd sure be interesting to know what the SQL committee's target parsing
algorithm is. I find it hard to believe they're uninformed enough to
not know that these random syntaxes they keep inventing are hard to deal
with in LALR(1). Or maybe they really don't give a damn about breaking
applications every time they invent a new reserved word?
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Jun 24, 2013 at 3:50 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Or maybe they really don't give a damn about breaking
applications every time they invent a new reserved word?
I think this is the obvious conclusion. In the standard the reserved
words are pretty explicitly reserved and not legal column names, no?
I think their model is that applications work with a certain version
of SQL and they're not expected to work with a new version without
extensive updating.
--
greg
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Greg Stark <stark@mit.edu> writes:
I think their model is that applications work with a certain version
of SQL and they're not expected to work with a new version without
extensive updating.
Hm. We could invent a "sql_version" parameter and tweak the lexer to
return keywords added in spec versions later than that as IDENT.
However, I fear such a parameter would be a major PITA from the user's
standpoint, just like most of our backwards-compatibility GUCs have
proven to be.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Greg Stark <stark@mit.edu> wrote:
On Mon, Jun 24, 2013 at 3:50 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Or maybe they really don't give a damn about breaking
applications every time they invent a new reserved word?I think this is the obvious conclusion. In the standard the reserved
words are pretty explicitly reserved and not legal column names, no?I think their model is that applications work with a certain version
of SQL and they're not expected to work with a new version without
extensive updating.
IMO it is insanity to write an application of any significant
complexity without a data abstraction layer sitting on a data
access layer, and it is silly to write such layers which are
intended to interface to SQL in a portable way without quoting
*all* identifiers sent to the server. As a developer, new reserved
words never bothered me in the slightest. At Wisconsin Courts the
most heavily used table has been called "Case" since 1989, and the
table to hold a row for each paper document printed to pay someone
is called "Check". No need to change the names just because SQL
started using those words for new language features after we had
the tables. And there is no reason to assume that any particular
word won't become reserved in the future.
I think the most likely explanation is not that they don't mind
breaking applications, but that they don't understand that there
are significant numbers of people who choose to write code in a
fashion which leaves them vulnerable to breakage when new reserved
words are added.
Being closer to the wide variety of users we know that there are
many such people out there, and we try to look after them as best
we can; which is entirely appropriate.
--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi David,
I hope this is the latest patch to review, right ?
I am going to review it.
I have gone through the discussion on this thread and I agree with Stephen
Frost that it don't add much improvements as such but definitely it is
going to be easy for contributors in this area as they don't need to go all
over to add any extra parameter they need to add. This patch simplifies it
well enough.
Will post my review soon.
Thanks.
On Mon, Jun 17, 2013 at 11:13 AM, David Fetter <david@fetter.org> wrote:
On Mon, Feb 11, 2013 at 10:48:38AM -0800, David Fetter wrote:
On Sun, Feb 10, 2013 at 10:09:19AM -0500, Tom Lane wrote:
David Fetter <david@fetter.org> writes:
Per suggestions and lots of help from Andrew Gierth, please find
attached a patch to clean up the call sites for FuncCall nodes, which
I'd like to expand centrally rather than in each of the 37 (or 38,but
I only redid 37) places where it's called. The remaining one is in
src/backend/nodes/copyfuncs.c, which has to be modified for any
changes in the that struct anyhow.TBH, I don't think this is an improvement.
The problem with adding a new field to any struct is that you have to
run around and examine (and, usually, modify) every place that
manufactures that type of struct. With a makeFuncCall defined like
this, you'd still have to do that; it would just become a lot easier
to forget to do so.If the subroutine were defined like most other makeXXX subroutines,
ie you have to supply *all* the fields, that argument would go away,
but the notational advantage is then dubious.The bigger-picture point is that you're proposing to make the coding
conventions for building FuncCalls different from what they are for
any other grammar node. I don't think that's a great idea; it will
mostly foster confusion.The major difference between FuncCalls and others is that `while most
raw-parsetree nodes are constructed only in their own syntax
productions, FuncCall is constructed in many places unrelated to
actual function call syntax.This really will make things a good bit easier on our successors.
Here's a rebased patch with comments illustrating how best to employ.
In my previous message, I characterized the difference between
FuncCalls and other raw-parsetree nodes. Is there some flaw in that
logic? If there isn't, is there some reason not to treat them in a
less redundant, more uniform manner as this patch does?Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.icsRemember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
--
Jeevan B Chalke
Senior Software Engineer, R&D
EnterpriseDB Corporation
The Enterprise PostgreSQL Company
Phone: +91 20 30589500
Website: www.enterprisedb.com
EnterpriseDB Blog: http://blogs.enterprisedb.com/
Follow us on Twitter: http://www.twitter.com/enterprisedb
This e-mail message (and any attachment) is intended for the use of the
individual or entity to whom it is addressed. This message contains
information from EnterpriseDB Corporation that may be privileged,
confidential, or exempt from disclosure under applicable law. If you are
not the intended recipient or authorized to receive this for the intended
recipient, any use, dissemination, distribution, retention, archiving, or
copying of this communication is strictly prohibited. If you have received
this e-mail in error, please notify the sender immediately by reply e-mail
and delete this message.
On Tue, Jun 25, 2013 at 11:11 AM, Jeevan Chalke <
jeevan.chalke@enterprisedb.com> wrote:
Hi David,
I hope this is the latest patch to review, right ?
I am going to review it.
I have gone through the discussion on this thread and I agree with Stephen
Frost that it don't add much improvements as such but definitely it is
going to be easy for contributors in this area as they don't need to go all
over to add any extra parameter they need to add. This patch simplifies it
well enough.Will post my review soon.
Assuming *makeFuncArgs_002.diff* is the latest patch, here are my review
points.
About this patch and feature:
===
This patch tries to reduce redundancy when we need FuncCall expression. With
this patch it will become easier to add new parameter if needed. We just
need
to put it's default value at centralized location (I mean in this new
function)
so that all other places need not require and changes. And this new
parameter
is handled by the new feature who demanded it keep other untouched.
Review comments on patch:
===
1. Can't apply with "git apply" command but able to do it with patch -p1.
So no
issues
2. Adds one whitespace error, hopefully it will get corrected once it goes
through pgindent.
3. There is absolutely NO user visibility and thus I guess we don't need any
test case. Also no documentation are needed.
4. Also make check went smooth and thus assumes that there is no issue as
such.
Even I couldn't find any issue with my testing other than regression suite.
5. I had a code walk-through over patch and it looks good to me. However,
following line change seems unrelated (Line 186 in your patch)
! $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~", $1,
(Node *) n, @2);
!
Changes required from author:
===
It will be good if you remove unrelated changes from the patch and possibly
all
white-space errors.
Thanks
Thanks.
On Mon, Jun 17, 2013 at 11:13 AM, David Fetter <david@fetter.org> wrote:
On Mon, Feb 11, 2013 at 10:48:38AM -0800, David Fetter wrote:
On Sun, Feb 10, 2013 at 10:09:19AM -0500, Tom Lane wrote:
David Fetter <david@fetter.org> writes:
Per suggestions and lots of help from Andrew Gierth, please find
attached a patch to clean up the call sites for FuncCall nodes,which
I'd like to expand centrally rather than in each of the 37 (or 38,
but
I only redid 37) places where it's called. The remaining one is in
src/backend/nodes/copyfuncs.c, which has to be modified for any
changes in the that struct anyhow.TBH, I don't think this is an improvement.
The problem with adding a new field to any struct is that you have to
run around and examine (and, usually, modify) every place that
manufactures that type of struct. With a makeFuncCall defined like
this, you'd still have to do that; it would just become a lot easier
to forget to do so.If the subroutine were defined like most other makeXXX subroutines,
ie you have to supply *all* the fields, that argument would go away,
but the notational advantage is then dubious.The bigger-picture point is that you're proposing to make the coding
conventions for building FuncCalls different from what they are for
any other grammar node. I don't think that's a great idea; it will
mostly foster confusion.The major difference between FuncCalls and others is that `while most
raw-parsetree nodes are constructed only in their own syntax
productions, FuncCall is constructed in many places unrelated to
actual function call syntax.This really will make things a good bit easier on our successors.
Here's a rebased patch with comments illustrating how best to employ.
In my previous message, I characterized the difference between
FuncCalls and other raw-parsetree nodes. Is there some flaw in that
logic? If there isn't, is there some reason not to treat them in a
less redundant, more uniform manner as this patch does?Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.icsRemember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers--
Jeevan B Chalke
Senior Software Engineer, R&D
EnterpriseDB Corporation
The Enterprise PostgreSQL CompanyPhone: +91 20 30589500
Website: www.enterprisedb.com
EnterpriseDB Blog: http://blogs.enterprisedb.com/
Follow us on Twitter: http://www.twitter.com/enterprisedbThis e-mail message (and any attachment) is intended for the use of the
individual or entity to whom it is addressed. This message contains
information from EnterpriseDB Corporation that may be privileged,
confidential, or exempt from disclosure under applicable law. If you are
not the intended recipient or authorized to receive this for the intended
recipient, any use, dissemination, distribution, retention, archiving, or
copying of this communication is strictly prohibited. If you have received
this e-mail in error, please notify the sender immediately by reply e-mail
and delete this message.
--
Jeevan B Chalke
Senior Software Engineer, R&D
EnterpriseDB Corporation
The Enterprise PostgreSQL Company
Phone: +91 20 30589500
Website: www.enterprisedb.com
EnterpriseDB Blog: http://blogs.enterprisedb.com/
Follow us on Twitter: http://www.twitter.com/enterprisedb
This e-mail message (and any attachment) is intended for the use of the
individual or entity to whom it is addressed. This message contains
information from EnterpriseDB Corporation that may be privileged,
confidential, or exempt from disclosure under applicable law. If you are
not the intended recipient or authorized to receive this for the intended
recipient, any use, dissemination, distribution, retention, archiving, or
copying of this communication is strictly prohibited. If you have received
this e-mail in error, please notify the sender immediately by reply e-mail
and delete this message.
On 24 June 2013 03:50, Tom Lane <tgl@sss.pgh.pa.us> wrote:
David Fetter <david@fetter.org> writes:
On Sun, Jun 23, 2013 at 07:44:26AM -0700, Kevin Grittner wrote:
I think it is OK if that gets a syntax error. If that's the "worst
case" I like this approach.I think reducing the usefulness of error messages is something we need
to think extremely hard about before we do. Is there maybe a way to
keep the error messages even if by some magic we manage to unreserve
the words?
The flip side is that the error message you get if you don't realise a
word is now reserved is not exactly useful: "Syntax error at or near
xxx". I don't know of any way to improve that though.
Of the alternatives discussed so far, I don't really like anything
better than adding another special case to base_yylex(). Robert opined
in the other thread about RESPECT NULLS that the potential failure cases
with that approach are harder to reason about, which is true, but that
doesn't mean that we should accept failures we know of because there
might be failures we don't know of.One thing that struck me while thinking about this is that it seems
like we've been going about it wrong in base_yylex() in any case.
For example, because we fold WITH followed by TIME into a special token
WITH_TIME, we will fail on a statement beginningWITH time AS ...
even though "time" is a legal ColId. But suppose that instead of
merging the two tokens into one, we just changed the first token into
something special; that is, base_yylex would return a special token
WITH_FOLLOWED_BY_TIME and then TIME. We could then fix the above
problem by allowing either WITH or WITH_FOLLOWED_BY_TIME as the leading
keyword of a statement; and similarly for the few other places where
WITH can be followed by an arbitrary identifier.Going on the same principle, we could probably let FILTER be an
unreserved keyword while FILTER_FOLLOWED_BY_PAREN could be a
type_func_name_keyword. (I've not tried this though.)
I've not tried either, but wouldn't that mean that "SELECT * FROM
list_filters() filter" would be legal, whereas "SELECT * FROM
list_filters() filter(id, val)" would be a syntax error? If so, I
don't think that would be an improvement.
Regards,
Dean
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Dean Rasheed <dean.a.rasheed@gmail.com> writes:
On 24 June 2013 03:50, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Going on the same principle, we could probably let FILTER be an
unreserved keyword while FILTER_FOLLOWED_BY_PAREN could be a
type_func_name_keyword. (I've not tried this though.)
I've not tried either, but wouldn't that mean that "SELECT * FROM
list_filters() filter" would be legal, whereas "SELECT * FROM
list_filters() filter(id, val)" would be a syntax error? If so, I
don't think that would be an improvement.
Hm, good point. The SQL committee really managed to choose some
unfortunate syntax here, didn't they.
I know it's heresy in these parts, but maybe we should consider
adopting a non-spec syntax for this feature? In particular, it's
really un-obvious why the FILTER clause shouldn't be inside rather
than outside the aggregate's parens, like ORDER BY.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
2013/6/25 Tom Lane <tgl@sss.pgh.pa.us>:
Dean Rasheed <dean.a.rasheed@gmail.com> writes:
On 24 June 2013 03:50, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Going on the same principle, we could probably let FILTER be an
unreserved keyword while FILTER_FOLLOWED_BY_PAREN could be a
type_func_name_keyword. (I've not tried this though.)I've not tried either, but wouldn't that mean that "SELECT * FROM
list_filters() filter" would be legal, whereas "SELECT * FROM
list_filters() filter(id, val)" would be a syntax error? If so, I
don't think that would be an improvement.Hm, good point. The SQL committee really managed to choose some
unfortunate syntax here, didn't they.I know it's heresy in these parts, but maybe we should consider
adopting a non-spec syntax for this feature? In particular, it's
really un-obvious why the FILTER clause shouldn't be inside rather
than outside the aggregate's parens, like ORDER BY.
the gram can be more free and final decision should be done in later stages ???
This technique was enough when I wrote prototype for CUBE ROLLUP
without CUBE ROLLUP reserwed keywords.
Regards
Pavel
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sun, Jun 23, 2013 at 10:50 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
David Fetter <david@fetter.org> writes:
On Sun, Jun 23, 2013 at 07:44:26AM -0700, Kevin Grittner wrote:
I think it is OK if that gets a syntax error. If that's the "worst
case" I like this approach.I think reducing the usefulness of error messages is something we need
to think extremely hard about before we do. Is there maybe a way to
keep the error messages even if by some magic we manage to unreserve
the words?Of the alternatives discussed so far, I don't really like anything
better than adding another special case to base_yylex(). Robert opined
in the other thread about RESPECT NULLS that the potential failure cases
with that approach are harder to reason about, which is true, but that
doesn't mean that we should accept failures we know of because there
might be failures we don't know of.
Sure, that's true; but the proposal on the other thread is just to
disallow invalid syntax early enough that it benefits the parser. The
error message is different, but I don't think it's a BAD error
message.
One thing that struck me while thinking about this is that it seems
like we've been going about it wrong in base_yylex() in any case.
For example, because we fold WITH followed by TIME into a special token
WITH_TIME, we will fail on a statement beginningWITH time AS ...
even though "time" is a legal ColId. But suppose that instead of
merging the two tokens into one, we just changed the first token into
something special; that is, base_yylex would return a special token
WITH_FOLLOWED_BY_TIME and then TIME. We could then fix the above
problem by allowing either WITH or WITH_FOLLOWED_BY_TIME as the leading
keyword of a statement; and similarly for the few other places where
WITH can be followed by an arbitrary identifier.Going on the same principle, we could probably let FILTER be an
unreserved keyword while FILTER_FOLLOWED_BY_PAREN could be a
type_func_name_keyword. (I've not tried this though.)
I think this whole direction is going to collapse under its own weight
VERY quickly. The problems you're describing are essentially
shift/reduce conflicts that are invisible because they're hidden
behind lexer magic. Part of the value of using a parser generator is
that it TELLS you when you've added ambiguous syntax. But it doesn't
know about lexer hacks, so stuff will just silently break. I think
this type of lexer hacks works reasonably well keyword-like things
that are used in just one place in the grammar. As soon as you get up
to two, the wheels come off - as with RESPECT NULLS vs. NULLS FIRST.
This idea doesn't help much for OVER because one of the alternatives for
over_clause is "OVER ColId", and I doubt we want to have base_yylex know
all the alternatives for ColId. I also had no great success with the
NULLS FIRST/LAST case: AFAICT the substitute token for NULLS still has
to be fully reserved, meaning that something like "select nulls last"
still doesn't work without quoting. We could maybe fix that with enough
denormalization of the index_elem productions, but it'd be ugly.
I don't think that particular example is very compelling - there's a
general rule that column aliases can't be keywords of any type.
That's not wonderful, and EnterpriseDB has had bug reports filed about
it, but the real-world impact is pretty minimal, certainly compared to
what we used to do which is not allow column aliases AT ALL.
It'd sure be interesting to know what the SQL committee's target parsing
algorithm is. I find it hard to believe they're uninformed enough to
not know that these random syntaxes they keep inventing are hard to deal
with in LALR(1). Or maybe they really don't give a damn about breaking
applications every time they invent a new reserved word?
Does the SQL committee contemplate that SELECT * FROM somefunc()
filter (id, val) should act as a table alias and that SELECT * FROM
somefunc() filter (where x > 1) is an aggregate filter? This all gets
much easier to understand if one of those constructs isn't allowed in
that particular context.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
I know it's heresy in these parts, but maybe we should consider
adopting a non-spec syntax for this feature? In particular, it's
really un-obvious why the FILTER clause shouldn't be inside rather
than outside the aggregate's parens, like ORDER BY.
Well, what other DBMSes support this feature? Will being non-spec
introduce migration pain?
--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Import Notes
Reply to msg id not found: WM6c058680e2430018a1a0ffc4efa66c9e6698239190eee81ed90357f7de4dbd3c029cd413101b7dcfa99cb522dad2e91d@asav-2.01.com
On 26 June 2013 01:01, Josh Berkus <josh@agliodbs.com> wrote:
I know it's heresy in these parts, but maybe we should consider
adopting a non-spec syntax for this feature? In particular, it's
really un-obvious why the FILTER clause shouldn't be inside rather
than outside the aggregate's parens, like ORDER BY.Well, what other DBMSes support this feature? Will being non-spec
introduce migration pain?
I can't find any, but that doesn't mean they don't exist, or aren't
being worked on.
To recap, the options currently on offer are:
1). Make FILTER a new partially reserved keyword, accepting that that
might break some users' application code.
2). Make FILTER unreserved, accepting that that will lead to syntax
errors rather than more specific error messages if the user tries to
use an aggregate/window function with FILTER or OVER in the FROM
clause of a query, or as an index expression.
3). Adopt a non-standard syntax for this feature, accepting that that
might conflict with other databases, and that we can never then claim
to have implemented T612, "Advanced OLAP operations".
4). Some other parser hack that will offer a better compromise?
My preference is for (2) as the lesser of several evils --- it's a
fairly narrow case where the quality of the error message is reduced.
Regards,
Dean
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
2013/6/26 Dean Rasheed <dean.a.rasheed@gmail.com>:
On 26 June 2013 01:01, Josh Berkus <josh@agliodbs.com> wrote:
I know it's heresy in these parts, but maybe we should consider
adopting a non-spec syntax for this feature? In particular, it's
really un-obvious why the FILTER clause shouldn't be inside rather
than outside the aggregate's parens, like ORDER BY.Well, what other DBMSes support this feature? Will being non-spec
introduce migration pain?I can't find any, but that doesn't mean they don't exist, or aren't
being worked on.To recap, the options currently on offer are:
1). Make FILTER a new partially reserved keyword, accepting that that
might break some users' application code.2). Make FILTER unreserved, accepting that that will lead to syntax
errors rather than more specific error messages if the user tries to
use an aggregate/window function with FILTER or OVER in the FROM
clause of a query, or as an index expression.3). Adopt a non-standard syntax for this feature, accepting that that
might conflict with other databases, and that we can never then claim
to have implemented T612, "Advanced OLAP operations".4). Some other parser hack that will offer a better compromise?
My preference is for (2) as the lesser of several evils --- it's a
fairly narrow case where the quality of the error message is reduced.
@2 looks well
Pavel
Regards,
Dean--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Dean Rasheed said:
To recap, the options currently on offer are:
1). Make FILTER a new partially reserved keyword, accepting that that
might break some users' application code.2). Make FILTER unreserved, accepting that that will lead to syntax
errors rather than more specific error messages if the user tries to
use an aggregate/window function with FILTER or OVER in the FROM
clause of a query, or as an index expression.3). Adopt a non-standard syntax for this feature, accepting that that
might conflict with other databases, and that we can never then claim
to have implemented T612, "Advanced OLAP operations".4). Some other parser hack that will offer a better compromise?
My preference is for (2) as the lesser of several evils --- it's a
fairly narrow case where the quality of the error message is reduced.
Possibly significant in this context is that there is a proof-of-concept
patch in development for another part of T612, namely inverse
distribution functions (e.g. percentile_disc and percentile_cont) which
should be available by the next CF, and which will require a similar
decision with respect to the keyword WITHIN (to support doing:
select percentile_cont(0.5) within group (order by x) from ...;
which returns the median value of x).
This syntax is much more widely supported than FILTER, including by both
Oracle and MSSQL, so a non-standard alternative is much less attractive.
A solution which suits both (i.e. either option 1 or option 2) would make
a whole lot more sense than trying to handle them differently.
--
Andrew (irc:RhodiumToad)
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Andrew Gierth <andrew@tao11.riddles.org.uk> writes:
Possibly significant in this context is that there is a proof-of-concept
patch in development for another part of T612, namely inverse
distribution functions (e.g. percentile_disc and percentile_cont) which
should be available by the next CF, and which will require a similar
decision with respect to the keyword WITHIN (to support doing:
select percentile_cont(0.5) within group (order by x) from ...;
which returns the median value of x).
Agreed, separating out the function-call-with-trailing-declaration
syntaxes so they aren't considered in FROM and index_elem seems like
the best compromise.
If we do that for window function OVER clauses as well, can we make
OVER less reserved?
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Tom Lane said:
Agreed, separating out the function-call-with-trailing-declaration
syntaxes so they aren't considered in FROM and index_elem seems like
the best compromise.If we do that for window function OVER clauses as well, can we make
OVER less reserved?
Yes.
At least, I tried it with both OVER and FILTER unreserved and there
were no grammar conflicts (and I didn't have to do anything fancy to
avoid them), and it passed regression with the exception of the
changed error message for window functions in the from-clause.
So is this the final decision on how to proceed? It seems good to me,
and I can work with David to get it done.
--
Andrew (irc:RhodiumToad)
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hello
2013/6/27 Andrew Gierth <andrew@tao11.riddles.org.uk>:
Tom Lane said:
Agreed, separating out the function-call-with-trailing-declaration
syntaxes so they aren't considered in FROM and index_elem seems like
the best compromise.If we do that for window function OVER clauses as well, can we make
OVER less reserved?Yes.
At least, I tried it with both OVER and FILTER unreserved and there
were no grammar conflicts (and I didn't have to do anything fancy to
avoid them), and it passed regression with the exception of the
changed error message for window functions in the from-clause.So is this the final decision on how to proceed? It seems good to me,
and I can work with David to get it done.
Isn't dangerous do OVER unreserved keyword??
Regards
Pavel
--
Andrew (irc:RhodiumToad)--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Jun 27, 2013 at 08:41:59AM +0000, Andrew Gierth wrote:
Tom Lane said:
Agreed, separating out the function-call-with-trailing-declaration
syntaxes so they aren't considered in FROM and index_elem seems
like the best compromise.If we do that for window function OVER clauses as well, can we
make OVER less reserved?Yes.
At least, I tried it with both OVER and FILTER unreserved and there
were no grammar conflicts (and I didn't have to do anything fancy to
avoid them), and it passed regression with the exception of the
changed error message for window functions in the from-clause.So is this the final decision on how to proceed? It seems good to
me, and I can work with David to get it done.
If this is really the direction people want to go, I'm in. Is there
some code I can look at?
I still submit that having our reserved word ducks in a row in advance
is a saner way to go about this, and will work up a patch for that as
I have time.
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Andrew Gierth <andrew@tao11.riddles.org.uk> writes:
Tom Lane said:
Agreed, separating out the function-call-with-trailing-declaration
syntaxes so they aren't considered in FROM and index_elem seems like
the best compromise.If we do that for window function OVER clauses as well, can we make
OVER less reserved?
Yes.
At least, I tried it with both OVER and FILTER unreserved and there
were no grammar conflicts (and I didn't have to do anything fancy to
avoid them), and it passed regression with the exception of the
changed error message for window functions in the from-clause.
So is this the final decision on how to proceed? It seems good to me,
and I can work with David to get it done.
Yeah, please submit a separate patch that just refactors the existing
grammar as above; that'll simplify reviewing.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Pavel Stehule <pavel.stehule@gmail.com> writes:
Tom Lane said:
If we do that for window function OVER clauses as well, can we make
OVER less reserved?
Isn't dangerous do OVER unreserved keyword??
How so? The worst-case scenario is that we find we have to make it more
reserved again in some future release, as a consequence of some new
randomness from the SQL committee. That will just return us to the
status quo, in which anybody who uses OVER as a table/column name has
been broken since about 8.4. Since we still hear of people using
releases as old as 7.2.x, I'm sure there are a few out there who would
still be helped if we could de-reserve OVER again. (Not to mention
people migrating from other systems in which it's not a keyword.)
In any case, the general project policy has been to never make keywords
any more reserved than we absolutely have to. If we didn't care about
this, we wouldn't be bothering with four separate categories of keywords.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 6/23/13 10:50 PM, Tom Lane wrote:
It'd sure be interesting to know what the SQL committee's target parsing
algorithm is.
It's whatever Oracle and IBM implement.
Or maybe they really don't give a damn about breaking
applications every time they invent a new reserved word?
Well, yes, I think that policy was built into the language at the very
beginning.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 27 June 2013 15:05, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Andrew Gierth <andrew@tao11.riddles.org.uk> writes:
Tom Lane said:
Agreed, separating out the function-call-with-trailing-declaration
syntaxes so they aren't considered in FROM and index_elem seems like
the best compromise.If we do that for window function OVER clauses as well, can we make
OVER less reserved?Yes.
At least, I tried it with both OVER and FILTER unreserved and there
were no grammar conflicts (and I didn't have to do anything fancy to
avoid them), and it passed regression with the exception of the
changed error message for window functions in the from-clause.So is this the final decision on how to proceed? It seems good to me,
and I can work with David to get it done.Yeah, please submit a separate patch that just refactors the existing
grammar as above; that'll simplify reviewing.
In that case, I'll re-review the latest FILTER patch over the weekend
on the understanding that the reserved/unreserved keyword issue will
be resolved in separate patch.
Regards,
Dean
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Jun 25, 2013 at 02:54:55PM +0530, Jeevan Chalke wrote:
On Tue, Jun 25, 2013 at 11:11 AM, Jeevan Chalke <
jeevan.chalke@enterprisedb.com> wrote:Hi David,
I hope this is the latest patch to review, right ?
I am going to review it.
I have gone through the discussion on this thread and I agree with Stephen
Frost that it don't add much improvements as such but definitely it is
going to be easy for contributors in this area as they don't need to go all
over to add any extra parameter they need to add. This patch simplifies it
well enough.Will post my review soon.
Assuming *makeFuncArgs_002.diff* is the latest patch, here are my review
points.About this patch and feature:
===
This patch tries to reduce redundancy when we need FuncCall expression. With
this patch it will become easier to add new parameter if needed. We just
need
to put it's default value at centralized location (I mean in this new
function)
so that all other places need not require and changes. And this new
parameter
is handled by the new feature who demanded it keep other untouched.Review comments on patch:
===
1. Can't apply with "git apply" command but able to do it with patch -p1.
So no
issues
2. Adds one whitespace error, hopefully it will get corrected once it goes
through pgindent.
3. There is absolutely NO user visibility and thus I guess we don't need any
test case. Also no documentation are needed.
4. Also make check went smooth and thus assumes that there is no issue as
such.
Even I couldn't find any issue with my testing other than regression suite.
5. I had a code walk-through over patch and it looks good to me. However,
following line change seems unrelated (Line 186 in your patch)! $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~", $1,
(Node *) n, @2);
!Changes required from author:
===
It will be good if you remove unrelated changes from the patch and possibly
all
white-space errors.Thanks
Thanks for the review!
Please find attached the latest patch.
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
Attachments:
makeFuncArgs_003.difftext/plain; charset=us-asciiDownload
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index c487db9..245aef2 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -508,3 +508,28 @@ makeDefElemExtended(char *nameSpace, char *name, Node *arg,
return res;
}
+
+/*
+ * makeFuncCall -
+ *
+ * Initialize a FuncCall struct with the information every caller must
+ * supply. Any non-default parameters have to be handled by the
+ * caller.
+ *
+ */
+
+FuncCall *
+makeFuncCall(List *name, List *args, int location)
+{
+ FuncCall *n = makeNode(FuncCall);
+ n->funcname = name;
+ n->args = args;
+ n->location = location;
+ n->agg_order = NIL;
+ n->agg_star = FALSE;
+ n->agg_distinct = FALSE;
+ n->func_variadic = FALSE;
+ n->over = NULL;
+ return n;
+}
+
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5094226..24a585e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10487,16 +10487,9 @@ a_expr: c_expr { $$ = $1; }
}
| a_expr AT TIME ZONE a_expr %prec AT
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("timezone");
- n->args = list_make2($5, $1);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @2;
- $$ = (Node *) n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("timezone"),
+ list_make2($5, $1),
+ @2);
}
/*
* These operators must be called out explicitly in order to make use
@@ -10548,113 +10541,65 @@ a_expr: c_expr { $$ = $1; }
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~", $1, $3, @2); }
| a_expr LIKE a_expr ESCAPE a_expr
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("like_escape");
- n->args = list_make2($3, $5);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @2;
+ FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
+ list_make2($3, $5),
+ @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~", $1, (Node *) n, @2);
}
| a_expr NOT LIKE a_expr
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~", $1, $4, @2); }
| a_expr NOT LIKE a_expr ESCAPE a_expr
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("like_escape");
- n->args = list_make2($4, $6);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @2;
+ FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
+ list_make2($4, $6),
+ @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~", $1, (Node *) n, @2);
}
| a_expr ILIKE a_expr
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~*", $1, $3, @2); }
| a_expr ILIKE a_expr ESCAPE a_expr
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("like_escape");
- n->args = list_make2($3, $5);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @2;
+ FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
+ list_make2($3, $5),
+ @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~*", $1, (Node *) n, @2);
}
| a_expr NOT ILIKE a_expr
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~*", $1, $4, @2); }
| a_expr NOT ILIKE a_expr ESCAPE a_expr
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("like_escape");
- n->args = list_make2($4, $6);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @2;
+ FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
+ list_make2($4, $6),
+ @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~*", $1, (Node *) n, @2);
}
| a_expr SIMILAR TO a_expr %prec SIMILAR
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("similar_escape");
- n->args = list_make2($4, makeNullAConst(-1));
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @2;
+ FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
+ list_make2($4, makeNullAConst(-1)),
+ @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~", $1, (Node *) n, @2);
}
| a_expr SIMILAR TO a_expr ESCAPE a_expr
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("similar_escape");
- n->args = list_make2($4, $6);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @2;
+ FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
+ list_make2($4, $6),
+ @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~", $1, (Node *) n, @2);
}
| a_expr NOT SIMILAR TO a_expr %prec SIMILAR
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("similar_escape");
- n->args = list_make2($5, makeNullAConst(-1));
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @2;
+ FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
+ list_make2($5, makeNullAConst(-1)),
+ @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~", $1, (Node *) n, @2);
}
| a_expr NOT SIMILAR TO a_expr ESCAPE a_expr
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("similar_escape");
- n->args = list_make2($5, $7);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @2;
+ FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
+ list_make2($5, $7),
+ @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~", $1, (Node *) n, @2);
}
@@ -11089,97 +11034,54 @@ c_expr: columnref { $$ = $1; }
*/
func_expr: func_name '(' ')' over_clause
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = $1;
- n->args = NIL;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
+ FuncCall *n = makeFuncCall($1, NIL, @1);
n->over = $4;
- n->location = @1;
$$ = (Node *)n;
}
| func_name '(' func_arg_list ')' over_clause
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = $1;
- n->args = $3;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
+ FuncCall *n = makeFuncCall($1, $3, @1);
n->over = $5;
- n->location = @1;
$$ = (Node *)n;
}
| func_name '(' VARIADIC func_arg_expr ')' over_clause
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = $1;
- n->args = list_make1($4);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
+ FuncCall *n = makeFuncCall($1, list_make1($4), @1);
n->func_variadic = TRUE;
n->over = $6;
- n->location = @1;
$$ = (Node *)n;
}
| func_name '(' func_arg_list ',' VARIADIC func_arg_expr ')' over_clause
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = $1;
- n->args = lappend($3, $6);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
+ FuncCall *n = makeFuncCall($1, lappend($3, $6), @1);
n->func_variadic = TRUE;
n->over = $8;
- n->location = @1;
$$ = (Node *)n;
}
| func_name '(' func_arg_list sort_clause ')' over_clause
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = $1;
- n->args = $3;
+ FuncCall *n = makeFuncCall($1, $3, @1);
n->agg_order = $4;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
n->over = $6;
- n->location = @1;
$$ = (Node *)n;
}
| func_name '(' ALL func_arg_list opt_sort_clause ')' over_clause
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = $1;
- n->args = $4;
+ FuncCall *n = makeFuncCall($1, $4, @1);
n->agg_order = $5;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
/* Ideally we'd mark the FuncCall node to indicate
* "must be an aggregate", but there's no provision
* for that in FuncCall at the moment.
*/
- n->func_variadic = FALSE;
n->over = $7;
- n->location = @1;
$$ = (Node *)n;
}
| func_name '(' DISTINCT func_arg_list opt_sort_clause ')' over_clause
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = $1;
- n->args = $4;
+ FuncCall *n = makeFuncCall($1, $4, @1);
n->agg_order = $5;
- n->agg_star = FALSE;
n->agg_distinct = TRUE;
- n->func_variadic = FALSE;
n->over = $7;
- n->location = @1;
$$ = (Node *)n;
}
| func_name '(' '*' ')' over_clause
@@ -11194,29 +11096,16 @@ func_expr: func_name '(' ')' over_clause
* so that later processing can detect what the argument
* really was.
*/
- FuncCall *n = makeNode(FuncCall);
- n->funcname = $1;
- n->args = NIL;
- n->agg_order = NIL;
+ FuncCall *n = makeFuncCall($1, NIL, @1);
n->agg_star = TRUE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
n->over = $5;
- n->location = @1;
$$ = (Node *)n;
}
| COLLATION FOR '(' a_expr ')'
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("pg_collation_for");
- n->args = list_make1($4);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("pg_collation_for"),
+ list_make1($4),
+ @1);
}
| CURRENT_DATE
{
@@ -11268,16 +11157,7 @@ func_expr: func_name '(' ')' over_clause
* Translate as "now()", since we have a function that
* does exactly what is needed.
*/
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("now");
- n->args = NIL;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("now"), NIL, @1);
}
| CURRENT_TIMESTAMP '(' Iconst ')'
{
@@ -11340,96 +11220,33 @@ func_expr: func_name '(' ')' over_clause
}
| CURRENT_ROLE
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("current_user");
- n->args = NIL;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("current_user"), NIL, @1);
}
| CURRENT_USER
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("current_user");
- n->args = NIL;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("current_user"), NIL, @1);
}
| SESSION_USER
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("session_user");
- n->args = NIL;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("session_user"), NIL, @1);
}
| USER
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("current_user");
- n->args = NIL;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("current_user"), NIL, @1);
}
| CURRENT_CATALOG
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("current_database");
- n->args = NIL;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("current_database"), NIL, @1);
}
| CURRENT_SCHEMA
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("current_schema");
- n->args = NIL;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("current_schema"), NIL, @1);
}
| CAST '(' a_expr AS Typename ')'
{ $$ = makeTypeCast($3, $5, @1); }
| EXTRACT '(' extract_list ')'
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("date_part");
- n->args = $3;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("date_part"), $3, @1);
}
| OVERLAY '(' overlay_list ')'
{
@@ -11438,46 +11255,19 @@ func_expr: func_name '(' ')' over_clause
* overlay(A PLACING B FROM C) is converted to
* overlay(A, B, C)
*/
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("overlay");
- n->args = $3;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("overlay"), $3, @1);
}
| POSITION '(' position_list ')'
{
/* position(A in B) is converted to position(B, A) */
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("position");
- n->args = $3;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("position"), $3, @1);
}
| SUBSTRING '(' substr_list ')'
{
/* substring(A from B for C) is converted to
* substring(A, B, C) - thomas 2000-11-28
*/
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("substring");
- n->args = $3;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("substring"), $3, @1);
}
| TREAT '(' a_expr AS Typename ')'
{
@@ -11486,75 +11276,32 @@ func_expr: func_name '(' ')' over_clause
* In SQL99, this is intended for use with structured UDTs,
* but let's make this a generally useful form allowing stronger
* coercions than are handled by implicit casting.
- */
- FuncCall *n = makeNode(FuncCall);
- /* Convert SystemTypeName() to SystemFuncName() even though
+ *
+ * Convert SystemTypeName() to SystemFuncName() even though
* at the moment they result in the same thing.
*/
- n->funcname = SystemFuncName(((Value *)llast($5->names))->val.str);
- n->args = list_make1($3);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName(((Value *)llast($5->names))->val.str),
+ list_make1($3),
+ @1);
}
| TRIM '(' BOTH trim_list ')'
{
/* various trim expressions are defined in SQL
* - thomas 1997-07-19
*/
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("btrim");
- n->args = $4;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("btrim"), $4, @1);
}
| TRIM '(' LEADING trim_list ')'
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("ltrim");
- n->args = $4;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("ltrim"), $4, @1);
}
| TRIM '(' TRAILING trim_list ')'
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("rtrim");
- n->args = $4;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("rtrim"), $4, @1);
}
| TRIM '(' trim_list ')'
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("btrim");
- n->args = $3;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("btrim"), $3, @1);
}
| NULLIF '(' a_expr ',' a_expr ')'
{
@@ -11607,16 +11354,7 @@ func_expr: func_name '(' ')' over_clause
{
/* xmlexists(A PASSING [BY REF] B [BY REF]) is
* converted to xmlexists(A, B)*/
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("xmlexists");
- n->args = list_make2($3, $4);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("xmlexists"), list_make2($3, $4), @1);
}
| XMLFOREST '(' xml_attribute_list ')'
{
@@ -13272,9 +13010,7 @@ makeBoolAConst(bool state, int location)
static FuncCall *
makeOverlaps(List *largs, List *rargs, int location, core_yyscan_t yyscanner)
{
- FuncCall *n = makeNode(FuncCall);
-
- n->funcname = SystemFuncName("overlaps");
+ FuncCall *n;
if (list_length(largs) == 1)
largs = lappend(largs, largs);
else if (list_length(largs) != 2)
@@ -13289,13 +13025,7 @@ makeOverlaps(List *largs, List *rargs, int location, core_yyscan_t yyscanner)
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("wrong number of parameters on right side of OVERLAPS expression"),
parser_errposition(location)));
- n->args = list_concat(largs, rargs);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = location;
+ n = makeFuncCall(SystemFuncName("overlaps"), list_concat(largs, rargs), location);
return n;
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index b426a45..40e3717 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -448,16 +448,9 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
castnode->typeName = SystemTypeName("regclass");
castnode->arg = (Node *) snamenode;
castnode->location = -1;
- funccallnode = makeNode(FuncCall);
- funccallnode->funcname = SystemFuncName("nextval");
- funccallnode->args = list_make1(castnode);
- funccallnode->agg_order = NIL;
- funccallnode->agg_star = false;
- funccallnode->agg_distinct = false;
- funccallnode->func_variadic = false;
- funccallnode->over = NULL;
- funccallnode->location = -1;
-
+ funccallnode = makeFuncCall(SystemFuncName("nextval"),
+ list_make1(castnode),
+ -1);
constraint = makeNode(Constraint);
constraint->contype = CONSTR_DEFAULT;
constraint->location = -1;
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index ee0c365..3b71b5b 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -75,6 +75,8 @@ extern TypeName *makeTypeNameFromOid(Oid typeOid, int32 typmod);
extern FuncExpr *makeFuncExpr(Oid funcid, Oid rettype, List *args,
Oid funccollid, Oid inputcollid, CoercionForm fformat);
+extern FuncCall *makeFuncCall(List *name, List *args, int location);
+
extern DefElem *makeDefElem(char *name, Node *arg);
extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
DefElemAction defaction);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6723647..532416d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -285,6 +285,11 @@ typedef struct CollateClause
* construct *must* be an aggregate call. Otherwise, it might be either an
* aggregate or some other kind of function. However, if OVER is present
* it had better be an aggregate or window function.
+ *
+ * Normally, you'd initialize this via makeFuncCall() and then only
+ * change the parts of the struct its defaults don't match afterwards
+ * if needed.
+ *
*/
typedef struct FuncCall
{
David Fetter <david@fetter.org> writes:
Please find attached the latest patch.
I remain of the opinion that this is simply a bad idea. It is unlike
our habits for constructing other types of nodes, and makes it harder
not easier to find all the places that need to be updated when adding
another field to FuncCall.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Jun 28, 2013 at 10:31:16AM -0400, Tom Lane wrote:
David Fetter <david@fetter.org> writes:
Please find attached the latest patch.
I remain of the opinion that this is simply a bad idea. It is unlike
our habits for constructing other types of nodes, and makes it harder
not easier to find all the places that need to be updated when adding
another field to FuncCall.
With utmost respect, this is just not true.
There's exactly one place that needs updating after adding another
field to FuncCall in the general case where the default value of the
field doesn't affect most setters of FuncCall, i.e. where the default
default is the right thing for current setters. In specific cases
where such a field might need to be set to something other than its
default value, finding calls to makeFuncCall is just as easy, and with
some tools like cscope, even easier.
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Jun 28, 2013 at 10:31:16AM -0400, Tom Lane wrote:
David Fetter <david@fetter.org> writes:
Please find attached the latest patch.
I remain of the opinion that this is simply a bad idea. It is unlike
our habits for constructing other types of nodes, and makes it harder
not easier to find all the places that need to be updated when adding
another field to FuncCall.
We have precedents in makeRangeVar() and makeDefElem().
For me, this change would make it slightly easier to visit affected code sites
after a change. I could cscope for callers of makeFuncCall() instead of doing
"git grep 'makeNode(FuncCall)'". The advantage could go either way depending
on your tooling, though.
By having each call site only mention the seldom-used fields for which it does
something special, the distinctive aspects of the call site stand out better.
That's a nice advantage.
--
Noah Misch
EnterpriseDB http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Jun 28, 2013 at 10:31 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
David Fetter <david@fetter.org> writes:
Please find attached the latest patch.
I remain of the opinion that this is simply a bad idea. It is unlike
our habits for constructing other types of nodes, and makes it harder
not easier to find all the places that need to be updated when adding
another field to FuncCall.
I think it's a nice code cleanup. I don't understand your objection.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 6/28/13 11:30 AM, Robert Haas wrote:
On Fri, Jun 28, 2013 at 10:31 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
David Fetter <david@fetter.org> writes:
Please find attached the latest patch.
I remain of the opinion that this is simply a bad idea. It is unlike
our habits for constructing other types of nodes, and makes it harder
not easier to find all the places that need to be updated when adding
another field to FuncCall.I think it's a nice code cleanup. I don't understand your objection.
Yeah, I was reading the patch thinking, yes, finally someone cleans that up.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 21 June 2013 06:16, David Fetter <david@fetter.org> wrote:
Please find attached a patch which allows subqueries in the FILTER
clause and adds regression testing for same.
This needs re-basing/merging following Robert's recent commit to make
OVER unreserved.
Regards,
Dean
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Jun 28, 2013 at 09:22:52PM +0100, Dean Rasheed wrote:
On 21 June 2013 06:16, David Fetter <david@fetter.org> wrote:
Please find attached a patch which allows subqueries in the FILTER
clause and adds regression testing for same.This needs re-basing/merging following Robert's recent commit to make
OVER unreserved.
Please find attached. Thanks, Andrew Gierth! In this one, FILTER is
no longer a reserved word.
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
Attachments:
filter_007.difftext/plain; charset=us-asciiDownload
diff --git a/doc/src/sgml/keywords.sgml b/doc/src/sgml/keywords.sgml
index 5e3b33a..ecfde99 100644
--- a/doc/src/sgml/keywords.sgml
+++ b/doc/src/sgml/keywords.sgml
@@ -1786,7 +1786,7 @@
</row>
<row>
<entry><token>FILTER</token></entry>
- <entry></entry>
+ <entry>non-reserved</entry>
<entry>reserved</entry>
<entry>reserved</entry>
<entry></entry>
@@ -3200,7 +3200,7 @@
</row>
<row>
<entry><token>OVER</token></entry>
- <entry>reserved (can be function or type)</entry>
+ <entry>non-reserved</entry>
<entry>reserved</entry>
<entry>reserved</entry>
<entry></entry>
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 68309ba..b289a3a 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -594,10 +594,13 @@ GROUP BY <replaceable class="parameter">expression</replaceable> [, ...]
</para>
<para>
- Aggregate functions, if any are used, are computed across all rows
+ In the absence of a <literal>FILTER</literal> clause,
+ aggregate functions, if any are used, are computed across all rows
making up each group, producing a separate value for each group
(whereas without <literal>GROUP BY</literal>, an aggregate
produces a single value computed across all the selected rows).
+ When a <literal>FILTER</literal> clause is present, only those
+ rows matching the FILTER clause are included.
When <literal>GROUP BY</literal> is present, it is not valid for
the <command>SELECT</command> list expressions to refer to
ungrouped columns except within aggregate functions or if the
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index b139212..c4d5f33 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -1562,24 +1562,26 @@ sqrt(2)
syntax of an aggregate expression is one of the following:
<synopsis>
-<replaceable>aggregate_name</replaceable> (<replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] )
-<replaceable>aggregate_name</replaceable> (ALL <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] )
-<replaceable>aggregate_name</replaceable> (DISTINCT <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] )
-<replaceable>aggregate_name</replaceable> ( * )
+<replaceable>aggregate_name</replaceable> (<replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
+<replaceable>aggregate_name</replaceable> (ALL <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
+<replaceable>aggregate_name</replaceable> (DISTINCT <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
+<replaceable>aggregate_name</replaceable> ( * ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
</synopsis>
where <replaceable>aggregate_name</replaceable> is a previously
defined aggregate (possibly qualified with a schema name),
- <replaceable>expression</replaceable> is
- any value expression that does not itself contain an aggregate
- expression or a window function call, and
- <replaceable>order_by_clause</replaceable> is a optional
- <literal>ORDER BY</> clause as described below.
+ <replaceable>expression</replaceable> is any value expression that
+ does not itself contain an aggregate expression or a window
+ function call, <replaceable>order_by_clause</replaceable> is a
+ optional <literal>ORDER BY</> clause as described below. The
+ <replaceable>aggregate_name</replaceable> can also be suffixed
+ with <literal>FILTER</literal> as described below.
</para>
<para>
- The first form of aggregate expression invokes the aggregate
- once for each input row.
+ The first form of aggregate expression invokes the aggregate once
+ for each input row, or when a FILTER clause is present, each row
+ matching same.
The second form is the same as the first, since
<literal>ALL</literal> is the default.
The third form invokes the aggregate once for each distinct value
@@ -1607,6 +1609,21 @@ sqrt(2)
</para>
<para>
+ Adding a FILTER clause to an aggregate specifies which values of
+ the expression being aggregated to evaluate. For example:
+<programlisting>
+SELECT
+ count(*) AS unfiltered,
+ count(*) FILTER (WHERE i < 5) AS filtered
+FROM generate_series(1,10) AS s(i);
+ unfiltered | filtered
+------------+----------
+ 10 | 4
+(1 row)
+</programlisting>
+ </para>
+
+ <para>
Ordinarily, the input rows are fed to the aggregate function in an
unspecified order. In many cases this does not matter; for example,
<function>min</> produces the same result no matter what order it
@@ -1709,10 +1726,10 @@ SELECT string_agg(a ORDER BY a, ',') FROM table; -- incorrect
The syntax of a window function call is one of the following:
<synopsis>
-<replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) OVER ( <replaceable class="parameter">window_definition</replaceable> )
-<replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) OVER <replaceable>window_name</replaceable>
-<replaceable>function_name</replaceable> ( * ) OVER ( <replaceable class="parameter">window_definition</replaceable> )
-<replaceable>function_name</replaceable> ( * ) OVER <replaceable>window_name</replaceable>
+<replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER ( <replaceable class="parameter">window_definition</replaceable> )
+<replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER <replaceable>window_name</replaceable>
+<replaceable>function_name</replaceable> ( * ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER ( <replaceable class="parameter">window_definition</replaceable> )
+<replaceable>function_name</replaceable> ( * ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER <replaceable>window_name</replaceable>
</synopsis>
where <replaceable class="parameter">window_definition</replaceable>
has the syntax
@@ -1836,16 +1853,18 @@ UNBOUNDED FOLLOWING
The built-in window functions are described in <xref
linkend="functions-window-table">. Other window functions can be added by
the user. Also, any built-in or user-defined aggregate function can be
- used as a window function.
+ used as a window function. A <literal>FILTER</literal> clause is
+ only valid for aggregate functions used in windowing.
</para>
<para>
- The syntaxes using <literal>*</> are used for calling parameter-less
- aggregate functions as window functions, for example
- <literal>count(*) OVER (PARTITION BY x ORDER BY y)</>.
- The asterisk (<literal>*</>) is customarily not used for non-aggregate window functions.
- Aggregate window functions, unlike normal aggregate functions, do not
- allow <literal>DISTINCT</> or <literal>ORDER BY</> to be used within the
+ The syntaxes using <literal>*</> are used for calling
+ parameter-less aggregate functions as window functions, for
+ example <literal>count(*) OVER (PARTITION BY x ORDER BY y)</>.
+ The asterisk (<literal>*</>) is customarily not used for
+ non-aggregate window functions. Aggregate window functions,
+ unlike normal aggregate functions, do not allow
+ <literal>DISTINCT</> or <literal>ORDER BY</> to be used within the
function argument list.
</para>
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 1388183..34dbef9 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -4410,6 +4410,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
astate->args = (List *) ExecInitExpr((Expr *) aggref->args,
parent);
+ astate->agg_filter = ExecInitExpr(aggref->agg_filter, parent);
/*
* Complain if the aggregate's arguments contain any
@@ -4448,6 +4449,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
wfstate->args = (List *) ExecInitExpr((Expr *) wfunc->args,
parent);
+ wfstate->agg_filter = ExecInitExpr(wfunc->agg_filter, parent);
/*
* Complain if the windowfunc's arguments contain any
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 12e1b8e..a43fdf2 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -381,7 +381,7 @@ sql_fn_post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var)
list_make1(subfield),
list_make1(param),
NIL, false, false, false,
- NULL, true, cref->location);
+ NULL, NULL, true, cref->location);
}
return param;
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index c741131..19105d2 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -488,6 +488,18 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
int i;
TupleTableSlot *slot;
+ /* Skip anything FILTERed out */
+ ExprState *filter = peraggstate->aggrefstate->agg_filter;
+ if (filter)
+ {
+ MemoryContext oldcontext = MemoryContextSwitchTo(aggstate->tmpcontext->ecxt_per_tuple_memory);
+ bool isnull;
+ Datum res = ExecEvalExpr(filter, aggstate->tmpcontext, &isnull, NULL);
+ MemoryContextSwitchTo(oldcontext);
+ if (isnull || !DatumGetBool(res))
+ continue;
+ }
+
/* Evaluate the current input expressions for this aggregate */
slot = ExecProject(peraggstate->evalproj, NULL);
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index d9f0e79..c00b058 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -227,9 +227,22 @@ advance_windowaggregate(WindowAggState *winstate,
int i;
MemoryContext oldContext;
ExprContext *econtext = winstate->tmpcontext;
+ ExprState *filter = wfuncstate->agg_filter;
oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+ /* Skip anything FILTERed out */
+ if (filter)
+ {
+ bool isnull;
+ Datum res = ExecEvalExpr(filter, econtext, &isnull, NULL);
+ if (isnull || !DatumGetBool(res))
+ {
+ MemoryContextSwitchTo(oldContext);
+ return;
+ }
+ }
+
/* We start from 1, since the 0th arg will be the transition value */
i = 1;
foreach(arg, wfuncstate->args)
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index b5b8d63..050fc83 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1137,6 +1137,7 @@ _copyAggref(const Aggref *from)
COPY_NODE_FIELD(args);
COPY_NODE_FIELD(aggorder);
COPY_NODE_FIELD(aggdistinct);
+ COPY_NODE_FIELD(agg_filter);
COPY_SCALAR_FIELD(aggstar);
COPY_SCALAR_FIELD(agglevelsup);
COPY_LOCATION_FIELD(location);
@@ -1157,6 +1158,7 @@ _copyWindowFunc(const WindowFunc *from)
COPY_SCALAR_FIELD(wincollid);
COPY_SCALAR_FIELD(inputcollid);
COPY_NODE_FIELD(args);
+ COPY_NODE_FIELD(agg_filter);
COPY_SCALAR_FIELD(winref);
COPY_SCALAR_FIELD(winstar);
COPY_SCALAR_FIELD(winagg);
@@ -2155,6 +2157,7 @@ _copyFuncCall(const FuncCall *from)
COPY_SCALAR_FIELD(agg_star);
COPY_SCALAR_FIELD(agg_distinct);
COPY_SCALAR_FIELD(func_variadic);
+ COPY_NODE_FIELD(agg_filter);
COPY_NODE_FIELD(over);
COPY_LOCATION_FIELD(location);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 3f96595..e1f63f1 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -196,6 +196,7 @@ _equalAggref(const Aggref *a, const Aggref *b)
COMPARE_NODE_FIELD(args);
COMPARE_NODE_FIELD(aggorder);
COMPARE_NODE_FIELD(aggdistinct);
+ COMPARE_NODE_FIELD(agg_filter);
COMPARE_SCALAR_FIELD(aggstar);
COMPARE_SCALAR_FIELD(agglevelsup);
COMPARE_LOCATION_FIELD(location);
@@ -211,6 +212,7 @@ _equalWindowFunc(const WindowFunc *a, const WindowFunc *b)
COMPARE_SCALAR_FIELD(wincollid);
COMPARE_SCALAR_FIELD(inputcollid);
COMPARE_NODE_FIELD(args);
+ COMPARE_NODE_FIELD(agg_filter);
COMPARE_SCALAR_FIELD(winref);
COMPARE_SCALAR_FIELD(winstar);
COMPARE_SCALAR_FIELD(winagg);
@@ -1995,6 +1997,7 @@ _equalFuncCall(const FuncCall *a, const FuncCall *b)
COMPARE_SCALAR_FIELD(agg_star);
COMPARE_SCALAR_FIELD(agg_distinct);
COMPARE_SCALAR_FIELD(func_variadic);
+ COMPARE_NODE_FIELD(agg_filter);
COMPARE_NODE_FIELD(over);
COMPARE_LOCATION_FIELD(location);
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 42d6621..d5b4049 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1570,6 +1570,8 @@ expression_tree_walker(Node *node,
if (expression_tree_walker((Node *) expr->aggdistinct,
walker, context))
return true;
+ if (walker((Node *) expr->agg_filter, context))
+ return true;
}
break;
case T_WindowFunc:
@@ -1580,6 +1582,8 @@ expression_tree_walker(Node *node,
if (expression_tree_walker((Node *) expr->args,
walker, context))
return true;
+ if (walker((Node *) expr->agg_filter, context))
+ return true;
}
break;
case T_ArrayRef:
@@ -2079,6 +2083,7 @@ expression_tree_mutator(Node *node,
MUTATE(newnode->args, aggref->args, List *);
MUTATE(newnode->aggorder, aggref->aggorder, List *);
MUTATE(newnode->aggdistinct, aggref->aggdistinct, List *);
+ MUTATE(newnode->agg_filter, aggref->agg_filter, Expr *);
return (Node *) newnode;
}
break;
@@ -2089,6 +2094,7 @@ expression_tree_mutator(Node *node,
FLATCOPY(newnode, wfunc, WindowFunc);
MUTATE(newnode->args, wfunc->args, List *);
+ MUTATE(newnode->agg_filter, wfunc->agg_filter, Expr *);
return (Node *) newnode;
}
break;
@@ -2951,6 +2957,8 @@ raw_expression_tree_walker(Node *node,
return true;
if (walker(fcall->agg_order, context))
return true;
+ if (walker(fcall->agg_filter, context))
+ return true;
if (walker(fcall->over, context))
return true;
/* function name is deemed uninteresting */
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b2183f4..cc09a9a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -958,6 +958,7 @@ _outAggref(StringInfo str, const Aggref *node)
WRITE_NODE_FIELD(args);
WRITE_NODE_FIELD(aggorder);
WRITE_NODE_FIELD(aggdistinct);
+ WRITE_NODE_FIELD(agg_filter);
WRITE_BOOL_FIELD(aggstar);
WRITE_UINT_FIELD(agglevelsup);
WRITE_LOCATION_FIELD(location);
@@ -973,6 +974,7 @@ _outWindowFunc(StringInfo str, const WindowFunc *node)
WRITE_OID_FIELD(wincollid);
WRITE_OID_FIELD(inputcollid);
WRITE_NODE_FIELD(args);
+ WRITE_NODE_FIELD(agg_filter);
WRITE_UINT_FIELD(winref);
WRITE_BOOL_FIELD(winstar);
WRITE_BOOL_FIELD(winagg);
@@ -2083,6 +2085,7 @@ _outFuncCall(StringInfo str, const FuncCall *node)
WRITE_BOOL_FIELD(agg_star);
WRITE_BOOL_FIELD(agg_distinct);
WRITE_BOOL_FIELD(func_variadic);
+ WRITE_NODE_FIELD(agg_filter);
WRITE_NODE_FIELD(over);
WRITE_LOCATION_FIELD(location);
}
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3a16e9d..c9824b2 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -479,6 +479,7 @@ _readAggref(void)
READ_NODE_FIELD(args);
READ_NODE_FIELD(aggorder);
READ_NODE_FIELD(aggdistinct);
+ READ_NODE_FIELD(agg_filter);
READ_BOOL_FIELD(aggstar);
READ_UINT_FIELD(agglevelsup);
READ_LOCATION_FIELD(location);
@@ -499,6 +500,7 @@ _readWindowFunc(void)
READ_OID_FIELD(wincollid);
READ_OID_FIELD(inputcollid);
READ_NODE_FIELD(args);
+ READ_NODE_FIELD(agg_filter);
READ_UINT_FIELD(winref);
READ_BOOL_FIELD(winstar);
READ_BOOL_FIELD(winagg);
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
index 090ae0b..627eb4d 100644
--- a/src/backend/optimizer/plan/planagg.c
+++ b/src/backend/optimizer/plan/planagg.c
@@ -314,7 +314,7 @@ find_minmax_aggs_walker(Node *node, List **context)
ListCell *l;
Assert(aggref->agglevelsup == 0);
- if (list_length(aggref->args) != 1 || aggref->aggorder != NIL)
+ if (list_length(aggref->args) != 1 || aggref->aggorder != NIL || aggref->agg_filter != NULL)
return true; /* it couldn't be MIN/MAX */
/* note: we do not care if DISTINCT is mentioned ... */
curTarget = (TargetEntry *) linitial(aggref->args);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0fc5b13..d0961a8 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -492,6 +492,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
opt_frame_clause frame_extent frame_bound
%type <str> opt_existing_window_name
%type <boolean> opt_if_not_exists
+%type <node> filter_clause
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -538,7 +539,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
EXTENSION EXTERNAL EXTRACT
- FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
+ FALSE_P FAMILY FETCH FILTER FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
FREEZE FROM FULL FUNCTION FUNCTIONS
GLOBAL GRANT GRANTED GREATEST GROUP_P
@@ -11104,6 +11105,7 @@ func_application: func_name '(' ')'
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @1;
$$ = (Node *)n;
@@ -11117,6 +11119,7 @@ func_application: func_name '(' ')'
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @1;
$$ = (Node *)n;
@@ -11130,6 +11133,7 @@ func_application: func_name '(' ')'
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = TRUE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @1;
$$ = (Node *)n;
@@ -11143,6 +11147,7 @@ func_application: func_name '(' ')'
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = TRUE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @1;
$$ = (Node *)n;
@@ -11156,6 +11161,7 @@ func_application: func_name '(' ')'
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @1;
$$ = (Node *)n;
@@ -11173,6 +11179,7 @@ func_application: func_name '(' ')'
* for that in FuncCall at the moment.
*/
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @1;
$$ = (Node *)n;
@@ -11186,6 +11193,7 @@ func_application: func_name '(' ')'
n->agg_star = FALSE;
n->agg_distinct = TRUE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @1;
$$ = (Node *)n;
@@ -11209,6 +11217,7 @@ func_application: func_name '(' ')'
n->agg_star = TRUE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
n->location = @1;
$$ = (Node *)n;
@@ -11225,10 +11234,11 @@ func_application: func_name '(' ')'
* (Note that many of the special SQL functions wouldn't actually make any
* sense as functional index entries, but we ignore that consideration here.)
*/
-func_expr: func_application over_clause
+func_expr: func_application filter_clause over_clause
{
FuncCall *n = (FuncCall*)$1;
- n->over = $2;
+ n->agg_filter = $2;
+ n->over = $3;
$$ = (Node*)n;
}
| func_expr_common_subexpr
@@ -11797,6 +11807,11 @@ window_definition:
}
;
+filter_clause:
+ FILTER '(' WHERE a_expr ')' { $$ = $4; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
over_clause: OVER window_specification
{ $$ = $2; }
| OVER ColId
@@ -12771,6 +12786,7 @@ unreserved_keyword:
| EXTENSION
| EXTERNAL
| FAMILY
+ | FILTER
| FIRST_P
| FOLLOWING
| FORCE
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 7380618..e506797 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -44,7 +44,7 @@ typedef struct
int sublevels_up;
} check_ungrouped_columns_context;
-static int check_agg_arguments(ParseState *pstate, List *args);
+static int check_agg_arguments(ParseState *pstate, List *args, Expr *filter);
static bool check_agg_arguments_walker(Node *node,
check_agg_arguments_context *context);
static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
@@ -160,7 +160,7 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
* Check the arguments to compute the aggregate's level and detect
* improper nesting.
*/
- min_varlevel = check_agg_arguments(pstate, agg->args);
+ min_varlevel = check_agg_arguments(pstate, agg->args, agg->agg_filter);
agg->agglevelsup = min_varlevel;
/* Mark the correct pstate level as having aggregates */
@@ -207,6 +207,9 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
case EXPR_KIND_HAVING:
/* okay */
break;
+ case EXPR_KIND_FILTER:
+ errkind = true;
+ break;
case EXPR_KIND_WINDOW_PARTITION:
/* okay */
break;
@@ -309,7 +312,7 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
* which we can't know until we finish scanning the arguments.
*/
static int
-check_agg_arguments(ParseState *pstate, List *args)
+check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
{
int agglevel;
check_agg_arguments_context context;
@@ -323,6 +326,10 @@ check_agg_arguments(ParseState *pstate, List *args)
check_agg_arguments_walker,
(void *) &context);
+ (void) expression_tree_walker((Node *) filter,
+ check_agg_arguments_walker,
+ (void *) &context);
+
/*
* If we found no vars nor aggs at all, it's a level-zero aggregate;
* otherwise, its level is the minimum of vars or aggs.
@@ -481,6 +488,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_HAVING:
errkind = true;
break;
+ case EXPR_KIND_FILTER:
+ errkind = true;
+ break;
case EXPR_KIND_WINDOW_PARTITION:
case EXPR_KIND_WINDOW_ORDER:
case EXPR_KIND_WINDOW_FRAME_RANGE:
diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c
index 80f6ac7..b84f2bd 100644
--- a/src/backend/parser/parse_collate.c
+++ b/src/backend/parser/parse_collate.c
@@ -575,6 +575,10 @@ assign_collations_walker(Node *node, assign_collations_context *context)
* the case above for T_TargetEntry will apply
* appropriate checks to agg ORDER BY items.
*
+ * Likewise, we assign collations for the (bool)
+ * expression in agg_filter, independently of
+ * any other args.
+ *
* We need not recurse into the aggorder or
* aggdistinct lists, because those contain only
* SortGroupClause nodes which we need not
@@ -595,6 +599,22 @@ assign_collations_walker(Node *node, assign_collations_context *context)
(void) assign_collations_walker((Node *) tle,
&loccontext);
}
+
+ assign_expr_collations(context->pstate, (Node *) aggref->agg_filter);
+ }
+ break;
+ case T_WindowFunc:
+ {
+ /*
+ * WindowFunc requires special processing only for
+ * its agg_filter clause, as for aggregates.
+ */
+ WindowFunc *wfunc = (WindowFunc *) node;
+
+ (void) assign_collations_walker((Node *) wfunc->args,
+ &loccontext);
+
+ assign_expr_collations(context->pstate, (Node *) wfunc->agg_filter);
}
break;
case T_CaseExpr:
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 06f6512..2272965 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -22,6 +22,7 @@
#include "nodes/nodeFuncs.h"
#include "optimizer/var.h"
#include "parser/analyze.h"
+#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
@@ -463,7 +464,7 @@ transformIndirection(ParseState *pstate, Node *basenode, List *indirection)
list_make1(n),
list_make1(result),
NIL, false, false, false,
- NULL, true, location);
+ NULL, NULL, true, location);
if (newresult == NULL)
unknown_attribute(pstate, result, strVal(n), location);
result = newresult;
@@ -631,7 +632,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
- NULL, true, cref->location);
+ NULL, NULL, true, cref->location);
}
break;
}
@@ -676,7 +677,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
- NULL, true, cref->location);
+ NULL, NULL, true, cref->location);
}
break;
}
@@ -734,7 +735,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
- NULL, true, cref->location);
+ NULL, NULL, true, cref->location);
}
break;
}
@@ -1241,6 +1242,7 @@ transformFuncCall(ParseState *pstate, FuncCall *fn)
{
List *targs;
ListCell *args;
+ Expr *tagg_filter;
/* Transform the list of arguments ... */
targs = NIL;
@@ -1250,6 +1252,12 @@ transformFuncCall(ParseState *pstate, FuncCall *fn)
(Node *) lfirst(args)));
}
+ /* Transform the aggregate filter using transformWhereClause, to
+ * which FILTER is virually identical... */
+ tagg_filter = NULL;
+ if (fn->agg_filter != NULL)
+ tagg_filter = (Expr *)transformWhereClause(pstate, (Node *)fn->agg_filter, EXPR_KIND_FILTER, "FILTER");
+
/* ... and hand off to ParseFuncOrColumn */
return ParseFuncOrColumn(pstate,
fn->funcname,
@@ -1258,6 +1266,7 @@ transformFuncCall(ParseState *pstate, FuncCall *fn)
fn->agg_star,
fn->agg_distinct,
fn->func_variadic,
+ tagg_filter,
fn->over,
false,
fn->location);
@@ -1430,6 +1439,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_FROM_FUNCTION:
case EXPR_KIND_WHERE:
case EXPR_KIND_HAVING:
+ case EXPR_KIND_FILTER:
case EXPR_KIND_WINDOW_PARTITION:
case EXPR_KIND_WINDOW_ORDER:
case EXPR_KIND_WINDOW_FRAME_RANGE:
@@ -2579,6 +2589,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "WHERE";
case EXPR_KIND_HAVING:
return "HAVING";
+ case EXPR_KIND_FILTER:
+ return "FILTER";
case EXPR_KIND_WINDOW_PARTITION:
return "window PARTITION BY";
case EXPR_KIND_WINDOW_ORDER:
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index ae7d195..75c740e 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -63,7 +63,7 @@ Node *
ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
List *agg_order, bool agg_star, bool agg_distinct,
bool func_variadic,
- WindowDef *over, bool is_column, int location)
+ Expr *agg_filter, WindowDef *over, bool is_column, int location)
{
Oid rettype;
Oid funcid;
@@ -175,7 +175,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
* wasn't any aggregate or variadic decoration, nor an argument name.
*/
if (nargs == 1 && agg_order == NIL && !agg_star && !agg_distinct &&
- over == NULL && !func_variadic && argnames == NIL &&
+ agg_filter == NULL && over == NULL && !func_variadic && argnames == NIL &&
list_length(funcname) == 1)
{
Oid argtype = actual_arg_types[0];
@@ -251,6 +251,12 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
errmsg("ORDER BY specified, but %s is not an aggregate function",
NameListToString(funcname)),
parser_errposition(pstate, location)));
+ if (agg_filter)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("FILTER specified, but %s is not an aggregate function",
+ NameListToString(funcname)),
+ parser_errposition(pstate, location)));
if (over)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -402,6 +408,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
/* aggcollid and inputcollid will be set by parse_collate.c */
/* args, aggorder, aggdistinct will be set by transformAggregateCall */
aggref->aggstar = agg_star;
+ /* filter */
+ aggref->agg_filter = agg_filter;
/* agglevelsup will be set by transformAggregateCall */
aggref->location = location;
@@ -460,6 +468,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
/* winref will be set by transformWindowFuncCall */
wfunc->winstar = agg_star;
wfunc->winagg = (fdresult == FUNCDETAIL_AGGREGATE);
+ wfunc->agg_filter = agg_filter;
wfunc->location = location;
/*
@@ -483,6 +492,16 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
parser_errposition(pstate, location)));
/*
+ * Reject window functions which are not aggregates in the
+ * case of FILTER.
+ */
+ if (!wfunc->winagg && agg_filter)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("FILTER is not implemented in non-aggregate window functions"),
+ parser_errposition(pstate, location)));
+
+ /*
* ordered aggs not allowed in windows yet
*/
if (agg_order != NIL)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index a1ed781..bd3050a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -7424,7 +7424,15 @@ get_agg_expr(Aggref *aggref, deparse_context *context)
appendStringInfoString(buf, " ORDER BY ");
get_rule_orderby(aggref->aggorder, aggref->args, false, context);
}
+
+ if (aggref->agg_filter != NULL)
+ {
+ appendStringInfoString(buf, ") FILTER (WHERE ");
+ get_rule_expr((Node *)aggref->agg_filter, context, false);
+ }
+
appendStringInfoChar(buf, ')');
+
}
/*
@@ -7461,6 +7469,13 @@ get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context)
appendStringInfoChar(buf, '*');
else
get_rule_expr((Node *) wfunc->args, context, true);
+
+ if (wfunc->agg_filter != NULL)
+ {
+ appendStringInfoString(buf, ") FILTER (WHERE ");
+ get_rule_expr((Node *)wfunc->agg_filter, context, false);
+ }
+
appendStringInfoString(buf, ") OVER ");
foreach(l, context->windowClause)
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 4f77016..a3e6b05 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -584,6 +584,7 @@ typedef struct AggrefExprState
{
ExprState xprstate;
List *args; /* states of argument expressions */
+ ExprState *agg_filter; /* FILTER expression */
int aggno; /* ID number for agg within its plan node */
} AggrefExprState;
@@ -595,6 +596,7 @@ typedef struct WindowFuncExprState
{
ExprState xprstate;
List *args; /* states of argument expressions */
+ ExprState *agg_filter; /* FILTER expression */
int wfuncno; /* ID number for wfunc within its plan node */
} WindowFuncExprState;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 9453e1d..4519602 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -295,6 +295,7 @@ typedef struct FuncCall
bool agg_star; /* argument was really '*' */
bool agg_distinct; /* arguments were labeled DISTINCT */
bool func_variadic; /* last argument was labeled VARIADIC */
+ Node *agg_filter; /* FILTER clause, if any */
struct WindowDef *over; /* OVER clause, if any */
int location; /* token location, or -1 if unknown */
} FuncCall;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 75b716a..9f7111e 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -247,6 +247,7 @@ typedef struct Aggref
List *args; /* arguments and sort expressions */
List *aggorder; /* ORDER BY (list of SortGroupClause) */
List *aggdistinct; /* DISTINCT (list of SortGroupClause) */
+ Expr *agg_filter; /* FILTER expression */
bool aggstar; /* TRUE if argument list was really '*' */
Index agglevelsup; /* > 0 if agg belongs to outer query */
int location; /* token location, or -1 if unknown */
@@ -263,6 +264,7 @@ typedef struct WindowFunc
Oid wincollid; /* OID of collation of result */
Oid inputcollid; /* OID of collation that function should use */
List *args; /* arguments to the window function */
+ Expr *agg_filter; /* FILTER expression */
Index winref; /* index of associated WindowClause */
bool winstar; /* TRUE if argument list was really '*' */
bool winagg; /* is function a simple aggregate? */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index b3d72a9..287f78e 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -155,6 +155,7 @@ PG_KEYWORD("extract", EXTRACT, COL_NAME_KEYWORD)
PG_KEYWORD("false", FALSE_P, RESERVED_KEYWORD)
PG_KEYWORD("family", FAMILY, UNRESERVED_KEYWORD)
PG_KEYWORD("fetch", FETCH, RESERVED_KEYWORD)
+PG_KEYWORD("filter", FILTER, UNRESERVED_KEYWORD)
PG_KEYWORD("first", FIRST_P, UNRESERVED_KEYWORD)
PG_KEYWORD("float", FLOAT_P, COL_NAME_KEYWORD)
PG_KEYWORD("following", FOLLOWING, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_func.h b/src/include/parser/parse_func.h
index 6e09dc4..13efb57 100644
--- a/src/include/parser/parse_func.h
+++ b/src/include/parser/parse_func.h
@@ -46,7 +46,7 @@ extern Node *ParseFuncOrColumn(ParseState *pstate,
List *funcname, List *fargs,
List *agg_order, bool agg_star, bool agg_distinct,
bool func_variadic,
- WindowDef *over, bool is_column, int location);
+ Expr *agg_filter, WindowDef *over, bool is_column, int location);
extern FuncDetailCode func_get_detail(List *funcname,
List *fargs, List *fargnames,
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 49ca764..bea3b07 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -39,6 +39,7 @@ typedef enum ParseExprKind
EXPR_KIND_FROM_FUNCTION, /* function in FROM clause */
EXPR_KIND_WHERE, /* WHERE */
EXPR_KIND_HAVING, /* HAVING */
+ EXPR_KIND_FILTER, /* FILTER */
EXPR_KIND_WINDOW_PARTITION, /* window definition PARTITION BY */
EXPR_KIND_WINDOW_ORDER, /* window definition ORDER BY */
EXPR_KIND_WINDOW_FRAME_RANGE, /* window frame clause with RANGE */
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index d379c0d..9359811 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1154,3 +1154,69 @@ select string_agg(v, decode('ee', 'hex')) from bytea_test_table;
(1 row)
drop table bytea_test_table;
+-- FILTER tests
+select min(unique1) filter (where unique1 > 100) from tenk1;
+ min
+-----
+ 101
+(1 row)
+
+select ten, sum(distinct four) filter (where four::text ~ '123') from onek a
+group by ten;
+ ten | sum
+-----+-----
+ 0 |
+ 1 |
+ 2 |
+ 3 |
+ 4 |
+ 5 |
+ 6 |
+ 7 |
+ 8 |
+ 9 |
+(10 rows)
+
+select ten, sum(distinct four) filter (where four > 10) from onek a
+group by ten
+having exists (select 1 from onek b where sum(distinct a.four) = b.four);
+ ten | sum
+-----+-----
+ 0 |
+ 2 |
+ 4 |
+ 6 |
+ 8 |
+(5 rows)
+
+select max(foo COLLATE "C") filter (where (bar collate "POSIX") > '0') from (values ('a', 'b')) AS v(foo,bar);
+ max
+-----
+ a
+(1 row)
+
+-- outer-level aggregates
+select
+ (select max((select i.unique2 from tenk1 i where i.unique1 = o.unique1)) filter (where o.unique1 < 10))
+from tenk1 o;
+ max
+------
+ 9998
+(1 row)
+
+-- non-standard-conforming FILTER clause containing subquery
+select sum(unique1) FILTER (WHERE unique1 IN (SELECT unique1 FROM onek where unique1 < 100)) FROM tenk1;
+ sum
+------
+ 4950
+(1 row)
+
+-- exercise lots of aggregate parts with FILTER
+select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1)
+ from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+ generate_series(1,2) i;
+ aggfns
+---------------------------
+ {"(2,2,bar)","(3,1,baz)"}
+(1 row)
+
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index ecc1c2c..7b31d13 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -1020,5 +1020,18 @@ SELECT ntile(0) OVER (ORDER BY ten), ten, four FROM tenk1;
ERROR: argument of ntile must be greater than zero
SELECT nth_value(four, 0) OVER (ORDER BY ten), ten, four FROM tenk1;
ERROR: argument of nth_value must be greater than zero
+-- filter
+SELECT sum(salary), row_number() OVER (ORDER BY depname), sum(
+ sum(salary) FILTER (WHERE enroll_date > '2007-01-01')
+) FILTER (WHERE depname <> 'sales') OVER (ORDER BY depname DESC) AS "filtered_sum",
+ depname
+FROM empsalary GROUP BY depname;
+ sum | row_number | filtered_sum | depname
+-------+------------+--------------+-----------
+ 14600 | 3 | | sales
+ 7400 | 2 | 3500 | personnel
+ 25100 | 1 | 22600 | develop
+(3 rows)
+
-- cleanup
DROP TABLE empsalary;
diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql
index 38d4757..da0bd65 100644
--- a/src/test/regress/sql/aggregates.sql
+++ b/src/test/regress/sql/aggregates.sql
@@ -442,3 +442,31 @@ select string_agg(v, NULL) from bytea_test_table;
select string_agg(v, decode('ee', 'hex')) from bytea_test_table;
drop table bytea_test_table;
+
+-- FILTER tests
+
+select min(unique1) filter (where unique1 > 100) from tenk1;
+
+select ten, sum(distinct four) filter (where four::text ~ '123') from onek a
+group by ten;
+
+select ten, sum(distinct four) filter (where four > 10) from onek a
+group by ten
+having exists (select 1 from onek b where sum(distinct a.four) = b.four);
+
+select max(foo COLLATE "C") filter (where (bar collate "POSIX") > '0') from (values ('a', 'b')) AS v(foo,bar);
+
+-- outer-level aggregates
+select
+ (select max((select i.unique2 from tenk1 i where i.unique1 = o.unique1)) filter (where o.unique1 < 10))
+from tenk1 o;
+
+-- non-standard-conforming FILTER clause containing subquery
+
+select sum(unique1) FILTER (WHERE unique1 IN (SELECT unique1 FROM onek where unique1 < 100)) FROM tenk1;
+
+-- exercise lots of aggregate parts with FILTER
+
+select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1)
+ from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+ generate_series(1,2) i;
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 769be0f..6ee3696 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -264,5 +264,13 @@ SELECT ntile(0) OVER (ORDER BY ten), ten, four FROM tenk1;
SELECT nth_value(four, 0) OVER (ORDER BY ten), ten, four FROM tenk1;
+-- filter
+
+SELECT sum(salary), row_number() OVER (ORDER BY depname), sum(
+ sum(salary) FILTER (WHERE enroll_date > '2007-01-01')
+) FILTER (WHERE depname <> 'sales') OVER (ORDER BY depname DESC) AS "filtered_sum",
+ depname
+FROM empsalary GROUP BY depname;
+
-- cleanup
DROP TABLE empsalary;
On Fri, Jun 28, 2013 at 01:28:35PM -0400, Peter Eisentraut wrote:
On 6/28/13 11:30 AM, Robert Haas wrote:
On Fri, Jun 28, 2013 at 10:31 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
David Fetter <david@fetter.org> writes:
Please find attached the latest patch.
I remain of the opinion that this is simply a bad idea. It is unlike
our habits for constructing other types of nodes, and makes it harder
not easier to find all the places that need to be updated when adding
another field to FuncCall.I think it's a nice code cleanup. I don't understand your objection.
Yeah, I was reading the patch thinking, yes, finally someone cleans that up.
Please find enclosed a patch reflecting the changes that de-reserved
OVER as a keyword.
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
Attachments:
makeFuncArgs_004.difftext/plain; charset=us-asciiDownload
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index c487db9..245aef2 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -508,3 +508,28 @@ makeDefElemExtended(char *nameSpace, char *name, Node *arg,
return res;
}
+
+/*
+ * makeFuncCall -
+ *
+ * Initialize a FuncCall struct with the information every caller must
+ * supply. Any non-default parameters have to be handled by the
+ * caller.
+ *
+ */
+
+FuncCall *
+makeFuncCall(List *name, List *args, int location)
+{
+ FuncCall *n = makeNode(FuncCall);
+ n->funcname = name;
+ n->args = args;
+ n->location = location;
+ n->agg_order = NIL;
+ n->agg_star = FALSE;
+ n->agg_distinct = FALSE;
+ n->func_variadic = FALSE;
+ n->over = NULL;
+ return n;
+}
+
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0fc5b13..f67ef0c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10503,16 +10503,9 @@ a_expr: c_expr { $$ = $1; }
}
| a_expr AT TIME ZONE a_expr %prec AT
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("timezone");
- n->args = list_make2($5, $1);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @2;
- $$ = (Node *) n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("timezone"),
+ list_make2($5, $1),
+ @2);
}
/*
* These operators must be called out explicitly in order to make use
@@ -10564,113 +10557,65 @@ a_expr: c_expr { $$ = $1; }
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~", $1, $3, @2); }
| a_expr LIKE a_expr ESCAPE a_expr
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("like_escape");
- n->args = list_make2($3, $5);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @2;
+ FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
+ list_make2($3, $5),
+ @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~", $1, (Node *) n, @2);
}
| a_expr NOT LIKE a_expr
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~", $1, $4, @2); }
| a_expr NOT LIKE a_expr ESCAPE a_expr
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("like_escape");
- n->args = list_make2($4, $6);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @2;
+ FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
+ list_make2($4, $6),
+ @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~", $1, (Node *) n, @2);
}
| a_expr ILIKE a_expr
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~*", $1, $3, @2); }
| a_expr ILIKE a_expr ESCAPE a_expr
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("like_escape");
- n->args = list_make2($3, $5);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @2;
+ FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
+ list_make2($3, $5),
+ @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~~*", $1, (Node *) n, @2);
}
| a_expr NOT ILIKE a_expr
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~*", $1, $4, @2); }
| a_expr NOT ILIKE a_expr ESCAPE a_expr
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("like_escape");
- n->args = list_make2($4, $6);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @2;
+ FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
+ list_make2($4, $6),
+ @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~~*", $1, (Node *) n, @2);
}
| a_expr SIMILAR TO a_expr %prec SIMILAR
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("similar_escape");
- n->args = list_make2($4, makeNullAConst(-1));
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @2;
+ FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
+ list_make2($4, makeNullAConst(-1)),
+ @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~", $1, (Node *) n, @2);
}
| a_expr SIMILAR TO a_expr ESCAPE a_expr
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("similar_escape");
- n->args = list_make2($4, $6);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @2;
+ FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
+ list_make2($4, $6),
+ @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "~", $1, (Node *) n, @2);
}
| a_expr NOT SIMILAR TO a_expr %prec SIMILAR
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("similar_escape");
- n->args = list_make2($5, makeNullAConst(-1));
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @2;
+ FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
+ list_make2($5, makeNullAConst(-1)),
+ @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~", $1, (Node *) n, @2);
}
| a_expr NOT SIMILAR TO a_expr ESCAPE a_expr
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("similar_escape");
- n->args = list_make2($5, $7);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @2;
+ FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
+ list_make2($5, $7),
+ @2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "!~", $1, (Node *) n, @2);
}
@@ -11097,97 +11042,45 @@ c_expr: columnref { $$ = $1; }
func_application: func_name '(' ')'
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = $1;
- n->args = NIL;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall($1, NIL, @1);
}
| func_name '(' func_arg_list ')'
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = $1;
- n->args = $3;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall($1, $3, @1);
}
| func_name '(' VARIADIC func_arg_expr ')'
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = $1;
- n->args = list_make1($4);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
+ FuncCall *n = makeFuncCall($1, list_make1($4), @1);
n->func_variadic = TRUE;
- n->over = NULL;
- n->location = @1;
$$ = (Node *)n;
}
| func_name '(' func_arg_list ',' VARIADIC func_arg_expr ')'
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = $1;
- n->args = lappend($3, $6);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
+ FuncCall *n = makeFuncCall($1, lappend($3, $6), @1);
n->func_variadic = TRUE;
- n->over = NULL;
- n->location = @1;
$$ = (Node *)n;
}
| func_name '(' func_arg_list sort_clause ')'
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = $1;
- n->args = $3;
+ FuncCall *n = makeFuncCall($1, $3, @1);
n->agg_order = $4;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
$$ = (Node *)n;
}
| func_name '(' ALL func_arg_list opt_sort_clause ')'
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = $1;
- n->args = $4;
+ FuncCall *n = makeFuncCall($1, $4, @1);
n->agg_order = $5;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
/* Ideally we'd mark the FuncCall node to indicate
* "must be an aggregate", but there's no provision
* for that in FuncCall at the moment.
*/
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
$$ = (Node *)n;
}
| func_name '(' DISTINCT func_arg_list opt_sort_clause ')'
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = $1;
- n->args = $4;
+ FuncCall *n = makeFuncCall($1, $4, @1);
n->agg_order = $5;
- n->agg_star = FALSE;
n->agg_distinct = TRUE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
$$ = (Node *)n;
}
| func_name '(' '*' ')'
@@ -11202,15 +11095,8 @@ func_application: func_name '(' ')'
* so that later processing can detect what the argument
* really was.
*/
- FuncCall *n = makeNode(FuncCall);
- n->funcname = $1;
- n->args = NIL;
- n->agg_order = NIL;
+ FuncCall *n = makeFuncCall($1, NIL, @1);
n->agg_star = TRUE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
$$ = (Node *)n;
}
;
@@ -11252,16 +11138,9 @@ func_expr_windowless:
func_expr_common_subexpr:
COLLATION FOR '(' a_expr ')'
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("pg_collation_for");
- n->args = list_make1($4);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("pg_collation_for"),
+ list_make1($4),
+ @1);
}
| CURRENT_DATE
{
@@ -11313,16 +11192,7 @@ func_expr_common_subexpr:
* Translate as "now()", since we have a function that
* does exactly what is needed.
*/
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("now");
- n->args = NIL;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("now"), NIL, @1);
}
| CURRENT_TIMESTAMP '(' Iconst ')'
{
@@ -11385,96 +11255,33 @@ func_expr_common_subexpr:
}
| CURRENT_ROLE
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("current_user");
- n->args = NIL;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("current_user"), NIL, @1);
}
| CURRENT_USER
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("current_user");
- n->args = NIL;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("current_user"), NIL, @1);
}
| SESSION_USER
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("session_user");
- n->args = NIL;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("session_user"), NIL, @1);
}
| USER
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("current_user");
- n->args = NIL;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("current_user"), NIL, @1);
}
| CURRENT_CATALOG
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("current_database");
- n->args = NIL;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("current_database"), NIL, @1);
}
| CURRENT_SCHEMA
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("current_schema");
- n->args = NIL;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("current_schema"), NIL, @1);
}
| CAST '(' a_expr AS Typename ')'
{ $$ = makeTypeCast($3, $5, @1); }
| EXTRACT '(' extract_list ')'
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("date_part");
- n->args = $3;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("date_part"), $3, @1);
}
| OVERLAY '(' overlay_list ')'
{
@@ -11483,46 +11290,19 @@ func_expr_common_subexpr:
* overlay(A PLACING B FROM C) is converted to
* overlay(A, B, C)
*/
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("overlay");
- n->args = $3;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("overlay"), $3, @1);
}
| POSITION '(' position_list ')'
{
/* position(A in B) is converted to position(B, A) */
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("position");
- n->args = $3;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("position"), $3, @1);
}
| SUBSTRING '(' substr_list ')'
{
/* substring(A from B for C) is converted to
* substring(A, B, C) - thomas 2000-11-28
*/
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("substring");
- n->args = $3;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("substring"), $3, @1);
}
| TREAT '(' a_expr AS Typename ')'
{
@@ -11531,75 +11311,32 @@ func_expr_common_subexpr:
* In SQL99, this is intended for use with structured UDTs,
* but let's make this a generally useful form allowing stronger
* coercions than are handled by implicit casting.
- */
- FuncCall *n = makeNode(FuncCall);
- /* Convert SystemTypeName() to SystemFuncName() even though
+ *
+ * Convert SystemTypeName() to SystemFuncName() even though
* at the moment they result in the same thing.
*/
- n->funcname = SystemFuncName(((Value *)llast($5->names))->val.str);
- n->args = list_make1($3);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName(((Value *)llast($5->names))->val.str),
+ list_make1($3),
+ @1);
}
| TRIM '(' BOTH trim_list ')'
{
/* various trim expressions are defined in SQL
* - thomas 1997-07-19
*/
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("btrim");
- n->args = $4;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("btrim"), $4, @1);
}
| TRIM '(' LEADING trim_list ')'
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("ltrim");
- n->args = $4;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("ltrim"), $4, @1);
}
| TRIM '(' TRAILING trim_list ')'
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("rtrim");
- n->args = $4;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("rtrim"), $4, @1);
}
| TRIM '(' trim_list ')'
{
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("btrim");
- n->args = $3;
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("btrim"), $3, @1);
}
| NULLIF '(' a_expr ',' a_expr ')'
{
@@ -11652,16 +11389,7 @@ func_expr_common_subexpr:
{
/* xmlexists(A PASSING [BY REF] B [BY REF]) is
* converted to xmlexists(A, B)*/
- FuncCall *n = makeNode(FuncCall);
- n->funcname = SystemFuncName("xmlexists");
- n->args = list_make2($3, $4);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = @1;
- $$ = (Node *)n;
+ $$ = (Node *) makeFuncCall(SystemFuncName("xmlexists"), list_make2($3, $4), @1);
}
| XMLFOREST '(' xml_attribute_list ')'
{
@@ -13317,9 +13045,7 @@ makeBoolAConst(bool state, int location)
static FuncCall *
makeOverlaps(List *largs, List *rargs, int location, core_yyscan_t yyscanner)
{
- FuncCall *n = makeNode(FuncCall);
-
- n->funcname = SystemFuncName("overlaps");
+ FuncCall *n;
if (list_length(largs) == 1)
largs = lappend(largs, largs);
else if (list_length(largs) != 2)
@@ -13334,13 +13060,7 @@ makeOverlaps(List *largs, List *rargs, int location, core_yyscan_t yyscanner)
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("wrong number of parameters on right side of OVERLAPS expression"),
parser_errposition(location)));
- n->args = list_concat(largs, rargs);
- n->agg_order = NIL;
- n->agg_star = FALSE;
- n->agg_distinct = FALSE;
- n->func_variadic = FALSE;
- n->over = NULL;
- n->location = location;
+ n = makeFuncCall(SystemFuncName("overlaps"), list_concat(largs, rargs), location);
return n;
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index b426a45..40e3717 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -448,16 +448,9 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
castnode->typeName = SystemTypeName("regclass");
castnode->arg = (Node *) snamenode;
castnode->location = -1;
- funccallnode = makeNode(FuncCall);
- funccallnode->funcname = SystemFuncName("nextval");
- funccallnode->args = list_make1(castnode);
- funccallnode->agg_order = NIL;
- funccallnode->agg_star = false;
- funccallnode->agg_distinct = false;
- funccallnode->func_variadic = false;
- funccallnode->over = NULL;
- funccallnode->location = -1;
-
+ funccallnode = makeFuncCall(SystemFuncName("nextval"),
+ list_make1(castnode),
+ -1);
constraint = makeNode(Constraint);
constraint->contype = CONSTR_DEFAULT;
constraint->location = -1;
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index ee0c365..3b71b5b 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -75,6 +75,8 @@ extern TypeName *makeTypeNameFromOid(Oid typeOid, int32 typmod);
extern FuncExpr *makeFuncExpr(Oid funcid, Oid rettype, List *args,
Oid funccollid, Oid inputcollid, CoercionForm fformat);
+extern FuncCall *makeFuncCall(List *name, List *args, int location);
+
extern DefElem *makeDefElem(char *name, Node *arg);
extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
DefElemAction defaction);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 9453e1d..de22dff 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -285,6 +285,11 @@ typedef struct CollateClause
* construct *must* be an aggregate call. Otherwise, it might be either an
* aggregate or some other kind of function. However, if OVER is present
* it had better be an aggregate or window function.
+ *
+ * Normally, you'd initialize this via makeFuncCall() and then only
+ * change the parts of the struct its defaults don't match afterwards
+ * if needed.
+ *
*/
typedef struct FuncCall
{
On Mon, Jul 1, 2013 at 6:16 AM, David Fetter <david@fetter.org> wrote:
On Fri, Jun 28, 2013 at 01:28:35PM -0400, Peter Eisentraut wrote:
On 6/28/13 11:30 AM, Robert Haas wrote:
On Fri, Jun 28, 2013 at 10:31 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
David Fetter <david@fetter.org> writes:
Please find attached the latest patch.
I remain of the opinion that this is simply a bad idea. It is unlike
our habits for constructing other types of nodes, and makes it harder
not easier to find all the places that need to be updated when adding
another field to FuncCall.I think it's a nice code cleanup. I don't understand your objection.
Yeah, I was reading the patch thinking, yes, finally someone cleans that
up.
Please find enclosed a patch reflecting the changes that de-reserved
OVER as a keyword.
I have re-validated this new patch and it looks good to go in now.
I saw that it's already marked ready for committer.
Thanks David
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.icsRemember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
--
Jeevan B Chalke
Senior Software Engineer, R&D
EnterpriseDB Corporation
The Enterprise PostgreSQL Company
Phone: +91 20 30589500
Website: www.enterprisedb.com
EnterpriseDB Blog: http://blogs.enterprisedb.com/
Follow us on Twitter: http://www.twitter.com/enterprisedb
This e-mail message (and any attachment) is intended for the use of the
individual or entity to whom it is addressed. This message contains
information from EnterpriseDB Corporation that may be privileged,
confidential, or exempt from disclosure under applicable law. If you are
not the intended recipient or authorized to receive this for the intended
recipient, any use, dissemination, distribution, retention, archiving, or
copying of this communication is strictly prohibited. If you have received
this e-mail in error, please notify the sender immediately by reply e-mail
and delete this message.
On 1 July 2013 01:44, David Fetter <david@fetter.org> wrote:
On Fri, Jun 28, 2013 at 09:22:52PM +0100, Dean Rasheed wrote:
On 21 June 2013 06:16, David Fetter <david@fetter.org> wrote:
Please find attached a patch which allows subqueries in the FILTER
clause and adds regression testing for same.This needs re-basing/merging following Robert's recent commit to make
OVER unreserved.Please find attached. Thanks, Andrew Gierth! In this one, FILTER is
no longer a reserved word.
Looking at this patch again, it appears to be in pretty good shape.
- Applies cleanly to head.
- Compiles with no warnings.
- Includes regression test cases and doc updates.
- Compatible with the relevant part of T612, "Advanced OLAP operations".
- Includes pg_dump support.
- Code changes all look reasonable, and I can't find any corner cases
that have been missed.
- Appears to work as expected. I tested everything I could think of
and couldn't break it.
AFAICT all the bases have been covered. As mentioned upthread, I would
have preferred a few more regression test cases, and a couple of the
tests don't appear to return anything interesting, but I'll leave that
for the committer to decide whether they're sufficient for regression
tests.
I have a few suggestions to improve the docs:
1). In syntax.sgml: "The aggregate_name can also be suffixed with
FILTER as described below". It's not really a suffix to the aggregate
name, since it follows the function arguments and optional order by
clause. Perhaps it would be more consistent with the surrounding text
to say something like
<replaceable>expression</replaceable> is
any value expression that does not itself contain an aggregate
expression or a window function call, and
! <replaceable>order_by_clause</replaceable> and
! <replaceable>filter_clause</replaceable> are optional
! <literal>ORDER BY</> and <literal>FILTER</> clauses as described below.
2). In syntax.sgml: "... or when a FILTER clause is present, each row
matching same". In the context of that paragraph this suggests that
the filter clause only applies to the first form, since that paragraph
is a description of the 4 forms of the aggregate function. I don't
think it's worth mentioning FILTER in this paragraph at all --- it's
adequately described below that.
3). In syntax.sgml: "Adding a FILTER clause to an aggregate specifies
which values of the expression being aggregated to evaluate". How
about something a little more specific, along the lines of
If <literal>FILTER</> is specified, then only input rows for which
the <replaceable>filter_clause</replaceable> evaluates to true are
fed to the aggregate function; input rows for which the
<replaceable>filter_clause</replaceable> evaluates to false or the
null value are discarded. For example...
4). In select.sgml: "In the absence of a FILTER clause, aggregate
functions...". It doesn't seem right to refer to the FILTER clause at
the top level here because it's not part of the SELECT syntax being
described on this page. Also I think this should include a
cross-reference to the aggregate function syntax section, perhaps
something like:
Aggregate functions, if any are used, are computed across all rows
making up each group, producing a separate value for each group
(whereas without <literal>GROUP BY</literal>, an aggregate
produces a single value computed across all the selected rows).
+ The set of rows fed to the aggregate function can be further filtered
+ by attaching a <literal>FILTER</literal> clause to the aggregate
+ function call, see <xref ...> for more information.
When <literal>GROUP BY</literal> is present, it is not valid for
the <command>SELECT</command> list expressions to refer to
ungrouped columns except within aggregate functions or if the
ungrouped column is functionally dependent on the grouped columns,
since there would otherwise be more than one possible value to
return for an ungrouped column. A functional dependency exists if
the grouped columns (or a subset thereof) are the primary key of
the table containing the ungrouped column.
Regards,
Dean
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Jul 1, 2013 at 3:19 AM, Jeevan Chalke
<jeevan.chalke@enterprisedb.com> wrote:
I have re-validated this new patch and it looks good to go in now.
I saw that it's already marked ready for committer.
I don't normally like to commit things over another committer's
objections, but this has +1 votes from four other committers (Stephen,
Noah, Peter E, myself) so I think that's enough reason to move
forward. So committed.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Jul 01, 2013 at 05:30:38PM +0100, Dean Rasheed wrote:
On 1 July 2013 01:44, David Fetter <david@fetter.org> wrote:
On Fri, Jun 28, 2013 at 09:22:52PM +0100, Dean Rasheed wrote:
On 21 June 2013 06:16, David Fetter <david@fetter.org> wrote:
Please find attached a patch which allows subqueries in the FILTER
clause and adds regression testing for same.This needs re-basing/merging following Robert's recent commit to make
OVER unreserved.Please find attached. Thanks, Andrew Gierth! In this one, FILTER is
no longer a reserved word.Looking at this patch again, it appears to be in pretty good shape.
- Applies cleanly to head.
- Compiles with no warnings.
- Includes regression test cases and doc updates.
- Compatible with the relevant part of T612, "Advanced OLAP operations".
- Includes pg_dump support.
- Code changes all look reasonable, and I can't find any corner cases
that have been missed.
- Appears to work as expected. I tested everything I could think of
and couldn't break it.AFAICT all the bases have been covered. As mentioned upthread, I would
have preferred a few more regression test cases, and a couple of the
tests don't appear to return anything interesting, but I'll leave that
for the committer to decide whether they're sufficient for regression
tests.I have a few suggestions to improve the docs:
1). In syntax.sgml: "The aggregate_name can also be suffixed with
FILTER as described below". It's not really a suffix to the aggregate
name, since it follows the function arguments and optional order by
clause. Perhaps it would be more consistent with the surrounding text
to say something like<replaceable>expression</replaceable> is
any value expression that does not itself contain an aggregate
expression or a window function call, and
! <replaceable>order_by_clause</replaceable> and
! <replaceable>filter_clause</replaceable> are optional
! <literal>ORDER BY</> and <literal>FILTER</> clauses as described below.2). In syntax.sgml: "... or when a FILTER clause is present, each row
matching same". In the context of that paragraph this suggests that
the filter clause only applies to the first form, since that paragraph
is a description of the 4 forms of the aggregate function. I don't
think it's worth mentioning FILTER in this paragraph at all --- it's
adequately described below that.3). In syntax.sgml: "Adding a FILTER clause to an aggregate specifies
which values of the expression being aggregated to evaluate". How
about something a little more specific, along the lines ofIf <literal>FILTER</> is specified, then only input rows for which
the <replaceable>filter_clause</replaceable> evaluates to true are
fed to the aggregate function; input rows for which the
<replaceable>filter_clause</replaceable> evaluates to false or the
null value are discarded. For example...4). In select.sgml: "In the absence of a FILTER clause, aggregate
functions...". It doesn't seem right to refer to the FILTER clause at
the top level here because it's not part of the SELECT syntax being
described on this page. Also I think this should include a
cross-reference to the aggregate function syntax section, perhaps
something like:Aggregate functions, if any are used, are computed across all rows making up each group, producing a separate value for each group (whereas without <literal>GROUP BY</literal>, an aggregate produces a single value computed across all the selected rows). + The set of rows fed to the aggregate function can be further filtered + by attaching a <literal>FILTER</literal> clause to the aggregate + function call, see <xref ...> for more information. When <literal>GROUP BY</literal> is present, it is not valid for the <command>SELECT</command> list expressions to refer to ungrouped columns except within aggregate functions or if the ungrouped column is functionally dependent on the grouped columns, since there would otherwise be more than one possible value to return for an ungrouped column. A functional dependency exists if the grouped columns (or a subset thereof) are the primary key of the table containing the ungrouped column.
Please find attached changes based on the above.
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
Attachments:
filter_008.difftext/plain; charset=us-asciiDownload
diff --git a/doc/src/sgml/keywords.sgml b/doc/src/sgml/keywords.sgml
index 5e3b33a..ecfde99 100644
--- a/doc/src/sgml/keywords.sgml
+++ b/doc/src/sgml/keywords.sgml
@@ -1786,7 +1786,7 @@
</row>
<row>
<entry><token>FILTER</token></entry>
- <entry></entry>
+ <entry>non-reserved</entry>
<entry>reserved</entry>
<entry>reserved</entry>
<entry></entry>
@@ -3200,7 +3200,7 @@
</row>
<row>
<entry><token>OVER</token></entry>
- <entry>reserved (can be function or type)</entry>
+ <entry>non-reserved</entry>
<entry>reserved</entry>
<entry>reserved</entry>
<entry></entry>
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 68309ba..709d5ae 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -598,6 +598,11 @@ GROUP BY <replaceable class="parameter">expression</replaceable> [, ...]
making up each group, producing a separate value for each group
(whereas without <literal>GROUP BY</literal>, an aggregate
produces a single value computed across all the selected rows).
+ The set of rows fed to the aggregate function can be further filtered
+ by attaching a <literal>FILTER</literal> clause to the aggregate
+ function call, see <xref linkend="syntax-aggregates"> for more information.
+ When a <literal>FILTER</literal> clause is present, only those
+ rows matching the FILTER clause are included.
When <literal>GROUP BY</literal> is present, it is not valid for
the <command>SELECT</command> list expressions to refer to
ungrouped columns except within aggregate functions or if the
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index b139212..fdef6e8 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -1554,6 +1554,10 @@ sqrt(2)
<secondary>invocation</secondary>
</indexterm>
+ <indexterm zone="syntax-aggregates">
+ <primary>filter</primary>
+ </indexterm>
+
<para>
An <firstterm>aggregate expression</firstterm> represents the
application of an aggregate function across the rows selected by a
@@ -1562,10 +1566,10 @@ sqrt(2)
syntax of an aggregate expression is one of the following:
<synopsis>
-<replaceable>aggregate_name</replaceable> (<replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] )
-<replaceable>aggregate_name</replaceable> (ALL <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] )
-<replaceable>aggregate_name</replaceable> (DISTINCT <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] )
-<replaceable>aggregate_name</replaceable> ( * )
+<replaceable>aggregate_name</replaceable> (<replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
+<replaceable>aggregate_name</replaceable> (ALL <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
+<replaceable>aggregate_name</replaceable> (DISTINCT <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
+<replaceable>aggregate_name</replaceable> ( * ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
</synopsis>
where <replaceable>aggregate_name</replaceable> is a previously
@@ -1573,13 +1577,14 @@ sqrt(2)
<replaceable>expression</replaceable> is
any value expression that does not itself contain an aggregate
expression or a window function call, and
- <replaceable>order_by_clause</replaceable> is a optional
- <literal>ORDER BY</> clause as described below.
+ <replaceable>order_by_clause</replaceable> and
+ <replaceable>filter_clause</replaceable> are optional
+ <literal>ORDER BY</literal> and <literal>FILTER</literal> clauses as described below.
</para>
<para>
- The first form of aggregate expression invokes the aggregate
- once for each input row.
+ The first form of aggregate expression invokes the aggregate once
+ for each input row, each row matching same.
The second form is the same as the first, since
<literal>ALL</literal> is the default.
The third form invokes the aggregate once for each distinct value
@@ -1607,6 +1612,23 @@ sqrt(2)
</para>
<para>
+ If <literal>FILTER</literal> is specified, then only the input
+ rows for which the <replaceable>filter_clause</replaceable>
+ evaluates to true are fed to the aggregate function; other rows
+ are discarded. For example:
+<programlisting>
+SELECT
+ count(*) AS unfiltered,
+ count(*) FILTER (WHERE i < 5) AS filtered
+FROM generate_series(1,10) AS s(i);
+ unfiltered | filtered
+------------+----------
+ 10 | 4
+(1 row)
+</programlisting>
+ </para>
+
+ <para>
Ordinarily, the input rows are fed to the aggregate function in an
unspecified order. In many cases this does not matter; for example,
<function>min</> produces the same result no matter what order it
@@ -1709,10 +1731,10 @@ SELECT string_agg(a ORDER BY a, ',') FROM table; -- incorrect
The syntax of a window function call is one of the following:
<synopsis>
-<replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) OVER ( <replaceable class="parameter">window_definition</replaceable> )
-<replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) OVER <replaceable>window_name</replaceable>
-<replaceable>function_name</replaceable> ( * ) OVER ( <replaceable class="parameter">window_definition</replaceable> )
-<replaceable>function_name</replaceable> ( * ) OVER <replaceable>window_name</replaceable>
+<replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER ( <replaceable class="parameter">window_definition</replaceable> )
+<replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER <replaceable>window_name</replaceable>
+<replaceable>function_name</replaceable> ( * ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER ( <replaceable class="parameter">window_definition</replaceable> )
+<replaceable>function_name</replaceable> ( * ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER <replaceable>window_name</replaceable>
</synopsis>
where <replaceable class="parameter">window_definition</replaceable>
has the syntax
@@ -1836,16 +1858,18 @@ UNBOUNDED FOLLOWING
The built-in window functions are described in <xref
linkend="functions-window-table">. Other window functions can be added by
the user. Also, any built-in or user-defined aggregate function can be
- used as a window function.
+ used as a window function. A <literal>FILTER</literal> clause is
+ only valid for aggregate functions used in windowing.
</para>
<para>
- The syntaxes using <literal>*</> are used for calling parameter-less
- aggregate functions as window functions, for example
- <literal>count(*) OVER (PARTITION BY x ORDER BY y)</>.
- The asterisk (<literal>*</>) is customarily not used for non-aggregate window functions.
- Aggregate window functions, unlike normal aggregate functions, do not
- allow <literal>DISTINCT</> or <literal>ORDER BY</> to be used within the
+ The syntaxes using <literal>*</> are used for calling
+ parameter-less aggregate functions as window functions, for
+ example <literal>count(*) OVER (PARTITION BY x ORDER BY y)</>.
+ The asterisk (<literal>*</>) is customarily not used for
+ non-aggregate window functions. Aggregate window functions,
+ unlike normal aggregate functions, do not allow
+ <literal>DISTINCT</> or <literal>ORDER BY</> to be used within the
function argument list.
</para>
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 1388183..34dbef9 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -4410,6 +4410,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
astate->args = (List *) ExecInitExpr((Expr *) aggref->args,
parent);
+ astate->agg_filter = ExecInitExpr(aggref->agg_filter, parent);
/*
* Complain if the aggregate's arguments contain any
@@ -4448,6 +4449,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
wfstate->args = (List *) ExecInitExpr((Expr *) wfunc->args,
parent);
+ wfstate->agg_filter = ExecInitExpr(wfunc->agg_filter, parent);
/*
* Complain if the windowfunc's arguments contain any
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 12e1b8e..a43fdf2 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -381,7 +381,7 @@ sql_fn_post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var)
list_make1(subfield),
list_make1(param),
NIL, false, false, false,
- NULL, true, cref->location);
+ NULL, NULL, true, cref->location);
}
return param;
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index c741131..19105d2 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -488,6 +488,18 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
int i;
TupleTableSlot *slot;
+ /* Skip anything FILTERed out */
+ ExprState *filter = peraggstate->aggrefstate->agg_filter;
+ if (filter)
+ {
+ MemoryContext oldcontext = MemoryContextSwitchTo(aggstate->tmpcontext->ecxt_per_tuple_memory);
+ bool isnull;
+ Datum res = ExecEvalExpr(filter, aggstate->tmpcontext, &isnull, NULL);
+ MemoryContextSwitchTo(oldcontext);
+ if (isnull || !DatumGetBool(res))
+ continue;
+ }
+
/* Evaluate the current input expressions for this aggregate */
slot = ExecProject(peraggstate->evalproj, NULL);
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index d9f0e79..c00b058 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -227,9 +227,22 @@ advance_windowaggregate(WindowAggState *winstate,
int i;
MemoryContext oldContext;
ExprContext *econtext = winstate->tmpcontext;
+ ExprState *filter = wfuncstate->agg_filter;
oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+ /* Skip anything FILTERed out */
+ if (filter)
+ {
+ bool isnull;
+ Datum res = ExecEvalExpr(filter, econtext, &isnull, NULL);
+ if (isnull || !DatumGetBool(res))
+ {
+ MemoryContextSwitchTo(oldContext);
+ return;
+ }
+ }
+
/* We start from 1, since the 0th arg will be the transition value */
i = 1;
foreach(arg, wfuncstate->args)
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index b5b8d63..050fc83 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1137,6 +1137,7 @@ _copyAggref(const Aggref *from)
COPY_NODE_FIELD(args);
COPY_NODE_FIELD(aggorder);
COPY_NODE_FIELD(aggdistinct);
+ COPY_NODE_FIELD(agg_filter);
COPY_SCALAR_FIELD(aggstar);
COPY_SCALAR_FIELD(agglevelsup);
COPY_LOCATION_FIELD(location);
@@ -1157,6 +1158,7 @@ _copyWindowFunc(const WindowFunc *from)
COPY_SCALAR_FIELD(wincollid);
COPY_SCALAR_FIELD(inputcollid);
COPY_NODE_FIELD(args);
+ COPY_NODE_FIELD(agg_filter);
COPY_SCALAR_FIELD(winref);
COPY_SCALAR_FIELD(winstar);
COPY_SCALAR_FIELD(winagg);
@@ -2155,6 +2157,7 @@ _copyFuncCall(const FuncCall *from)
COPY_SCALAR_FIELD(agg_star);
COPY_SCALAR_FIELD(agg_distinct);
COPY_SCALAR_FIELD(func_variadic);
+ COPY_NODE_FIELD(agg_filter);
COPY_NODE_FIELD(over);
COPY_LOCATION_FIELD(location);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 3f96595..e1f63f1 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -196,6 +196,7 @@ _equalAggref(const Aggref *a, const Aggref *b)
COMPARE_NODE_FIELD(args);
COMPARE_NODE_FIELD(aggorder);
COMPARE_NODE_FIELD(aggdistinct);
+ COMPARE_NODE_FIELD(agg_filter);
COMPARE_SCALAR_FIELD(aggstar);
COMPARE_SCALAR_FIELD(agglevelsup);
COMPARE_LOCATION_FIELD(location);
@@ -211,6 +212,7 @@ _equalWindowFunc(const WindowFunc *a, const WindowFunc *b)
COMPARE_SCALAR_FIELD(wincollid);
COMPARE_SCALAR_FIELD(inputcollid);
COMPARE_NODE_FIELD(args);
+ COMPARE_NODE_FIELD(agg_filter);
COMPARE_SCALAR_FIELD(winref);
COMPARE_SCALAR_FIELD(winstar);
COMPARE_SCALAR_FIELD(winagg);
@@ -1995,6 +1997,7 @@ _equalFuncCall(const FuncCall *a, const FuncCall *b)
COMPARE_SCALAR_FIELD(agg_star);
COMPARE_SCALAR_FIELD(agg_distinct);
COMPARE_SCALAR_FIELD(func_variadic);
+ COMPARE_NODE_FIELD(agg_filter);
COMPARE_NODE_FIELD(over);
COMPARE_LOCATION_FIELD(location);
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 245aef2..4b20808 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -529,6 +529,7 @@ makeFuncCall(List *name, List *args, int location)
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
return n;
}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 42d6621..d5b4049 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1570,6 +1570,8 @@ expression_tree_walker(Node *node,
if (expression_tree_walker((Node *) expr->aggdistinct,
walker, context))
return true;
+ if (walker((Node *) expr->agg_filter, context))
+ return true;
}
break;
case T_WindowFunc:
@@ -1580,6 +1582,8 @@ expression_tree_walker(Node *node,
if (expression_tree_walker((Node *) expr->args,
walker, context))
return true;
+ if (walker((Node *) expr->agg_filter, context))
+ return true;
}
break;
case T_ArrayRef:
@@ -2079,6 +2083,7 @@ expression_tree_mutator(Node *node,
MUTATE(newnode->args, aggref->args, List *);
MUTATE(newnode->aggorder, aggref->aggorder, List *);
MUTATE(newnode->aggdistinct, aggref->aggdistinct, List *);
+ MUTATE(newnode->agg_filter, aggref->agg_filter, Expr *);
return (Node *) newnode;
}
break;
@@ -2089,6 +2094,7 @@ expression_tree_mutator(Node *node,
FLATCOPY(newnode, wfunc, WindowFunc);
MUTATE(newnode->args, wfunc->args, List *);
+ MUTATE(newnode->agg_filter, wfunc->agg_filter, Expr *);
return (Node *) newnode;
}
break;
@@ -2951,6 +2957,8 @@ raw_expression_tree_walker(Node *node,
return true;
if (walker(fcall->agg_order, context))
return true;
+ if (walker(fcall->agg_filter, context))
+ return true;
if (walker(fcall->over, context))
return true;
/* function name is deemed uninteresting */
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b2183f4..cc09a9a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -958,6 +958,7 @@ _outAggref(StringInfo str, const Aggref *node)
WRITE_NODE_FIELD(args);
WRITE_NODE_FIELD(aggorder);
WRITE_NODE_FIELD(aggdistinct);
+ WRITE_NODE_FIELD(agg_filter);
WRITE_BOOL_FIELD(aggstar);
WRITE_UINT_FIELD(agglevelsup);
WRITE_LOCATION_FIELD(location);
@@ -973,6 +974,7 @@ _outWindowFunc(StringInfo str, const WindowFunc *node)
WRITE_OID_FIELD(wincollid);
WRITE_OID_FIELD(inputcollid);
WRITE_NODE_FIELD(args);
+ WRITE_NODE_FIELD(agg_filter);
WRITE_UINT_FIELD(winref);
WRITE_BOOL_FIELD(winstar);
WRITE_BOOL_FIELD(winagg);
@@ -2083,6 +2085,7 @@ _outFuncCall(StringInfo str, const FuncCall *node)
WRITE_BOOL_FIELD(agg_star);
WRITE_BOOL_FIELD(agg_distinct);
WRITE_BOOL_FIELD(func_variadic);
+ WRITE_NODE_FIELD(agg_filter);
WRITE_NODE_FIELD(over);
WRITE_LOCATION_FIELD(location);
}
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3a16e9d..c9824b2 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -479,6 +479,7 @@ _readAggref(void)
READ_NODE_FIELD(args);
READ_NODE_FIELD(aggorder);
READ_NODE_FIELD(aggdistinct);
+ READ_NODE_FIELD(agg_filter);
READ_BOOL_FIELD(aggstar);
READ_UINT_FIELD(agglevelsup);
READ_LOCATION_FIELD(location);
@@ -499,6 +500,7 @@ _readWindowFunc(void)
READ_OID_FIELD(wincollid);
READ_OID_FIELD(inputcollid);
READ_NODE_FIELD(args);
+ READ_NODE_FIELD(agg_filter);
READ_UINT_FIELD(winref);
READ_BOOL_FIELD(winstar);
READ_BOOL_FIELD(winagg);
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
index 090ae0b..627eb4d 100644
--- a/src/backend/optimizer/plan/planagg.c
+++ b/src/backend/optimizer/plan/planagg.c
@@ -314,7 +314,7 @@ find_minmax_aggs_walker(Node *node, List **context)
ListCell *l;
Assert(aggref->agglevelsup == 0);
- if (list_length(aggref->args) != 1 || aggref->aggorder != NIL)
+ if (list_length(aggref->args) != 1 || aggref->aggorder != NIL || aggref->agg_filter != NULL)
return true; /* it couldn't be MIN/MAX */
/* note: we do not care if DISTINCT is mentioned ... */
curTarget = (TargetEntry *) linitial(aggref->args);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index f67ef0c..c465161 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -492,6 +492,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
opt_frame_clause frame_extent frame_bound
%type <str> opt_existing_window_name
%type <boolean> opt_if_not_exists
+%type <node> filter_clause
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -538,7 +539,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
EXTENSION EXTERNAL EXTRACT
- FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
+ FALSE_P FAMILY FETCH FILTER FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
FREEZE FROM FULL FUNCTION FUNCTIONS
GLOBAL GRANT GRANTED GREATEST GROUP_P
@@ -11111,10 +11112,11 @@ func_application: func_name '(' ')'
* (Note that many of the special SQL functions wouldn't actually make any
* sense as functional index entries, but we ignore that consideration here.)
*/
-func_expr: func_application over_clause
+func_expr: func_application filter_clause over_clause
{
FuncCall *n = (FuncCall*)$1;
- n->over = $2;
+ n->agg_filter = $2;
+ n->over = $3;
$$ = (Node*)n;
}
| func_expr_common_subexpr
@@ -11525,6 +11527,11 @@ window_definition:
}
;
+filter_clause:
+ FILTER '(' WHERE a_expr ')' { $$ = $4; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
over_clause: OVER window_specification
{ $$ = $2; }
| OVER ColId
@@ -12499,6 +12506,7 @@ unreserved_keyword:
| EXTENSION
| EXTERNAL
| FAMILY
+ | FILTER
| FIRST_P
| FOLLOWING
| FORCE
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 7380618..e506797 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -44,7 +44,7 @@ typedef struct
int sublevels_up;
} check_ungrouped_columns_context;
-static int check_agg_arguments(ParseState *pstate, List *args);
+static int check_agg_arguments(ParseState *pstate, List *args, Expr *filter);
static bool check_agg_arguments_walker(Node *node,
check_agg_arguments_context *context);
static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
@@ -160,7 +160,7 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
* Check the arguments to compute the aggregate's level and detect
* improper nesting.
*/
- min_varlevel = check_agg_arguments(pstate, agg->args);
+ min_varlevel = check_agg_arguments(pstate, agg->args, agg->agg_filter);
agg->agglevelsup = min_varlevel;
/* Mark the correct pstate level as having aggregates */
@@ -207,6 +207,9 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
case EXPR_KIND_HAVING:
/* okay */
break;
+ case EXPR_KIND_FILTER:
+ errkind = true;
+ break;
case EXPR_KIND_WINDOW_PARTITION:
/* okay */
break;
@@ -309,7 +312,7 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
* which we can't know until we finish scanning the arguments.
*/
static int
-check_agg_arguments(ParseState *pstate, List *args)
+check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
{
int agglevel;
check_agg_arguments_context context;
@@ -323,6 +326,10 @@ check_agg_arguments(ParseState *pstate, List *args)
check_agg_arguments_walker,
(void *) &context);
+ (void) expression_tree_walker((Node *) filter,
+ check_agg_arguments_walker,
+ (void *) &context);
+
/*
* If we found no vars nor aggs at all, it's a level-zero aggregate;
* otherwise, its level is the minimum of vars or aggs.
@@ -481,6 +488,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_HAVING:
errkind = true;
break;
+ case EXPR_KIND_FILTER:
+ errkind = true;
+ break;
case EXPR_KIND_WINDOW_PARTITION:
case EXPR_KIND_WINDOW_ORDER:
case EXPR_KIND_WINDOW_FRAME_RANGE:
diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c
index 80f6ac7..b84f2bd 100644
--- a/src/backend/parser/parse_collate.c
+++ b/src/backend/parser/parse_collate.c
@@ -575,6 +575,10 @@ assign_collations_walker(Node *node, assign_collations_context *context)
* the case above for T_TargetEntry will apply
* appropriate checks to agg ORDER BY items.
*
+ * Likewise, we assign collations for the (bool)
+ * expression in agg_filter, independently of
+ * any other args.
+ *
* We need not recurse into the aggorder or
* aggdistinct lists, because those contain only
* SortGroupClause nodes which we need not
@@ -595,6 +599,22 @@ assign_collations_walker(Node *node, assign_collations_context *context)
(void) assign_collations_walker((Node *) tle,
&loccontext);
}
+
+ assign_expr_collations(context->pstate, (Node *) aggref->agg_filter);
+ }
+ break;
+ case T_WindowFunc:
+ {
+ /*
+ * WindowFunc requires special processing only for
+ * its agg_filter clause, as for aggregates.
+ */
+ WindowFunc *wfunc = (WindowFunc *) node;
+
+ (void) assign_collations_walker((Node *) wfunc->args,
+ &loccontext);
+
+ assign_expr_collations(context->pstate, (Node *) wfunc->agg_filter);
}
break;
case T_CaseExpr:
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 06f6512..2272965 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -22,6 +22,7 @@
#include "nodes/nodeFuncs.h"
#include "optimizer/var.h"
#include "parser/analyze.h"
+#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
@@ -463,7 +464,7 @@ transformIndirection(ParseState *pstate, Node *basenode, List *indirection)
list_make1(n),
list_make1(result),
NIL, false, false, false,
- NULL, true, location);
+ NULL, NULL, true, location);
if (newresult == NULL)
unknown_attribute(pstate, result, strVal(n), location);
result = newresult;
@@ -631,7 +632,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
- NULL, true, cref->location);
+ NULL, NULL, true, cref->location);
}
break;
}
@@ -676,7 +677,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
- NULL, true, cref->location);
+ NULL, NULL, true, cref->location);
}
break;
}
@@ -734,7 +735,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
- NULL, true, cref->location);
+ NULL, NULL, true, cref->location);
}
break;
}
@@ -1241,6 +1242,7 @@ transformFuncCall(ParseState *pstate, FuncCall *fn)
{
List *targs;
ListCell *args;
+ Expr *tagg_filter;
/* Transform the list of arguments ... */
targs = NIL;
@@ -1250,6 +1252,12 @@ transformFuncCall(ParseState *pstate, FuncCall *fn)
(Node *) lfirst(args)));
}
+ /* Transform the aggregate filter using transformWhereClause, to
+ * which FILTER is virually identical... */
+ tagg_filter = NULL;
+ if (fn->agg_filter != NULL)
+ tagg_filter = (Expr *)transformWhereClause(pstate, (Node *)fn->agg_filter, EXPR_KIND_FILTER, "FILTER");
+
/* ... and hand off to ParseFuncOrColumn */
return ParseFuncOrColumn(pstate,
fn->funcname,
@@ -1258,6 +1266,7 @@ transformFuncCall(ParseState *pstate, FuncCall *fn)
fn->agg_star,
fn->agg_distinct,
fn->func_variadic,
+ tagg_filter,
fn->over,
false,
fn->location);
@@ -1430,6 +1439,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_FROM_FUNCTION:
case EXPR_KIND_WHERE:
case EXPR_KIND_HAVING:
+ case EXPR_KIND_FILTER:
case EXPR_KIND_WINDOW_PARTITION:
case EXPR_KIND_WINDOW_ORDER:
case EXPR_KIND_WINDOW_FRAME_RANGE:
@@ -2579,6 +2589,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "WHERE";
case EXPR_KIND_HAVING:
return "HAVING";
+ case EXPR_KIND_FILTER:
+ return "FILTER";
case EXPR_KIND_WINDOW_PARTITION:
return "window PARTITION BY";
case EXPR_KIND_WINDOW_ORDER:
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index ae7d195..75c740e 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -63,7 +63,7 @@ Node *
ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
List *agg_order, bool agg_star, bool agg_distinct,
bool func_variadic,
- WindowDef *over, bool is_column, int location)
+ Expr *agg_filter, WindowDef *over, bool is_column, int location)
{
Oid rettype;
Oid funcid;
@@ -175,7 +175,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
* wasn't any aggregate or variadic decoration, nor an argument name.
*/
if (nargs == 1 && agg_order == NIL && !agg_star && !agg_distinct &&
- over == NULL && !func_variadic && argnames == NIL &&
+ agg_filter == NULL && over == NULL && !func_variadic && argnames == NIL &&
list_length(funcname) == 1)
{
Oid argtype = actual_arg_types[0];
@@ -251,6 +251,12 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
errmsg("ORDER BY specified, but %s is not an aggregate function",
NameListToString(funcname)),
parser_errposition(pstate, location)));
+ if (agg_filter)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("FILTER specified, but %s is not an aggregate function",
+ NameListToString(funcname)),
+ parser_errposition(pstate, location)));
if (over)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -402,6 +408,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
/* aggcollid and inputcollid will be set by parse_collate.c */
/* args, aggorder, aggdistinct will be set by transformAggregateCall */
aggref->aggstar = agg_star;
+ /* filter */
+ aggref->agg_filter = agg_filter;
/* agglevelsup will be set by transformAggregateCall */
aggref->location = location;
@@ -460,6 +468,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
/* winref will be set by transformWindowFuncCall */
wfunc->winstar = agg_star;
wfunc->winagg = (fdresult == FUNCDETAIL_AGGREGATE);
+ wfunc->agg_filter = agg_filter;
wfunc->location = location;
/*
@@ -483,6 +492,16 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
parser_errposition(pstate, location)));
/*
+ * Reject window functions which are not aggregates in the
+ * case of FILTER.
+ */
+ if (!wfunc->winagg && agg_filter)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("FILTER is not implemented in non-aggregate window functions"),
+ parser_errposition(pstate, location)));
+
+ /*
* ordered aggs not allowed in windows yet
*/
if (agg_order != NIL)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index cf9ce3f..8eb015e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -7424,7 +7424,15 @@ get_agg_expr(Aggref *aggref, deparse_context *context)
appendStringInfoString(buf, " ORDER BY ");
get_rule_orderby(aggref->aggorder, aggref->args, false, context);
}
+
+ if (aggref->agg_filter != NULL)
+ {
+ appendStringInfoString(buf, ") FILTER (WHERE ");
+ get_rule_expr((Node *)aggref->agg_filter, context, false);
+ }
+
appendStringInfoChar(buf, ')');
+
}
/*
@@ -7461,6 +7469,13 @@ get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context)
appendStringInfoChar(buf, '*');
else
get_rule_expr((Node *) wfunc->args, context, true);
+
+ if (wfunc->agg_filter != NULL)
+ {
+ appendStringInfoString(buf, ") FILTER (WHERE ");
+ get_rule_expr((Node *)wfunc->agg_filter, context, false);
+ }
+
appendStringInfoString(buf, ") OVER ");
foreach(l, context->windowClause)
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 4f77016..a3e6b05 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -584,6 +584,7 @@ typedef struct AggrefExprState
{
ExprState xprstate;
List *args; /* states of argument expressions */
+ ExprState *agg_filter; /* FILTER expression */
int aggno; /* ID number for agg within its plan node */
} AggrefExprState;
@@ -595,6 +596,7 @@ typedef struct WindowFuncExprState
{
ExprState xprstate;
List *args; /* states of argument expressions */
+ ExprState *agg_filter; /* FILTER expression */
int wfuncno; /* ID number for wfunc within its plan node */
} WindowFuncExprState;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index de22dff..14f8d26 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -300,6 +300,7 @@ typedef struct FuncCall
bool agg_star; /* argument was really '*' */
bool agg_distinct; /* arguments were labeled DISTINCT */
bool func_variadic; /* last argument was labeled VARIADIC */
+ Node *agg_filter; /* FILTER clause, if any */
struct WindowDef *over; /* OVER clause, if any */
int location; /* token location, or -1 if unknown */
} FuncCall;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 75b716a..9f7111e 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -247,6 +247,7 @@ typedef struct Aggref
List *args; /* arguments and sort expressions */
List *aggorder; /* ORDER BY (list of SortGroupClause) */
List *aggdistinct; /* DISTINCT (list of SortGroupClause) */
+ Expr *agg_filter; /* FILTER expression */
bool aggstar; /* TRUE if argument list was really '*' */
Index agglevelsup; /* > 0 if agg belongs to outer query */
int location; /* token location, or -1 if unknown */
@@ -263,6 +264,7 @@ typedef struct WindowFunc
Oid wincollid; /* OID of collation of result */
Oid inputcollid; /* OID of collation that function should use */
List *args; /* arguments to the window function */
+ Expr *agg_filter; /* FILTER expression */
Index winref; /* index of associated WindowClause */
bool winstar; /* TRUE if argument list was really '*' */
bool winagg; /* is function a simple aggregate? */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index b3d72a9..287f78e 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -155,6 +155,7 @@ PG_KEYWORD("extract", EXTRACT, COL_NAME_KEYWORD)
PG_KEYWORD("false", FALSE_P, RESERVED_KEYWORD)
PG_KEYWORD("family", FAMILY, UNRESERVED_KEYWORD)
PG_KEYWORD("fetch", FETCH, RESERVED_KEYWORD)
+PG_KEYWORD("filter", FILTER, UNRESERVED_KEYWORD)
PG_KEYWORD("first", FIRST_P, UNRESERVED_KEYWORD)
PG_KEYWORD("float", FLOAT_P, COL_NAME_KEYWORD)
PG_KEYWORD("following", FOLLOWING, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_func.h b/src/include/parser/parse_func.h
index 6e09dc4..13efb57 100644
--- a/src/include/parser/parse_func.h
+++ b/src/include/parser/parse_func.h
@@ -46,7 +46,7 @@ extern Node *ParseFuncOrColumn(ParseState *pstate,
List *funcname, List *fargs,
List *agg_order, bool agg_star, bool agg_distinct,
bool func_variadic,
- WindowDef *over, bool is_column, int location);
+ Expr *agg_filter, WindowDef *over, bool is_column, int location);
extern FuncDetailCode func_get_detail(List *funcname,
List *fargs, List *fargnames,
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 49ca764..bea3b07 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -39,6 +39,7 @@ typedef enum ParseExprKind
EXPR_KIND_FROM_FUNCTION, /* function in FROM clause */
EXPR_KIND_WHERE, /* WHERE */
EXPR_KIND_HAVING, /* HAVING */
+ EXPR_KIND_FILTER, /* FILTER */
EXPR_KIND_WINDOW_PARTITION, /* window definition PARTITION BY */
EXPR_KIND_WINDOW_ORDER, /* window definition ORDER BY */
EXPR_KIND_WINDOW_FRAME_RANGE, /* window frame clause with RANGE */
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index d379c0d..9359811 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1154,3 +1154,69 @@ select string_agg(v, decode('ee', 'hex')) from bytea_test_table;
(1 row)
drop table bytea_test_table;
+-- FILTER tests
+select min(unique1) filter (where unique1 > 100) from tenk1;
+ min
+-----
+ 101
+(1 row)
+
+select ten, sum(distinct four) filter (where four::text ~ '123') from onek a
+group by ten;
+ ten | sum
+-----+-----
+ 0 |
+ 1 |
+ 2 |
+ 3 |
+ 4 |
+ 5 |
+ 6 |
+ 7 |
+ 8 |
+ 9 |
+(10 rows)
+
+select ten, sum(distinct four) filter (where four > 10) from onek a
+group by ten
+having exists (select 1 from onek b where sum(distinct a.four) = b.four);
+ ten | sum
+-----+-----
+ 0 |
+ 2 |
+ 4 |
+ 6 |
+ 8 |
+(5 rows)
+
+select max(foo COLLATE "C") filter (where (bar collate "POSIX") > '0') from (values ('a', 'b')) AS v(foo,bar);
+ max
+-----
+ a
+(1 row)
+
+-- outer-level aggregates
+select
+ (select max((select i.unique2 from tenk1 i where i.unique1 = o.unique1)) filter (where o.unique1 < 10))
+from tenk1 o;
+ max
+------
+ 9998
+(1 row)
+
+-- non-standard-conforming FILTER clause containing subquery
+select sum(unique1) FILTER (WHERE unique1 IN (SELECT unique1 FROM onek where unique1 < 100)) FROM tenk1;
+ sum
+------
+ 4950
+(1 row)
+
+-- exercise lots of aggregate parts with FILTER
+select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1)
+ from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+ generate_series(1,2) i;
+ aggfns
+---------------------------
+ {"(2,2,bar)","(3,1,baz)"}
+(1 row)
+
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index ecc1c2c..7b31d13 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -1020,5 +1020,18 @@ SELECT ntile(0) OVER (ORDER BY ten), ten, four FROM tenk1;
ERROR: argument of ntile must be greater than zero
SELECT nth_value(four, 0) OVER (ORDER BY ten), ten, four FROM tenk1;
ERROR: argument of nth_value must be greater than zero
+-- filter
+SELECT sum(salary), row_number() OVER (ORDER BY depname), sum(
+ sum(salary) FILTER (WHERE enroll_date > '2007-01-01')
+) FILTER (WHERE depname <> 'sales') OVER (ORDER BY depname DESC) AS "filtered_sum",
+ depname
+FROM empsalary GROUP BY depname;
+ sum | row_number | filtered_sum | depname
+-------+------------+--------------+-----------
+ 14600 | 3 | | sales
+ 7400 | 2 | 3500 | personnel
+ 25100 | 1 | 22600 | develop
+(3 rows)
+
-- cleanup
DROP TABLE empsalary;
diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql
index 38d4757..da0bd65 100644
--- a/src/test/regress/sql/aggregates.sql
+++ b/src/test/regress/sql/aggregates.sql
@@ -442,3 +442,31 @@ select string_agg(v, NULL) from bytea_test_table;
select string_agg(v, decode('ee', 'hex')) from bytea_test_table;
drop table bytea_test_table;
+
+-- FILTER tests
+
+select min(unique1) filter (where unique1 > 100) from tenk1;
+
+select ten, sum(distinct four) filter (where four::text ~ '123') from onek a
+group by ten;
+
+select ten, sum(distinct four) filter (where four > 10) from onek a
+group by ten
+having exists (select 1 from onek b where sum(distinct a.four) = b.four);
+
+select max(foo COLLATE "C") filter (where (bar collate "POSIX") > '0') from (values ('a', 'b')) AS v(foo,bar);
+
+-- outer-level aggregates
+select
+ (select max((select i.unique2 from tenk1 i where i.unique1 = o.unique1)) filter (where o.unique1 < 10))
+from tenk1 o;
+
+-- non-standard-conforming FILTER clause containing subquery
+
+select sum(unique1) FILTER (WHERE unique1 IN (SELECT unique1 FROM onek where unique1 < 100)) FROM tenk1;
+
+-- exercise lots of aggregate parts with FILTER
+
+select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1)
+ from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+ generate_series(1,2) i;
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 769be0f..6ee3696 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -264,5 +264,13 @@ SELECT ntile(0) OVER (ORDER BY ten), ten, four FROM tenk1;
SELECT nth_value(four, 0) OVER (ORDER BY ten), ten, four FROM tenk1;
+-- filter
+
+SELECT sum(salary), row_number() OVER (ORDER BY depname), sum(
+ sum(salary) FILTER (WHERE enroll_date > '2007-01-01')
+) FILTER (WHERE depname <> 'sales') OVER (ORDER BY depname DESC) AS "filtered_sum",
+ depname
+FROM empsalary GROUP BY depname;
+
-- cleanup
DROP TABLE empsalary;
On 5 July 2013 18:23, David Fetter <david@fetter.org> wrote:
Please find attached changes based on the above.
This looks good. The grammar changes are smaller and neater now on top
of the makeFuncCall() patch.
Overall I think this patch offers useful additional functionality, in
compliance with the SQL spec, which should be handy to simplify
complex grouping queries.
There's a minor typo in syntax.sgml: "for each input row, each row
matching same." --- fix attached.
I think this is ready for committer.
Regards,
Dean
Attachments:
filter_008b.diffapplication/octet-stream; name=filter_008b.diffDownload
diff --git a/doc/src/sgml/keywords.sgml b/doc/src/sgml/keywords.sgml
index 5e3b33a..ecfde99 100644
--- a/doc/src/sgml/keywords.sgml
+++ b/doc/src/sgml/keywords.sgml
@@ -1786,7 +1786,7 @@
</row>
<row>
<entry><token>FILTER</token></entry>
- <entry></entry>
+ <entry>non-reserved</entry>
<entry>reserved</entry>
<entry>reserved</entry>
<entry></entry>
@@ -3200,7 +3200,7 @@
</row>
<row>
<entry><token>OVER</token></entry>
- <entry>reserved (can be function or type)</entry>
+ <entry>non-reserved</entry>
<entry>reserved</entry>
<entry>reserved</entry>
<entry></entry>
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 68309ba..709d5ae 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -598,6 +598,11 @@ GROUP BY <replaceable class="parameter">expression</replaceable> [, ...]
making up each group, producing a separate value for each group
(whereas without <literal>GROUP BY</literal>, an aggregate
produces a single value computed across all the selected rows).
+ The set of rows fed to the aggregate function can be further filtered
+ by attaching a <literal>FILTER</literal> clause to the aggregate
+ function call, see <xref linkend="syntax-aggregates"> for more information.
+ When a <literal>FILTER</literal> clause is present, only those
+ rows matching the FILTER clause are included.
When <literal>GROUP BY</literal> is present, it is not valid for
the <command>SELECT</command> list expressions to refer to
ungrouped columns except within aggregate functions or if the
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index b139212..fdef6e8 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -1554,6 +1554,10 @@ sqrt(2)
<secondary>invocation</secondary>
</indexterm>
+ <indexterm zone="syntax-aggregates">
+ <primary>filter</primary>
+ </indexterm>
+
<para>
An <firstterm>aggregate expression</firstterm> represents the
application of an aggregate function across the rows selected by a
@@ -1562,10 +1566,10 @@ sqrt(2)
syntax of an aggregate expression is one of the following:
<synopsis>
-<replaceable>aggregate_name</replaceable> (<replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] )
-<replaceable>aggregate_name</replaceable> (ALL <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] )
-<replaceable>aggregate_name</replaceable> (DISTINCT <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] )
-<replaceable>aggregate_name</replaceable> ( * )
+<replaceable>aggregate_name</replaceable> (<replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
+<replaceable>aggregate_name</replaceable> (ALL <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
+<replaceable>aggregate_name</replaceable> (DISTINCT <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
+<replaceable>aggregate_name</replaceable> ( * ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
</synopsis>
where <replaceable>aggregate_name</replaceable> is a previously
@@ -1573,8 +1577,9 @@ sqrt(2)
<replaceable>expression</replaceable> is
any value expression that does not itself contain an aggregate
expression or a window function call, and
- <replaceable>order_by_clause</replaceable> is a optional
- <literal>ORDER BY</> clause as described below.
+ <replaceable>order_by_clause</replaceable> and
+ <replaceable>filter_clause</replaceable> are optional
+ <literal>ORDER BY</literal> and <literal>FILTER</literal> clauses as described below.
</para>
<para>
@@ -1607,6 +1612,23 @@ sqrt(2)
</para>
<para>
+ If <literal>FILTER</literal> is specified, then only the input
+ rows for which the <replaceable>filter_clause</replaceable>
+ evaluates to true are fed to the aggregate function; other rows
+ are discarded. For example:
+<programlisting>
+SELECT
+ count(*) AS unfiltered,
+ count(*) FILTER (WHERE i < 5) AS filtered
+FROM generate_series(1,10) AS s(i);
+ unfiltered | filtered
+------------+----------
+ 10 | 4
+(1 row)
+</programlisting>
+ </para>
+
+ <para>
Ordinarily, the input rows are fed to the aggregate function in an
unspecified order. In many cases this does not matter; for example,
<function>min</> produces the same result no matter what order it
@@ -1709,10 +1731,10 @@ SELECT string_agg(a ORDER BY a, ',') FROM table; -- incorrect
The syntax of a window function call is one of the following:
<synopsis>
-<replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) OVER ( <replaceable class="parameter">window_definition</replaceable> )
-<replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) OVER <replaceable>window_name</replaceable>
-<replaceable>function_name</replaceable> ( * ) OVER ( <replaceable class="parameter">window_definition</replaceable> )
-<replaceable>function_name</replaceable> ( * ) OVER <replaceable>window_name</replaceable>
+<replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER ( <replaceable class="parameter">window_definition</replaceable> )
+<replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER <replaceable>window_name</replaceable>
+<replaceable>function_name</replaceable> ( * ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER ( <replaceable class="parameter">window_definition</replaceable> )
+<replaceable>function_name</replaceable> ( * ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER <replaceable>window_name</replaceable>
</synopsis>
where <replaceable class="parameter">window_definition</replaceable>
has the syntax
@@ -1836,16 +1858,18 @@ UNBOUNDED FOLLOWING
The built-in window functions are described in <xref
linkend="functions-window-table">. Other window functions can be added by
the user. Also, any built-in or user-defined aggregate function can be
- used as a window function.
+ used as a window function. A <literal>FILTER</literal> clause is
+ only valid for aggregate functions used in windowing.
</para>
<para>
- The syntaxes using <literal>*</> are used for calling parameter-less
- aggregate functions as window functions, for example
- <literal>count(*) OVER (PARTITION BY x ORDER BY y)</>.
- The asterisk (<literal>*</>) is customarily not used for non-aggregate window functions.
- Aggregate window functions, unlike normal aggregate functions, do not
- allow <literal>DISTINCT</> or <literal>ORDER BY</> to be used within the
+ The syntaxes using <literal>*</> are used for calling
+ parameter-less aggregate functions as window functions, for
+ example <literal>count(*) OVER (PARTITION BY x ORDER BY y)</>.
+ The asterisk (<literal>*</>) is customarily not used for
+ non-aggregate window functions. Aggregate window functions,
+ unlike normal aggregate functions, do not allow
+ <literal>DISTINCT</> or <literal>ORDER BY</> to be used within the
function argument list.
</para>
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 1388183..34dbef9 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -4410,6 +4410,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
astate->args = (List *) ExecInitExpr((Expr *) aggref->args,
parent);
+ astate->agg_filter = ExecInitExpr(aggref->agg_filter, parent);
/*
* Complain if the aggregate's arguments contain any
@@ -4448,6 +4449,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
wfstate->args = (List *) ExecInitExpr((Expr *) wfunc->args,
parent);
+ wfstate->agg_filter = ExecInitExpr(wfunc->agg_filter, parent);
/*
* Complain if the windowfunc's arguments contain any
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 12e1b8e..a43fdf2 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -381,7 +381,7 @@ sql_fn_post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var)
list_make1(subfield),
list_make1(param),
NIL, false, false, false,
- NULL, true, cref->location);
+ NULL, NULL, true, cref->location);
}
return param;
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index c741131..19105d2 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -488,6 +488,18 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
int i;
TupleTableSlot *slot;
+ /* Skip anything FILTERed out */
+ ExprState *filter = peraggstate->aggrefstate->agg_filter;
+ if (filter)
+ {
+ MemoryContext oldcontext = MemoryContextSwitchTo(aggstate->tmpcontext->ecxt_per_tuple_memory);
+ bool isnull;
+ Datum res = ExecEvalExpr(filter, aggstate->tmpcontext, &isnull, NULL);
+ MemoryContextSwitchTo(oldcontext);
+ if (isnull || !DatumGetBool(res))
+ continue;
+ }
+
/* Evaluate the current input expressions for this aggregate */
slot = ExecProject(peraggstate->evalproj, NULL);
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index d9f0e79..c00b058 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -227,9 +227,22 @@ advance_windowaggregate(WindowAggState *winstate,
int i;
MemoryContext oldContext;
ExprContext *econtext = winstate->tmpcontext;
+ ExprState *filter = wfuncstate->agg_filter;
oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+ /* Skip anything FILTERed out */
+ if (filter)
+ {
+ bool isnull;
+ Datum res = ExecEvalExpr(filter, econtext, &isnull, NULL);
+ if (isnull || !DatumGetBool(res))
+ {
+ MemoryContextSwitchTo(oldContext);
+ return;
+ }
+ }
+
/* We start from 1, since the 0th arg will be the transition value */
i = 1;
foreach(arg, wfuncstate->args)
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index b5b8d63..050fc83 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1137,6 +1137,7 @@ _copyAggref(const Aggref *from)
COPY_NODE_FIELD(args);
COPY_NODE_FIELD(aggorder);
COPY_NODE_FIELD(aggdistinct);
+ COPY_NODE_FIELD(agg_filter);
COPY_SCALAR_FIELD(aggstar);
COPY_SCALAR_FIELD(agglevelsup);
COPY_LOCATION_FIELD(location);
@@ -1157,6 +1158,7 @@ _copyWindowFunc(const WindowFunc *from)
COPY_SCALAR_FIELD(wincollid);
COPY_SCALAR_FIELD(inputcollid);
COPY_NODE_FIELD(args);
+ COPY_NODE_FIELD(agg_filter);
COPY_SCALAR_FIELD(winref);
COPY_SCALAR_FIELD(winstar);
COPY_SCALAR_FIELD(winagg);
@@ -2155,6 +2157,7 @@ _copyFuncCall(const FuncCall *from)
COPY_SCALAR_FIELD(agg_star);
COPY_SCALAR_FIELD(agg_distinct);
COPY_SCALAR_FIELD(func_variadic);
+ COPY_NODE_FIELD(agg_filter);
COPY_NODE_FIELD(over);
COPY_LOCATION_FIELD(location);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 3f96595..e1f63f1 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -196,6 +196,7 @@ _equalAggref(const Aggref *a, const Aggref *b)
COMPARE_NODE_FIELD(args);
COMPARE_NODE_FIELD(aggorder);
COMPARE_NODE_FIELD(aggdistinct);
+ COMPARE_NODE_FIELD(agg_filter);
COMPARE_SCALAR_FIELD(aggstar);
COMPARE_SCALAR_FIELD(agglevelsup);
COMPARE_LOCATION_FIELD(location);
@@ -211,6 +212,7 @@ _equalWindowFunc(const WindowFunc *a, const WindowFunc *b)
COMPARE_SCALAR_FIELD(wincollid);
COMPARE_SCALAR_FIELD(inputcollid);
COMPARE_NODE_FIELD(args);
+ COMPARE_NODE_FIELD(agg_filter);
COMPARE_SCALAR_FIELD(winref);
COMPARE_SCALAR_FIELD(winstar);
COMPARE_SCALAR_FIELD(winagg);
@@ -1995,6 +1997,7 @@ _equalFuncCall(const FuncCall *a, const FuncCall *b)
COMPARE_SCALAR_FIELD(agg_star);
COMPARE_SCALAR_FIELD(agg_distinct);
COMPARE_SCALAR_FIELD(func_variadic);
+ COMPARE_NODE_FIELD(agg_filter);
COMPARE_NODE_FIELD(over);
COMPARE_LOCATION_FIELD(location);
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 245aef2..4b20808 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -529,6 +529,7 @@ makeFuncCall(List *name, List *args, int location)
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
+ n->agg_filter = NULL;
n->over = NULL;
return n;
}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 42d6621..d5b4049 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1570,6 +1570,8 @@ expression_tree_walker(Node *node,
if (expression_tree_walker((Node *) expr->aggdistinct,
walker, context))
return true;
+ if (walker((Node *) expr->agg_filter, context))
+ return true;
}
break;
case T_WindowFunc:
@@ -1580,6 +1582,8 @@ expression_tree_walker(Node *node,
if (expression_tree_walker((Node *) expr->args,
walker, context))
return true;
+ if (walker((Node *) expr->agg_filter, context))
+ return true;
}
break;
case T_ArrayRef:
@@ -2079,6 +2083,7 @@ expression_tree_mutator(Node *node,
MUTATE(newnode->args, aggref->args, List *);
MUTATE(newnode->aggorder, aggref->aggorder, List *);
MUTATE(newnode->aggdistinct, aggref->aggdistinct, List *);
+ MUTATE(newnode->agg_filter, aggref->agg_filter, Expr *);
return (Node *) newnode;
}
break;
@@ -2089,6 +2094,7 @@ expression_tree_mutator(Node *node,
FLATCOPY(newnode, wfunc, WindowFunc);
MUTATE(newnode->args, wfunc->args, List *);
+ MUTATE(newnode->agg_filter, wfunc->agg_filter, Expr *);
return (Node *) newnode;
}
break;
@@ -2951,6 +2957,8 @@ raw_expression_tree_walker(Node *node,
return true;
if (walker(fcall->agg_order, context))
return true;
+ if (walker(fcall->agg_filter, context))
+ return true;
if (walker(fcall->over, context))
return true;
/* function name is deemed uninteresting */
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b2183f4..cc09a9a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -958,6 +958,7 @@ _outAggref(StringInfo str, const Aggref *node)
WRITE_NODE_FIELD(args);
WRITE_NODE_FIELD(aggorder);
WRITE_NODE_FIELD(aggdistinct);
+ WRITE_NODE_FIELD(agg_filter);
WRITE_BOOL_FIELD(aggstar);
WRITE_UINT_FIELD(agglevelsup);
WRITE_LOCATION_FIELD(location);
@@ -973,6 +974,7 @@ _outWindowFunc(StringInfo str, const WindowFunc *node)
WRITE_OID_FIELD(wincollid);
WRITE_OID_FIELD(inputcollid);
WRITE_NODE_FIELD(args);
+ WRITE_NODE_FIELD(agg_filter);
WRITE_UINT_FIELD(winref);
WRITE_BOOL_FIELD(winstar);
WRITE_BOOL_FIELD(winagg);
@@ -2083,6 +2085,7 @@ _outFuncCall(StringInfo str, const FuncCall *node)
WRITE_BOOL_FIELD(agg_star);
WRITE_BOOL_FIELD(agg_distinct);
WRITE_BOOL_FIELD(func_variadic);
+ WRITE_NODE_FIELD(agg_filter);
WRITE_NODE_FIELD(over);
WRITE_LOCATION_FIELD(location);
}
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3a16e9d..c9824b2 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -479,6 +479,7 @@ _readAggref(void)
READ_NODE_FIELD(args);
READ_NODE_FIELD(aggorder);
READ_NODE_FIELD(aggdistinct);
+ READ_NODE_FIELD(agg_filter);
READ_BOOL_FIELD(aggstar);
READ_UINT_FIELD(agglevelsup);
READ_LOCATION_FIELD(location);
@@ -499,6 +500,7 @@ _readWindowFunc(void)
READ_OID_FIELD(wincollid);
READ_OID_FIELD(inputcollid);
READ_NODE_FIELD(args);
+ READ_NODE_FIELD(agg_filter);
READ_UINT_FIELD(winref);
READ_BOOL_FIELD(winstar);
READ_BOOL_FIELD(winagg);
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
index 090ae0b..627eb4d 100644
--- a/src/backend/optimizer/plan/planagg.c
+++ b/src/backend/optimizer/plan/planagg.c
@@ -314,7 +314,7 @@ find_minmax_aggs_walker(Node *node, List **context)
ListCell *l;
Assert(aggref->agglevelsup == 0);
- if (list_length(aggref->args) != 1 || aggref->aggorder != NIL)
+ if (list_length(aggref->args) != 1 || aggref->aggorder != NIL || aggref->agg_filter != NULL)
return true; /* it couldn't be MIN/MAX */
/* note: we do not care if DISTINCT is mentioned ... */
curTarget = (TargetEntry *) linitial(aggref->args);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index f67ef0c..c465161 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -492,6 +492,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
opt_frame_clause frame_extent frame_bound
%type <str> opt_existing_window_name
%type <boolean> opt_if_not_exists
+%type <node> filter_clause
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -538,7 +539,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
EXTENSION EXTERNAL EXTRACT
- FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
+ FALSE_P FAMILY FETCH FILTER FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
FREEZE FROM FULL FUNCTION FUNCTIONS
GLOBAL GRANT GRANTED GREATEST GROUP_P
@@ -11111,10 +11112,11 @@ func_application: func_name '(' ')'
* (Note that many of the special SQL functions wouldn't actually make any
* sense as functional index entries, but we ignore that consideration here.)
*/
-func_expr: func_application over_clause
+func_expr: func_application filter_clause over_clause
{
FuncCall *n = (FuncCall*)$1;
- n->over = $2;
+ n->agg_filter = $2;
+ n->over = $3;
$$ = (Node*)n;
}
| func_expr_common_subexpr
@@ -11525,6 +11527,11 @@ window_definition:
}
;
+filter_clause:
+ FILTER '(' WHERE a_expr ')' { $$ = $4; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
over_clause: OVER window_specification
{ $$ = $2; }
| OVER ColId
@@ -12499,6 +12506,7 @@ unreserved_keyword:
| EXTENSION
| EXTERNAL
| FAMILY
+ | FILTER
| FIRST_P
| FOLLOWING
| FORCE
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 7380618..e506797 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -44,7 +44,7 @@ typedef struct
int sublevels_up;
} check_ungrouped_columns_context;
-static int check_agg_arguments(ParseState *pstate, List *args);
+static int check_agg_arguments(ParseState *pstate, List *args, Expr *filter);
static bool check_agg_arguments_walker(Node *node,
check_agg_arguments_context *context);
static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
@@ -160,7 +160,7 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
* Check the arguments to compute the aggregate's level and detect
* improper nesting.
*/
- min_varlevel = check_agg_arguments(pstate, agg->args);
+ min_varlevel = check_agg_arguments(pstate, agg->args, agg->agg_filter);
agg->agglevelsup = min_varlevel;
/* Mark the correct pstate level as having aggregates */
@@ -207,6 +207,9 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
case EXPR_KIND_HAVING:
/* okay */
break;
+ case EXPR_KIND_FILTER:
+ errkind = true;
+ break;
case EXPR_KIND_WINDOW_PARTITION:
/* okay */
break;
@@ -309,7 +312,7 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
* which we can't know until we finish scanning the arguments.
*/
static int
-check_agg_arguments(ParseState *pstate, List *args)
+check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
{
int agglevel;
check_agg_arguments_context context;
@@ -323,6 +326,10 @@ check_agg_arguments(ParseState *pstate, List *args)
check_agg_arguments_walker,
(void *) &context);
+ (void) expression_tree_walker((Node *) filter,
+ check_agg_arguments_walker,
+ (void *) &context);
+
/*
* If we found no vars nor aggs at all, it's a level-zero aggregate;
* otherwise, its level is the minimum of vars or aggs.
@@ -481,6 +488,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_HAVING:
errkind = true;
break;
+ case EXPR_KIND_FILTER:
+ errkind = true;
+ break;
case EXPR_KIND_WINDOW_PARTITION:
case EXPR_KIND_WINDOW_ORDER:
case EXPR_KIND_WINDOW_FRAME_RANGE:
diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c
index 80f6ac7..b84f2bd 100644
--- a/src/backend/parser/parse_collate.c
+++ b/src/backend/parser/parse_collate.c
@@ -575,6 +575,10 @@ assign_collations_walker(Node *node, assign_collations_context *context)
* the case above for T_TargetEntry will apply
* appropriate checks to agg ORDER BY items.
*
+ * Likewise, we assign collations for the (bool)
+ * expression in agg_filter, independently of
+ * any other args.
+ *
* We need not recurse into the aggorder or
* aggdistinct lists, because those contain only
* SortGroupClause nodes which we need not
@@ -595,6 +599,22 @@ assign_collations_walker(Node *node, assign_collations_context *context)
(void) assign_collations_walker((Node *) tle,
&loccontext);
}
+
+ assign_expr_collations(context->pstate, (Node *) aggref->agg_filter);
+ }
+ break;
+ case T_WindowFunc:
+ {
+ /*
+ * WindowFunc requires special processing only for
+ * its agg_filter clause, as for aggregates.
+ */
+ WindowFunc *wfunc = (WindowFunc *) node;
+
+ (void) assign_collations_walker((Node *) wfunc->args,
+ &loccontext);
+
+ assign_expr_collations(context->pstate, (Node *) wfunc->agg_filter);
}
break;
case T_CaseExpr:
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 06f6512..2272965 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -22,6 +22,7 @@
#include "nodes/nodeFuncs.h"
#include "optimizer/var.h"
#include "parser/analyze.h"
+#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
@@ -463,7 +464,7 @@ transformIndirection(ParseState *pstate, Node *basenode, List *indirection)
list_make1(n),
list_make1(result),
NIL, false, false, false,
- NULL, true, location);
+ NULL, NULL, true, location);
if (newresult == NULL)
unknown_attribute(pstate, result, strVal(n), location);
result = newresult;
@@ -631,7 +632,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
- NULL, true, cref->location);
+ NULL, NULL, true, cref->location);
}
break;
}
@@ -676,7 +677,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
- NULL, true, cref->location);
+ NULL, NULL, true, cref->location);
}
break;
}
@@ -734,7 +735,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
list_make1(makeString(colname)),
list_make1(node),
NIL, false, false, false,
- NULL, true, cref->location);
+ NULL, NULL, true, cref->location);
}
break;
}
@@ -1241,6 +1242,7 @@ transformFuncCall(ParseState *pstate, FuncCall *fn)
{
List *targs;
ListCell *args;
+ Expr *tagg_filter;
/* Transform the list of arguments ... */
targs = NIL;
@@ -1250,6 +1252,12 @@ transformFuncCall(ParseState *pstate, FuncCall *fn)
(Node *) lfirst(args)));
}
+ /* Transform the aggregate filter using transformWhereClause, to
+ * which FILTER is virually identical... */
+ tagg_filter = NULL;
+ if (fn->agg_filter != NULL)
+ tagg_filter = (Expr *)transformWhereClause(pstate, (Node *)fn->agg_filter, EXPR_KIND_FILTER, "FILTER");
+
/* ... and hand off to ParseFuncOrColumn */
return ParseFuncOrColumn(pstate,
fn->funcname,
@@ -1258,6 +1266,7 @@ transformFuncCall(ParseState *pstate, FuncCall *fn)
fn->agg_star,
fn->agg_distinct,
fn->func_variadic,
+ tagg_filter,
fn->over,
false,
fn->location);
@@ -1430,6 +1439,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_FROM_FUNCTION:
case EXPR_KIND_WHERE:
case EXPR_KIND_HAVING:
+ case EXPR_KIND_FILTER:
case EXPR_KIND_WINDOW_PARTITION:
case EXPR_KIND_WINDOW_ORDER:
case EXPR_KIND_WINDOW_FRAME_RANGE:
@@ -2579,6 +2589,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "WHERE";
case EXPR_KIND_HAVING:
return "HAVING";
+ case EXPR_KIND_FILTER:
+ return "FILTER";
case EXPR_KIND_WINDOW_PARTITION:
return "window PARTITION BY";
case EXPR_KIND_WINDOW_ORDER:
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index ae7d195..75c740e 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -63,7 +63,7 @@ Node *
ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
List *agg_order, bool agg_star, bool agg_distinct,
bool func_variadic,
- WindowDef *over, bool is_column, int location)
+ Expr *agg_filter, WindowDef *over, bool is_column, int location)
{
Oid rettype;
Oid funcid;
@@ -175,7 +175,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
* wasn't any aggregate or variadic decoration, nor an argument name.
*/
if (nargs == 1 && agg_order == NIL && !agg_star && !agg_distinct &&
- over == NULL && !func_variadic && argnames == NIL &&
+ agg_filter == NULL && over == NULL && !func_variadic && argnames == NIL &&
list_length(funcname) == 1)
{
Oid argtype = actual_arg_types[0];
@@ -251,6 +251,12 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
errmsg("ORDER BY specified, but %s is not an aggregate function",
NameListToString(funcname)),
parser_errposition(pstate, location)));
+ if (agg_filter)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("FILTER specified, but %s is not an aggregate function",
+ NameListToString(funcname)),
+ parser_errposition(pstate, location)));
if (over)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -402,6 +408,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
/* aggcollid and inputcollid will be set by parse_collate.c */
/* args, aggorder, aggdistinct will be set by transformAggregateCall */
aggref->aggstar = agg_star;
+ /* filter */
+ aggref->agg_filter = agg_filter;
/* agglevelsup will be set by transformAggregateCall */
aggref->location = location;
@@ -460,6 +468,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
/* winref will be set by transformWindowFuncCall */
wfunc->winstar = agg_star;
wfunc->winagg = (fdresult == FUNCDETAIL_AGGREGATE);
+ wfunc->agg_filter = agg_filter;
wfunc->location = location;
/*
@@ -483,6 +492,16 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
parser_errposition(pstate, location)));
/*
+ * Reject window functions which are not aggregates in the
+ * case of FILTER.
+ */
+ if (!wfunc->winagg && agg_filter)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("FILTER is not implemented in non-aggregate window functions"),
+ parser_errposition(pstate, location)));
+
+ /*
* ordered aggs not allowed in windows yet
*/
if (agg_order != NIL)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index cf9ce3f..8eb015e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -7424,7 +7424,15 @@ get_agg_expr(Aggref *aggref, deparse_context *context)
appendStringInfoString(buf, " ORDER BY ");
get_rule_orderby(aggref->aggorder, aggref->args, false, context);
}
+
+ if (aggref->agg_filter != NULL)
+ {
+ appendStringInfoString(buf, ") FILTER (WHERE ");
+ get_rule_expr((Node *)aggref->agg_filter, context, false);
+ }
+
appendStringInfoChar(buf, ')');
+
}
/*
@@ -7461,6 +7469,13 @@ get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context)
appendStringInfoChar(buf, '*');
else
get_rule_expr((Node *) wfunc->args, context, true);
+
+ if (wfunc->agg_filter != NULL)
+ {
+ appendStringInfoString(buf, ") FILTER (WHERE ");
+ get_rule_expr((Node *)wfunc->agg_filter, context, false);
+ }
+
appendStringInfoString(buf, ") OVER ");
foreach(l, context->windowClause)
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 4f77016..a3e6b05 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -584,6 +584,7 @@ typedef struct AggrefExprState
{
ExprState xprstate;
List *args; /* states of argument expressions */
+ ExprState *agg_filter; /* FILTER expression */
int aggno; /* ID number for agg within its plan node */
} AggrefExprState;
@@ -595,6 +596,7 @@ typedef struct WindowFuncExprState
{
ExprState xprstate;
List *args; /* states of argument expressions */
+ ExprState *agg_filter; /* FILTER expression */
int wfuncno; /* ID number for wfunc within its plan node */
} WindowFuncExprState;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index de22dff..14f8d26 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -300,6 +300,7 @@ typedef struct FuncCall
bool agg_star; /* argument was really '*' */
bool agg_distinct; /* arguments were labeled DISTINCT */
bool func_variadic; /* last argument was labeled VARIADIC */
+ Node *agg_filter; /* FILTER clause, if any */
struct WindowDef *over; /* OVER clause, if any */
int location; /* token location, or -1 if unknown */
} FuncCall;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 75b716a..9f7111e 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -247,6 +247,7 @@ typedef struct Aggref
List *args; /* arguments and sort expressions */
List *aggorder; /* ORDER BY (list of SortGroupClause) */
List *aggdistinct; /* DISTINCT (list of SortGroupClause) */
+ Expr *agg_filter; /* FILTER expression */
bool aggstar; /* TRUE if argument list was really '*' */
Index agglevelsup; /* > 0 if agg belongs to outer query */
int location; /* token location, or -1 if unknown */
@@ -263,6 +264,7 @@ typedef struct WindowFunc
Oid wincollid; /* OID of collation of result */
Oid inputcollid; /* OID of collation that function should use */
List *args; /* arguments to the window function */
+ Expr *agg_filter; /* FILTER expression */
Index winref; /* index of associated WindowClause */
bool winstar; /* TRUE if argument list was really '*' */
bool winagg; /* is function a simple aggregate? */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index b3d72a9..287f78e 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -155,6 +155,7 @@ PG_KEYWORD("extract", EXTRACT, COL_NAME_KEYWORD)
PG_KEYWORD("false", FALSE_P, RESERVED_KEYWORD)
PG_KEYWORD("family", FAMILY, UNRESERVED_KEYWORD)
PG_KEYWORD("fetch", FETCH, RESERVED_KEYWORD)
+PG_KEYWORD("filter", FILTER, UNRESERVED_KEYWORD)
PG_KEYWORD("first", FIRST_P, UNRESERVED_KEYWORD)
PG_KEYWORD("float", FLOAT_P, COL_NAME_KEYWORD)
PG_KEYWORD("following", FOLLOWING, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_func.h b/src/include/parser/parse_func.h
index 6e09dc4..13efb57 100644
--- a/src/include/parser/parse_func.h
+++ b/src/include/parser/parse_func.h
@@ -46,7 +46,7 @@ extern Node *ParseFuncOrColumn(ParseState *pstate,
List *funcname, List *fargs,
List *agg_order, bool agg_star, bool agg_distinct,
bool func_variadic,
- WindowDef *over, bool is_column, int location);
+ Expr *agg_filter, WindowDef *over, bool is_column, int location);
extern FuncDetailCode func_get_detail(List *funcname,
List *fargs, List *fargnames,
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 49ca764..bea3b07 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -39,6 +39,7 @@ typedef enum ParseExprKind
EXPR_KIND_FROM_FUNCTION, /* function in FROM clause */
EXPR_KIND_WHERE, /* WHERE */
EXPR_KIND_HAVING, /* HAVING */
+ EXPR_KIND_FILTER, /* FILTER */
EXPR_KIND_WINDOW_PARTITION, /* window definition PARTITION BY */
EXPR_KIND_WINDOW_ORDER, /* window definition ORDER BY */
EXPR_KIND_WINDOW_FRAME_RANGE, /* window frame clause with RANGE */
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index d379c0d..9359811 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1154,3 +1154,69 @@ select string_agg(v, decode('ee', 'hex')) from bytea_test_table;
(1 row)
drop table bytea_test_table;
+-- FILTER tests
+select min(unique1) filter (where unique1 > 100) from tenk1;
+ min
+-----
+ 101
+(1 row)
+
+select ten, sum(distinct four) filter (where four::text ~ '123') from onek a
+group by ten;
+ ten | sum
+-----+-----
+ 0 |
+ 1 |
+ 2 |
+ 3 |
+ 4 |
+ 5 |
+ 6 |
+ 7 |
+ 8 |
+ 9 |
+(10 rows)
+
+select ten, sum(distinct four) filter (where four > 10) from onek a
+group by ten
+having exists (select 1 from onek b where sum(distinct a.four) = b.four);
+ ten | sum
+-----+-----
+ 0 |
+ 2 |
+ 4 |
+ 6 |
+ 8 |
+(5 rows)
+
+select max(foo COLLATE "C") filter (where (bar collate "POSIX") > '0') from (values ('a', 'b')) AS v(foo,bar);
+ max
+-----
+ a
+(1 row)
+
+-- outer-level aggregates
+select
+ (select max((select i.unique2 from tenk1 i where i.unique1 = o.unique1)) filter (where o.unique1 < 10))
+from tenk1 o;
+ max
+------
+ 9998
+(1 row)
+
+-- non-standard-conforming FILTER clause containing subquery
+select sum(unique1) FILTER (WHERE unique1 IN (SELECT unique1 FROM onek where unique1 < 100)) FROM tenk1;
+ sum
+------
+ 4950
+(1 row)
+
+-- exercise lots of aggregate parts with FILTER
+select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1)
+ from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+ generate_series(1,2) i;
+ aggfns
+---------------------------
+ {"(2,2,bar)","(3,1,baz)"}
+(1 row)
+
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index ecc1c2c..7b31d13 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -1020,5 +1020,18 @@ SELECT ntile(0) OVER (ORDER BY ten), ten, four FROM tenk1;
ERROR: argument of ntile must be greater than zero
SELECT nth_value(four, 0) OVER (ORDER BY ten), ten, four FROM tenk1;
ERROR: argument of nth_value must be greater than zero
+-- filter
+SELECT sum(salary), row_number() OVER (ORDER BY depname), sum(
+ sum(salary) FILTER (WHERE enroll_date > '2007-01-01')
+) FILTER (WHERE depname <> 'sales') OVER (ORDER BY depname DESC) AS "filtered_sum",
+ depname
+FROM empsalary GROUP BY depname;
+ sum | row_number | filtered_sum | depname
+-------+------------+--------------+-----------
+ 14600 | 3 | | sales
+ 7400 | 2 | 3500 | personnel
+ 25100 | 1 | 22600 | develop
+(3 rows)
+
-- cleanup
DROP TABLE empsalary;
diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql
index 38d4757..da0bd65 100644
--- a/src/test/regress/sql/aggregates.sql
+++ b/src/test/regress/sql/aggregates.sql
@@ -442,3 +442,31 @@ select string_agg(v, NULL) from bytea_test_table;
select string_agg(v, decode('ee', 'hex')) from bytea_test_table;
drop table bytea_test_table;
+
+-- FILTER tests
+
+select min(unique1) filter (where unique1 > 100) from tenk1;
+
+select ten, sum(distinct four) filter (where four::text ~ '123') from onek a
+group by ten;
+
+select ten, sum(distinct four) filter (where four > 10) from onek a
+group by ten
+having exists (select 1 from onek b where sum(distinct a.four) = b.four);
+
+select max(foo COLLATE "C") filter (where (bar collate "POSIX") > '0') from (values ('a', 'b')) AS v(foo,bar);
+
+-- outer-level aggregates
+select
+ (select max((select i.unique2 from tenk1 i where i.unique1 = o.unique1)) filter (where o.unique1 < 10))
+from tenk1 o;
+
+-- non-standard-conforming FILTER clause containing subquery
+
+select sum(unique1) FILTER (WHERE unique1 IN (SELECT unique1 FROM onek where unique1 < 100)) FROM tenk1;
+
+-- exercise lots of aggregate parts with FILTER
+
+select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1)
+ from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+ generate_series(1,2) i;
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 769be0f..6ee3696 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -264,5 +264,13 @@ SELECT ntile(0) OVER (ORDER BY ten), ten, four FROM tenk1;
SELECT nth_value(four, 0) OVER (ORDER BY ten), ten, four FROM tenk1;
+-- filter
+
+SELECT sum(salary), row_number() OVER (ORDER BY depname), sum(
+ sum(salary) FILTER (WHERE enroll_date > '2007-01-01')
+) FILTER (WHERE depname <> 'sales') OVER (ORDER BY depname DESC) AS "filtered_sum",
+ depname
+FROM empsalary GROUP BY depname;
+
-- cleanup
DROP TABLE empsalary;
On Sat, Jul 06, 2013 at 11:49:21AM +0100, Dean Rasheed wrote:
On 5 July 2013 18:23, David Fetter <david@fetter.org> wrote:
Please find attached changes based on the above.
This looks good. The grammar changes are smaller and neater now on top
of the makeFuncCall() patch.Overall I think this patch offers useful additional functionality, in
compliance with the SQL spec, which should be handy to simplify
complex grouping queries.There's a minor typo in syntax.sgml: "for each input row, each row
matching same." --- fix attached.
That is actually correct grammar, but may not be the easiest to
translate.
I think this is ready for committer.
Thanks :)
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sun, Jul 07, 2013 at 06:37:26PM -0700, David Fetter wrote:
On Sat, Jul 06, 2013 at 11:49:21AM +0100, Dean Rasheed wrote:
Overall I think this patch offers useful additional functionality, in
compliance with the SQL spec, which should be handy to simplify
complex grouping queries.
As I understand this feature, it is syntactic sugar for the typical case of an
aggregate with a strict transition function. For example, "min(x) FILTER
(WHERE y > 0)" is rigorously equivalent to "min(CASE y > 0 THEN x END)".
Every SQL aggregate is strict (ISO 9075-2 section 4.15.4), so for standard
queries it is *only* syntactic sugar. In PostgreSQL, it lets you do novel
things with, for example, array_agg(). Is that accurate?
I think this is ready for committer.
The patch was thorough. I updated applicable comments, revisited some
cosmetic choices, and made these functional changes:
1. The pg_stat_statements "query jumble" should incorporate the filter.
2. The patch did not update costing. I made it add the cost of the filter
expression the same way we add the cost of the argument expressions. This
makes "min(x) FILTER (WHERE y > 0)" match "min(case y > 0 THEN x end)" in
terms of cost, which is a fair start. At some point, we could do better by
reducing the argument cost by the filter selectivity.
A few choices/standard interpretations may deserve discussion. The patch
makes filter clauses contribute to the subquery level chosen to be the
"aggregation query". This is observable through the behavior of these two
standard-conforming queries:
select (select count(outer_c)
from (values (1)) t0(inner_c))
from (values (2),(3)) t1(outer_c); -- outer query is aggregation query
select (select count(outer_c) filter (where inner_c <> 0)
from (values (1)) t0(inner_c))
from (values (2),(3)) t1(outer_c); -- inner query is aggregation query
I believe SQL (ISO 9075-2 section 6.9 SR 3,4,6) does require this. Since that
still isn't crystal-clear to me, I mention it in case anyone has a different
reading.
Distinct from that, albeit in a similar vein, SQL does not permit outer
references in a filter clause. This patch permits them; I think that
qualifies as a reasonable PostgreSQL extension.
--- a/doc/src/sgml/keywords.sgml +++ b/doc/src/sgml/keywords.sgml
@@ -3200,7 +3200,7 @@ </row> <row> <entry><token>OVER</token></entry> - <entry>reserved (can be function or type)</entry> + <entry>non-reserved</entry>
I committed this one-line correction separately.
--- a/src/backend/optimizer/plan/planagg.c +++ b/src/backend/optimizer/plan/planagg.c @@ -314,7 +314,7 @@ find_minmax_aggs_walker(Node *node, List **context) ListCell *l;Assert(aggref->agglevelsup == 0); - if (list_length(aggref->args) != 1 || aggref->aggorder != NIL) + if (list_length(aggref->args) != 1 || aggref->aggorder != NIL || aggref->agg_filter != NULL) return true; /* it couldn't be MIN/MAX */ /* note: we do not care if DISTINCT is mentioned ... */
I twitched upon reading this, because neither ORDER BY nor FILTER preclude the
aggregate being MIN or MAX. Perhaps Andrew can explain why he put aggorder
there back in 2009. All I can figure is that writing max(c ORDER BY x) is so
unlikely that we'd too often waste the next syscache lookup. But the same
argument would apply to DISTINCT. With FILTER, the rationale is entirely
different. The aggregate could well be MIN/MAX; we just haven't implemented
the necessary support elsewhere in this file.
See attached patch revisions. The first patch edits find_minmax_aggs_walker()
per my comments just now. The second is an update of your FILTER patch with
the changes to which I alluded above; it applies atop the first patch. Would
you verify that I didn't ruin anything? Barring problems, will commit.
Are you the sole named author of this patch? That's what the CF page says,
but that wasn't fully clear to me from the list discussions.
Thanks,
nm
--
Noah Misch
EnterpriseDB http://www.enterprisedb.com
Attachments:
minmax-ignore-orderby-v1.patchtext/plain; charset=us-asciiDownload
*** a/src/backend/optimizer/plan/planagg.c
--- b/src/backend/optimizer/plan/planagg.c
***************
*** 314,328 **** find_minmax_aggs_walker(Node *node, List **context)
ListCell *l;
Assert(aggref->agglevelsup == 0);
! if (list_length(aggref->args) != 1 || aggref->aggorder != NIL)
return true; /* it couldn't be MIN/MAX */
! /* note: we do not care if DISTINCT is mentioned ... */
! curTarget = (TargetEntry *) linitial(aggref->args);
aggsortop = fetch_agg_sort_op(aggref->aggfnoid);
if (!OidIsValid(aggsortop))
return true; /* not a MIN/MAX aggregate */
if (contain_mutable_functions((Node *) curTarget->expr))
return true; /* not potentially indexable */
--- 314,333 ----
ListCell *l;
Assert(aggref->agglevelsup == 0);
! if (list_length(aggref->args) != 1)
return true; /* it couldn't be MIN/MAX */
!
! /*
! * Ignore ORDER BY and DISTINCT, which are valid but pointless on
! * MIN/MAX. They do not change its result.
! */
aggsortop = fetch_agg_sort_op(aggref->aggfnoid);
if (!OidIsValid(aggsortop))
return true; /* not a MIN/MAX aggregate */
+ curTarget = (TargetEntry *) linitial(aggref->args);
+
if (contain_mutable_functions((Node *) curTarget->expr))
return true; /* not potentially indexable */
filter-v9.patchtext/plain; charset=us-asciiDownload
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index a6ceaf4..ea930af 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -1546,6 +1546,7 @@ JumbleExpr(pgssJumbleState *jstate, Node *node)
JumbleExpr(jstate, (Node *) expr->args);
JumbleExpr(jstate, (Node *) expr->aggorder);
JumbleExpr(jstate, (Node *) expr->aggdistinct);
+ JumbleExpr(jstate, (Node *) expr->aggfilter);
}
break;
case T_WindowFunc:
@@ -1555,6 +1556,7 @@ JumbleExpr(pgssJumbleState *jstate, Node *node)
APP_JUMB(expr->winfnoid);
APP_JUMB(expr->winref);
JumbleExpr(jstate, (Node *) expr->args);
+ JumbleExpr(jstate, (Node *) expr->aggfilter);
}
break;
case T_ArrayRef:
diff --git a/doc/src/sgml/keywords.sgml b/doc/src/sgml/keywords.sgml
index 059c8e4..ecfde99 100644
--- a/doc/src/sgml/keywords.sgml
+++ b/doc/src/sgml/keywords.sgml
@@ -1786,7 +1786,7 @@
</row>
<row>
<entry><token>FILTER</token></entry>
- <entry></entry>
+ <entry>non-reserved</entry>
<entry>reserved</entry>
<entry>reserved</entry>
<entry></entry>
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 68309ba..b0cec14 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -598,6 +598,11 @@ GROUP BY <replaceable class="parameter">expression</replaceable> [, ...]
making up each group, producing a separate value for each group
(whereas without <literal>GROUP BY</literal>, an aggregate
produces a single value computed across all the selected rows).
+ The set of rows fed to the aggregate function can be further filtered by
+ attaching a <literal>FILTER</literal> clause to the aggregate function
+ call; see <xref linkend="syntax-aggregates"> for more information. When
+ a <literal>FILTER</literal> clause is present, only those rows matching it
+ are included.
When <literal>GROUP BY</literal> is present, it is not valid for
the <command>SELECT</command> list expressions to refer to
ungrouped columns except within aggregate functions or if the
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index b139212..803ed85 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -1554,6 +1554,10 @@ sqrt(2)
<secondary>invocation</secondary>
</indexterm>
+ <indexterm zone="syntax-aggregates">
+ <primary>filter</primary>
+ </indexterm>
+
<para>
An <firstterm>aggregate expression</firstterm> represents the
application of an aggregate function across the rows selected by a
@@ -1562,19 +1566,19 @@ sqrt(2)
syntax of an aggregate expression is one of the following:
<synopsis>
-<replaceable>aggregate_name</replaceable> (<replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] )
-<replaceable>aggregate_name</replaceable> (ALL <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] )
-<replaceable>aggregate_name</replaceable> (DISTINCT <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] )
-<replaceable>aggregate_name</replaceable> ( * )
+<replaceable>aggregate_name</replaceable> (<replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
+<replaceable>aggregate_name</replaceable> (ALL <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
+<replaceable>aggregate_name</replaceable> (DISTINCT <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
+<replaceable>aggregate_name</replaceable> ( * ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
</synopsis>
where <replaceable>aggregate_name</replaceable> is a previously
- defined aggregate (possibly qualified with a schema name),
+ defined aggregate (possibly qualified with a schema name) and
<replaceable>expression</replaceable> is
any value expression that does not itself contain an aggregate
- expression or a window function call, and
- <replaceable>order_by_clause</replaceable> is a optional
- <literal>ORDER BY</> clause as described below.
+ expression or a window function call. The optional
+ <replaceable>order_by_clause</replaceable> and
+ <replaceable>filter_clause</replaceable> are described below.
</para>
<para>
@@ -1607,6 +1611,23 @@ sqrt(2)
</para>
<para>
+ If <literal>FILTER</literal> is specified, then only the input
+ rows for which the <replaceable>filter_clause</replaceable>
+ evaluates to true are fed to the aggregate function; other rows
+ are discarded. For example:
+<programlisting>
+SELECT
+ count(*) AS unfiltered,
+ count(*) FILTER (WHERE i < 5) AS filtered
+FROM generate_series(1,10) AS s(i);
+ unfiltered | filtered
+------------+----------
+ 10 | 4
+(1 row)
+</programlisting>
+ </para>
+
+ <para>
Ordinarily, the input rows are fed to the aggregate function in an
unspecified order. In many cases this does not matter; for example,
<function>min</> produces the same result no matter what order it
@@ -1709,10 +1730,10 @@ SELECT string_agg(a ORDER BY a, ',') FROM table; -- incorrect
The syntax of a window function call is one of the following:
<synopsis>
-<replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) OVER ( <replaceable class="parameter">window_definition</replaceable> )
-<replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) OVER <replaceable>window_name</replaceable>
-<replaceable>function_name</replaceable> ( * ) OVER ( <replaceable class="parameter">window_definition</replaceable> )
-<replaceable>function_name</replaceable> ( * ) OVER <replaceable>window_name</replaceable>
+<replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER ( <replaceable class="parameter">window_definition</replaceable> )
+<replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER <replaceable>window_name</replaceable>
+<replaceable>function_name</replaceable> ( * ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER ( <replaceable class="parameter">window_definition</replaceable> )
+<replaceable>function_name</replaceable> ( * ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER <replaceable>window_name</replaceable>
</synopsis>
where <replaceable class="parameter">window_definition</replaceable>
has the syntax
@@ -1836,7 +1857,8 @@ UNBOUNDED FOLLOWING
The built-in window functions are described in <xref
linkend="functions-window-table">. Other window functions can be added by
the user. Also, any built-in or user-defined aggregate function can be
- used as a window function.
+ used as a window function. Only aggregate window functions accept
+ a <literal>FILTER</literal> clause.
</para>
<para>
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 1388183..90c2753 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -4410,6 +4410,8 @@ ExecInitExpr(Expr *node, PlanState *parent)
astate->args = (List *) ExecInitExpr((Expr *) aggref->args,
parent);
+ astate->aggfilter = ExecInitExpr(aggref->aggfilter,
+ parent);
/*
* Complain if the aggregate's arguments contain any
@@ -4448,6 +4450,8 @@ ExecInitExpr(Expr *node, PlanState *parent)
wfstate->args = (List *) ExecInitExpr((Expr *) wfunc->args,
parent);
+ wfstate->aggfilter = ExecInitExpr(wfunc->aggfilter,
+ parent);
/*
* Complain if the windowfunc's arguments contain any
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index cf7fb72..b449e0a 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -649,9 +649,9 @@ get_last_attnums(Node *node, ProjectionInfo *projInfo)
}
/*
- * Don't examine the arguments of Aggrefs or WindowFuncs, because those do
- * not represent expressions to be evaluated within the overall
- * targetlist's econtext.
+ * Don't examine the arguments or filters of Aggrefs or WindowFuncs,
+ * because those do not represent expressions to be evaluated within the
+ * overall targetlist's econtext.
*/
if (IsA(node, Aggref))
return false;
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 12e1b8e..ff6a123 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -380,7 +380,7 @@ sql_fn_post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var)
param = ParseFuncOrColumn(pstate,
list_make1(subfield),
list_make1(param),
- NIL, false, false, false,
+ NIL, NULL, false, false, false,
NULL, true, cref->location);
}
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index c741131..7a0c254 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -484,10 +484,23 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
{
AggStatePerAgg peraggstate = &aggstate->peragg[aggno];
AggStatePerGroup pergroupstate = &pergroup[aggno];
+ ExprState *filter = peraggstate->aggrefstate->aggfilter;
int nargs = peraggstate->numArguments;
int i;
TupleTableSlot *slot;
+ /* Skip anything FILTERed out */
+ if (filter)
+ {
+ bool isnull;
+ Datum res;
+
+ res = ExecEvalExprSwitchContext(filter, aggstate->tmpcontext,
+ &isnull, NULL);
+ if (isnull || !DatumGetBool(res))
+ continue;
+ }
+
/* Evaluate the current input expressions for this aggregate */
slot = ExecProject(peraggstate->evalproj, NULL);
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index d9f0e79..bbc5336 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -227,9 +227,23 @@ advance_windowaggregate(WindowAggState *winstate,
int i;
MemoryContext oldContext;
ExprContext *econtext = winstate->tmpcontext;
+ ExprState *filter = wfuncstate->aggfilter;
oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+ /* Skip anything FILTERed out */
+ if (filter)
+ {
+ bool isnull;
+ Datum res = ExecEvalExpr(filter, econtext, &isnull, NULL);
+
+ if (isnull || !DatumGetBool(res))
+ {
+ MemoryContextSwitchTo(oldContext);
+ return;
+ }
+ }
+
/* We start from 1, since the 0th arg will be the transition value */
i = 1;
foreach(arg, wfuncstate->args)
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index b5b8d63..55bdba2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1137,6 +1137,7 @@ _copyAggref(const Aggref *from)
COPY_NODE_FIELD(args);
COPY_NODE_FIELD(aggorder);
COPY_NODE_FIELD(aggdistinct);
+ COPY_NODE_FIELD(aggfilter);
COPY_SCALAR_FIELD(aggstar);
COPY_SCALAR_FIELD(agglevelsup);
COPY_LOCATION_FIELD(location);
@@ -1157,6 +1158,7 @@ _copyWindowFunc(const WindowFunc *from)
COPY_SCALAR_FIELD(wincollid);
COPY_SCALAR_FIELD(inputcollid);
COPY_NODE_FIELD(args);
+ COPY_NODE_FIELD(aggfilter);
COPY_SCALAR_FIELD(winref);
COPY_SCALAR_FIELD(winstar);
COPY_SCALAR_FIELD(winagg);
@@ -2152,6 +2154,7 @@ _copyFuncCall(const FuncCall *from)
COPY_NODE_FIELD(funcname);
COPY_NODE_FIELD(args);
COPY_NODE_FIELD(agg_order);
+ COPY_NODE_FIELD(agg_filter);
COPY_SCALAR_FIELD(agg_star);
COPY_SCALAR_FIELD(agg_distinct);
COPY_SCALAR_FIELD(func_variadic);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 3f96595..79384e7 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -196,6 +196,7 @@ _equalAggref(const Aggref *a, const Aggref *b)
COMPARE_NODE_FIELD(args);
COMPARE_NODE_FIELD(aggorder);
COMPARE_NODE_FIELD(aggdistinct);
+ COMPARE_NODE_FIELD(aggfilter);
COMPARE_SCALAR_FIELD(aggstar);
COMPARE_SCALAR_FIELD(agglevelsup);
COMPARE_LOCATION_FIELD(location);
@@ -211,6 +212,7 @@ _equalWindowFunc(const WindowFunc *a, const WindowFunc *b)
COMPARE_SCALAR_FIELD(wincollid);
COMPARE_SCALAR_FIELD(inputcollid);
COMPARE_NODE_FIELD(args);
+ COMPARE_NODE_FIELD(aggfilter);
COMPARE_SCALAR_FIELD(winref);
COMPARE_SCALAR_FIELD(winstar);
COMPARE_SCALAR_FIELD(winagg);
@@ -1992,6 +1994,7 @@ _equalFuncCall(const FuncCall *a, const FuncCall *b)
COMPARE_NODE_FIELD(funcname);
COMPARE_NODE_FIELD(args);
COMPARE_NODE_FIELD(agg_order);
+ COMPARE_NODE_FIELD(agg_filter);
COMPARE_SCALAR_FIELD(agg_star);
COMPARE_SCALAR_FIELD(agg_distinct);
COMPARE_SCALAR_FIELD(func_variadic);
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 245aef2..0f8a282 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -526,6 +526,7 @@ makeFuncCall(List *name, List *args, int location)
n->args = args;
n->location = location;
n->agg_order = NIL;
+ n->agg_filter = NULL;
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 42d6621..310400e 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1570,6 +1570,8 @@ expression_tree_walker(Node *node,
if (expression_tree_walker((Node *) expr->aggdistinct,
walker, context))
return true;
+ if (walker((Node *) expr->aggfilter, context))
+ return true;
}
break;
case T_WindowFunc:
@@ -1580,6 +1582,8 @@ expression_tree_walker(Node *node,
if (expression_tree_walker((Node *) expr->args,
walker, context))
return true;
+ if (walker((Node *) expr->aggfilter, context))
+ return true;
}
break;
case T_ArrayRef:
@@ -2079,6 +2083,7 @@ expression_tree_mutator(Node *node,
MUTATE(newnode->args, aggref->args, List *);
MUTATE(newnode->aggorder, aggref->aggorder, List *);
MUTATE(newnode->aggdistinct, aggref->aggdistinct, List *);
+ MUTATE(newnode->aggfilter, aggref->aggfilter, Expr *);
return (Node *) newnode;
}
break;
@@ -2089,6 +2094,7 @@ expression_tree_mutator(Node *node,
FLATCOPY(newnode, wfunc, WindowFunc);
MUTATE(newnode->args, wfunc->args, List *);
+ MUTATE(newnode->aggfilter, wfunc->aggfilter, Expr *);
return (Node *) newnode;
}
break;
@@ -2951,6 +2957,8 @@ raw_expression_tree_walker(Node *node,
return true;
if (walker(fcall->agg_order, context))
return true;
+ if (walker(fcall->agg_filter, context))
+ return true;
if (walker(fcall->over, context))
return true;
/* function name is deemed uninteresting */
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b2183f4..2475f8d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -958,6 +958,7 @@ _outAggref(StringInfo str, const Aggref *node)
WRITE_NODE_FIELD(args);
WRITE_NODE_FIELD(aggorder);
WRITE_NODE_FIELD(aggdistinct);
+ WRITE_NODE_FIELD(aggfilter);
WRITE_BOOL_FIELD(aggstar);
WRITE_UINT_FIELD(agglevelsup);
WRITE_LOCATION_FIELD(location);
@@ -973,6 +974,7 @@ _outWindowFunc(StringInfo str, const WindowFunc *node)
WRITE_OID_FIELD(wincollid);
WRITE_OID_FIELD(inputcollid);
WRITE_NODE_FIELD(args);
+ WRITE_NODE_FIELD(aggfilter);
WRITE_UINT_FIELD(winref);
WRITE_BOOL_FIELD(winstar);
WRITE_BOOL_FIELD(winagg);
@@ -2080,6 +2082,7 @@ _outFuncCall(StringInfo str, const FuncCall *node)
WRITE_NODE_FIELD(funcname);
WRITE_NODE_FIELD(args);
WRITE_NODE_FIELD(agg_order);
+ WRITE_NODE_FIELD(agg_filter);
WRITE_BOOL_FIELD(agg_star);
WRITE_BOOL_FIELD(agg_distinct);
WRITE_BOOL_FIELD(func_variadic);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3a16e9d..30c5150 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -479,6 +479,7 @@ _readAggref(void)
READ_NODE_FIELD(args);
READ_NODE_FIELD(aggorder);
READ_NODE_FIELD(aggdistinct);
+ READ_NODE_FIELD(aggfilter);
READ_BOOL_FIELD(aggstar);
READ_UINT_FIELD(agglevelsup);
READ_LOCATION_FIELD(location);
@@ -499,6 +500,7 @@ _readWindowFunc(void)
READ_OID_FIELD(wincollid);
READ_OID_FIELD(inputcollid);
READ_NODE_FIELD(args);
+ READ_NODE_FIELD(aggfilter);
READ_UINT_FIELD(winref);
READ_BOOL_FIELD(winstar);
READ_BOOL_FIELD(winagg);
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 3507f18..1732d71 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -1590,6 +1590,14 @@ cost_windowagg(Path *path, PlannerInfo *root,
startup_cost += argcosts.startup;
wfunccost += argcosts.per_tuple;
+ /*
+ * Add the filter's cost to per-input-row costs. XXX We should reduce
+ * input expression costs according to filter selectivity.
+ */
+ cost_qual_eval_node(&argcosts, (Node *) wfunc->aggfilter, root);
+ startup_cost += argcosts.startup;
+ wfunccost += argcosts.per_tuple;
+
total_cost += wfunccost * input_tuples;
}
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
index 243aeb3..2f6fc4a 100644
--- a/src/backend/optimizer/plan/planagg.c
+++ b/src/backend/optimizer/plan/planagg.c
@@ -318,6 +318,13 @@ find_minmax_aggs_walker(Node *node, List **context)
return true; /* it couldn't be MIN/MAX */
/*
+ * We might implement the optimization when a FILTER clause is present
+ * by adding the filter to the quals of the generated subquery.
+ */
+ if (aggref->aggfilter != NULL)
+ return true;
+
+ /*
* Ignore ORDER BY and DISTINCT, which are valid but pointless on
* MIN/MAX. They do not change its result.
*/
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 6d5b204..7ec6b0b 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -495,6 +495,15 @@ count_agg_clauses_walker(Node *node, count_agg_clauses_context *context)
costs->transCost.startup += argcosts.startup;
costs->transCost.per_tuple += argcosts.per_tuple;
+ /*
+ * Add the filter's cost to per-input-row costs. XXX We should reduce
+ * input expression costs according to filter selectivity.
+ */
+ cost_qual_eval_node(&argcosts, (Node *) aggref->aggfilter,
+ context->root);
+ costs->transCost.startup += argcosts.startup;
+ costs->transCost.per_tuple += argcosts.per_tuple;
+
/* extract argument types (ignoring any ORDER BY expressions) */
inputTypes = (Oid *) palloc(sizeof(Oid) * list_length(aggref->args));
numArguments = 0;
@@ -565,7 +574,8 @@ count_agg_clauses_walker(Node *node, count_agg_clauses_context *context)
/*
* Complain if the aggregate's arguments contain any aggregates;
- * nested agg functions are semantically nonsensical.
+ * nested agg functions are semantically nonsensical. Aggregates in
+ * the FILTER clause are detected in transformAggregateCall().
*/
if (contain_agg_clause((Node *) aggref->args))
ereport(ERROR,
@@ -639,7 +649,8 @@ find_window_functions_walker(Node *node, WindowFuncLists *lists)
/*
* Complain if the window function's arguments contain window
- * functions
+ * functions. Window functions in the FILTER clause are detected in
+ * transformAggregateCall().
*/
if (contain_window_function((Node *) wfunc->args))
ereport(ERROR,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index f67ef0c..5ff3e70 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -492,6 +492,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
opt_frame_clause frame_extent frame_bound
%type <str> opt_existing_window_name
%type <boolean> opt_if_not_exists
+%type <node> filter_clause
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -538,8 +539,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
EXTENSION EXTERNAL EXTRACT
- FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
- FREEZE FROM FULL FUNCTION FUNCTIONS
+ FALSE_P FAMILY FETCH FILTER FIRST_P FLOAT_P FOLLOWING FOR
+ FORCE FOREIGN FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS
GLOBAL GRANT GRANTED GREATEST GROUP_P
@@ -11111,10 +11112,11 @@ func_application: func_name '(' ')'
* (Note that many of the special SQL functions wouldn't actually make any
* sense as functional index entries, but we ignore that consideration here.)
*/
-func_expr: func_application over_clause
+func_expr: func_application filter_clause over_clause
{
FuncCall *n = (FuncCall*)$1;
- n->over = $2;
+ n->agg_filter = $2;
+ n->over = $3;
$$ = (Node*)n;
}
| func_expr_common_subexpr
@@ -11525,6 +11527,11 @@ window_definition:
}
;
+filter_clause:
+ FILTER '(' WHERE a_expr ')' { $$ = $4; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
over_clause: OVER window_specification
{ $$ = $2; }
| OVER ColId
@@ -12499,6 +12506,7 @@ unreserved_keyword:
| EXTENSION
| EXTERNAL
| FAMILY
+ | FILTER
| FIRST_P
| FOLLOWING
| FORCE
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 7380618..4e4e1cd 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -44,7 +44,7 @@ typedef struct
int sublevels_up;
} check_ungrouped_columns_context;
-static int check_agg_arguments(ParseState *pstate, List *args);
+static int check_agg_arguments(ParseState *pstate, List *args, Expr *filter);
static bool check_agg_arguments_walker(Node *node,
check_agg_arguments_context *context);
static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
@@ -160,7 +160,7 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
* Check the arguments to compute the aggregate's level and detect
* improper nesting.
*/
- min_varlevel = check_agg_arguments(pstate, agg->args);
+ min_varlevel = check_agg_arguments(pstate, agg->args, agg->aggfilter);
agg->agglevelsup = min_varlevel;
/* Mark the correct pstate level as having aggregates */
@@ -207,6 +207,9 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
case EXPR_KIND_HAVING:
/* okay */
break;
+ case EXPR_KIND_FILTER:
+ errkind = true;
+ break;
case EXPR_KIND_WINDOW_PARTITION:
/* okay */
break;
@@ -299,8 +302,8 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
* one is its parent, etc).
*
* The aggregate's level is the same as the level of the lowest-level variable
- * or aggregate in its arguments; or if it contains no variables at all, we
- * presume it to be local.
+ * or aggregate in its arguments or filter expression; or if it contains no
+ * variables at all, we presume it to be local.
*
* We also take this opportunity to detect any aggregates or window functions
* nested within the arguments. We can throw error immediately if we find
@@ -309,7 +312,7 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
* which we can't know until we finish scanning the arguments.
*/
static int
-check_agg_arguments(ParseState *pstate, List *args)
+check_agg_arguments(ParseState *pstate, List *args, Expr *filter)
{
int agglevel;
check_agg_arguments_context context;
@@ -323,6 +326,10 @@ check_agg_arguments(ParseState *pstate, List *args)
check_agg_arguments_walker,
(void *) &context);
+ (void) expression_tree_walker((Node *) filter,
+ check_agg_arguments_walker,
+ (void *) &context);
+
/*
* If we found no vars nor aggs at all, it's a level-zero aggregate;
* otherwise, its level is the minimum of vars or aggs.
@@ -481,6 +488,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_HAVING:
errkind = true;
break;
+ case EXPR_KIND_FILTER:
+ errkind = true;
+ break;
case EXPR_KIND_WINDOW_PARTITION:
case EXPR_KIND_WINDOW_ORDER:
case EXPR_KIND_WINDOW_FRAME_RANGE:
@@ -807,11 +817,10 @@ check_ungrouped_columns_walker(Node *node,
/*
* If we find an aggregate call of the original level, do not recurse into
- * its arguments; ungrouped vars in the arguments are not an error. We can
- * also skip looking at the arguments of aggregates of higher levels,
- * since they could not possibly contain Vars that are of concern to us
- * (see transformAggregateCall). We do need to look into the arguments of
- * aggregates of lower levels, however.
+ * its arguments or filter; ungrouped vars there are not an error. We can
+ * also skip looking at aggregates of higher levels, since they could not
+ * possibly contain Vars of concern to us (see transformAggregateCall).
+ * We do need to look at aggregates of lower levels, however.
*/
if (IsA(node, Aggref) &&
(int) ((Aggref *) node)->agglevelsup >= context->sublevels_up)
diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c
index 80f6ac7..fe57c59 100644
--- a/src/backend/parser/parse_collate.c
+++ b/src/backend/parser/parse_collate.c
@@ -575,6 +575,10 @@ assign_collations_walker(Node *node, assign_collations_context *context)
* the case above for T_TargetEntry will apply
* appropriate checks to agg ORDER BY items.
*
+ * Likewise, we assign collations for the (bool)
+ * expression in aggfilter, independently of any
+ * other args.
+ *
* We need not recurse into the aggorder or
* aggdistinct lists, because those contain only
* SortGroupClause nodes which we need not
@@ -595,6 +599,24 @@ assign_collations_walker(Node *node, assign_collations_context *context)
(void) assign_collations_walker((Node *) tle,
&loccontext);
}
+
+ assign_expr_collations(context->pstate,
+ (Node *) aggref->aggfilter);
+ }
+ break;
+ case T_WindowFunc:
+ {
+ /*
+ * WindowFunc requires special processing only for
+ * its aggfilter clause, as for aggregates.
+ */
+ WindowFunc *wfunc = (WindowFunc *) node;
+
+ (void) assign_collations_walker((Node *) wfunc->args,
+ &loccontext);
+
+ assign_expr_collations(context->pstate,
+ (Node *) wfunc->aggfilter);
}
break;
case T_CaseExpr:
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 06f6512..68b711d 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -22,6 +22,7 @@
#include "nodes/nodeFuncs.h"
#include "optimizer/var.h"
#include "parser/analyze.h"
+#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
@@ -462,7 +463,7 @@ transformIndirection(ParseState *pstate, Node *basenode, List *indirection)
newresult = ParseFuncOrColumn(pstate,
list_make1(n),
list_make1(result),
- NIL, false, false, false,
+ NIL, NULL, false, false, false,
NULL, true, location);
if (newresult == NULL)
unknown_attribute(pstate, result, strVal(n), location);
@@ -630,7 +631,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
node = ParseFuncOrColumn(pstate,
list_make1(makeString(colname)),
list_make1(node),
- NIL, false, false, false,
+ NIL, NULL, false, false, false,
NULL, true, cref->location);
}
break;
@@ -675,7 +676,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
node = ParseFuncOrColumn(pstate,
list_make1(makeString(colname)),
list_make1(node),
- NIL, false, false, false,
+ NIL, NULL, false, false, false,
NULL, true, cref->location);
}
break;
@@ -733,7 +734,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
node = ParseFuncOrColumn(pstate,
list_make1(makeString(colname)),
list_make1(node),
- NIL, false, false, false,
+ NIL, NULL, false, false, false,
NULL, true, cref->location);
}
break;
@@ -1241,6 +1242,7 @@ transformFuncCall(ParseState *pstate, FuncCall *fn)
{
List *targs;
ListCell *args;
+ Expr *tagg_filter;
/* Transform the list of arguments ... */
targs = NIL;
@@ -1250,11 +1252,22 @@ transformFuncCall(ParseState *pstate, FuncCall *fn)
(Node *) lfirst(args)));
}
+ /*
+ * Transform the aggregate filter using transformWhereClause(), to which
+ * FILTER is virtually identical...
+ */
+ tagg_filter = NULL;
+ if (fn->agg_filter != NULL)
+ tagg_filter = (Expr *)
+ transformWhereClause(pstate, (Node *) fn->agg_filter,
+ EXPR_KIND_FILTER, "FILTER");
+
/* ... and hand off to ParseFuncOrColumn */
return ParseFuncOrColumn(pstate,
fn->funcname,
targs,
fn->agg_order,
+ tagg_filter,
fn->agg_star,
fn->agg_distinct,
fn->func_variadic,
@@ -1430,6 +1443,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_FROM_FUNCTION:
case EXPR_KIND_WHERE:
case EXPR_KIND_HAVING:
+ case EXPR_KIND_FILTER:
case EXPR_KIND_WINDOW_PARTITION:
case EXPR_KIND_WINDOW_ORDER:
case EXPR_KIND_WINDOW_FRAME_RANGE:
@@ -2579,6 +2593,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "WHERE";
case EXPR_KIND_HAVING:
return "HAVING";
+ case EXPR_KIND_FILTER:
+ return "FILTER";
case EXPR_KIND_WINDOW_PARTITION:
return "window PARTITION BY";
case EXPR_KIND_WINDOW_ORDER:
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index ae7d195..e54922f 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -56,13 +56,13 @@ static Node *ParseComplexProjection(ParseState *pstate, char *funcname,
* Also, when is_column is true, we return NULL on failure rather than
* reporting a no-such-function error.
*
- * The argument expressions (in fargs) must have been transformed already.
- * But the agg_order expressions, if any, have not been.
+ * The argument expressions (in fargs) and filter must have been transformed
+ * already. But the agg_order expressions, if any, have not been.
*/
Node *
ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
- List *agg_order, bool agg_star, bool agg_distinct,
- bool func_variadic,
+ List *agg_order, Expr *agg_filter,
+ bool agg_star, bool agg_distinct, bool func_variadic,
WindowDef *over, bool is_column, int location)
{
Oid rettype;
@@ -174,8 +174,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
* the "function call" could be a projection. We also check that there
* wasn't any aggregate or variadic decoration, nor an argument name.
*/
- if (nargs == 1 && agg_order == NIL && !agg_star && !agg_distinct &&
- over == NULL && !func_variadic && argnames == NIL &&
+ if (nargs == 1 && agg_order == NIL && agg_filter == NULL && !agg_star &&
+ !agg_distinct && over == NULL && !func_variadic && argnames == NIL &&
list_length(funcname) == 1)
{
Oid argtype = actual_arg_types[0];
@@ -251,6 +251,12 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
errmsg("ORDER BY specified, but %s is not an aggregate function",
NameListToString(funcname)),
parser_errposition(pstate, location)));
+ if (agg_filter)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("FILTER specified, but %s is not an aggregate function",
+ NameListToString(funcname)),
+ parser_errposition(pstate, location)));
if (over)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -402,6 +408,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
/* aggcollid and inputcollid will be set by parse_collate.c */
/* args, aggorder, aggdistinct will be set by transformAggregateCall */
aggref->aggstar = agg_star;
+ aggref->aggfilter = agg_filter;
/* agglevelsup will be set by transformAggregateCall */
aggref->location = location;
@@ -460,6 +467,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
/* winref will be set by transformWindowFuncCall */
wfunc->winstar = agg_star;
wfunc->winagg = (fdresult == FUNCDETAIL_AGGREGATE);
+ wfunc->aggfilter = agg_filter;
wfunc->location = location;
/*
@@ -483,6 +491,16 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
parser_errposition(pstate, location)));
/*
+ * Reject window functions which are not aggregates in the case of
+ * FILTER.
+ */
+ if (!wfunc->winagg && agg_filter)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("FILTER is not implemented in non-aggregate window functions"),
+ parser_errposition(pstate, location)));
+
+ /*
* ordered aggs not allowed in windows yet
*/
if (agg_order != NIL)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index cf9ce3f..976bc98 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -7424,6 +7424,13 @@ get_agg_expr(Aggref *aggref, deparse_context *context)
appendStringInfoString(buf, " ORDER BY ");
get_rule_orderby(aggref->aggorder, aggref->args, false, context);
}
+
+ if (aggref->aggfilter != NULL)
+ {
+ appendStringInfoString(buf, ") FILTER (WHERE ");
+ get_rule_expr((Node *) aggref->aggfilter, context, false);
+ }
+
appendStringInfoChar(buf, ')');
}
@@ -7461,6 +7468,13 @@ get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context)
appendStringInfoChar(buf, '*');
else
get_rule_expr((Node *) wfunc->args, context, true);
+
+ if (wfunc->aggfilter != NULL)
+ {
+ appendStringInfoString(buf, ") FILTER (WHERE ");
+ get_rule_expr((Node *) wfunc->aggfilter, context, false);
+ }
+
appendStringInfoString(buf, ") OVER ");
foreach(l, context->windowClause)
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 4f77016..5de5db7 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -584,6 +584,7 @@ typedef struct AggrefExprState
{
ExprState xprstate;
List *args; /* states of argument expressions */
+ ExprState *aggfilter; /* FILTER expression */
int aggno; /* ID number for agg within its plan node */
} AggrefExprState;
@@ -595,6 +596,7 @@ typedef struct WindowFuncExprState
{
ExprState xprstate;
List *args; /* states of argument expressions */
+ ExprState *aggfilter; /* FILTER expression */
int wfuncno; /* ID number for wfunc within its plan node */
} WindowFuncExprState;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index de22dff..fd6cb5a 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -283,8 +283,8 @@ typedef struct CollateClause
* agg_star indicates we saw a 'foo(*)' construct, while agg_distinct
* indicates we saw 'foo(DISTINCT ...)'. In any of these cases, the
* construct *must* be an aggregate call. Otherwise, it might be either an
- * aggregate or some other kind of function. However, if OVER is present
- * it had better be an aggregate or window function.
+ * aggregate or some other kind of function. However, if FILTER or OVER is
+ * present it had better be an aggregate or window function.
*
* Normally, you'd initialize this via makeFuncCall() and then only
* change the parts of the struct its defaults don't match afterwards
@@ -297,6 +297,7 @@ typedef struct FuncCall
List *funcname; /* qualified name of function */
List *args; /* the arguments (list of exprs) */
List *agg_order; /* ORDER BY (list of SortBy) */
+ Node *agg_filter; /* FILTER clause, if any */
bool agg_star; /* argument was really '*' */
bool agg_distinct; /* arguments were labeled DISTINCT */
bool func_variadic; /* last argument was labeled VARIADIC */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 75b716a..a778951 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -247,6 +247,7 @@ typedef struct Aggref
List *args; /* arguments and sort expressions */
List *aggorder; /* ORDER BY (list of SortGroupClause) */
List *aggdistinct; /* DISTINCT (list of SortGroupClause) */
+ Expr *aggfilter; /* FILTER expression */
bool aggstar; /* TRUE if argument list was really '*' */
Index agglevelsup; /* > 0 if agg belongs to outer query */
int location; /* token location, or -1 if unknown */
@@ -263,6 +264,7 @@ typedef struct WindowFunc
Oid wincollid; /* OID of collation of result */
Oid inputcollid; /* OID of collation that function should use */
List *args; /* arguments to the window function */
+ Expr *aggfilter; /* FILTER expression */
Index winref; /* index of associated WindowClause */
bool winstar; /* TRUE if argument list was really '*' */
bool winagg; /* is function a simple aggregate? */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index b3d72a9..287f78e 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -155,6 +155,7 @@ PG_KEYWORD("extract", EXTRACT, COL_NAME_KEYWORD)
PG_KEYWORD("false", FALSE_P, RESERVED_KEYWORD)
PG_KEYWORD("family", FAMILY, UNRESERVED_KEYWORD)
PG_KEYWORD("fetch", FETCH, RESERVED_KEYWORD)
+PG_KEYWORD("filter", FILTER, UNRESERVED_KEYWORD)
PG_KEYWORD("first", FIRST_P, UNRESERVED_KEYWORD)
PG_KEYWORD("float", FLOAT_P, COL_NAME_KEYWORD)
PG_KEYWORD("following", FOLLOWING, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_func.h b/src/include/parser/parse_func.h
index 6e09dc4..d63cb95 100644
--- a/src/include/parser/parse_func.h
+++ b/src/include/parser/parse_func.h
@@ -42,10 +42,9 @@ typedef enum
} FuncDetailCode;
-extern Node *ParseFuncOrColumn(ParseState *pstate,
- List *funcname, List *fargs,
- List *agg_order, bool agg_star, bool agg_distinct,
- bool func_variadic,
+extern Node *ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
+ List *agg_order, Expr *agg_filter,
+ bool agg_star, bool agg_distinct, bool func_variadic,
WindowDef *over, bool is_column, int location);
extern FuncDetailCode func_get_detail(List *funcname,
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 49ca764..bea3b07 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -39,6 +39,7 @@ typedef enum ParseExprKind
EXPR_KIND_FROM_FUNCTION, /* function in FROM clause */
EXPR_KIND_WHERE, /* WHERE */
EXPR_KIND_HAVING, /* HAVING */
+ EXPR_KIND_FILTER, /* FILTER */
EXPR_KIND_WINDOW_PARTITION, /* window definition PARTITION BY */
EXPR_KIND_WINDOW_ORDER, /* window definition ORDER BY */
EXPR_KIND_WINDOW_FRAME_RANGE, /* window frame clause with RANGE */
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index d379c0d..7fa9005 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1154,3 +1154,98 @@ select string_agg(v, decode('ee', 'hex')) from bytea_test_table;
(1 row)
drop table bytea_test_table;
+-- FILTER tests
+select min(unique1) filter (where unique1 > 100) from tenk1;
+ min
+-----
+ 101
+(1 row)
+
+select ten, sum(distinct four) filter (where four::text ~ '123') from onek a
+group by ten;
+ ten | sum
+-----+-----
+ 0 |
+ 1 |
+ 2 |
+ 3 |
+ 4 |
+ 5 |
+ 6 |
+ 7 |
+ 8 |
+ 9 |
+(10 rows)
+
+select ten, sum(distinct four) filter (where four > 10) from onek a
+group by ten
+having exists (select 1 from onek b where sum(distinct a.four) = b.four);
+ ten | sum
+-----+-----
+ 0 |
+ 2 |
+ 4 |
+ 6 |
+ 8 |
+(5 rows)
+
+select max(foo COLLATE "C") filter (where (bar collate "POSIX") > '0')
+from (values ('a', 'b')) AS v(foo,bar);
+ max
+-----
+ a
+(1 row)
+
+-- outer reference in FILTER (PostgreSQL extension)
+select (select count(*)
+ from (values (1)) t0(inner_c))
+from (values (2),(3)) t1(outer_c); -- inner query is aggregation query
+ count
+-------
+ 1
+ 1
+(2 rows)
+
+select (select count(*) filter (where outer_c <> 0)
+ from (values (1)) t0(inner_c))
+from (values (2),(3)) t1(outer_c); -- outer query is aggregation query
+ count
+-------
+ 2
+(1 row)
+
+select (select count(inner_c) filter (where outer_c <> 0)
+ from (values (1)) t0(inner_c))
+from (values (2),(3)) t1(outer_c); -- inner query is aggregation query
+ count
+-------
+ 1
+ 1
+(2 rows)
+
+select
+ (select max((select i.unique2 from tenk1 i where i.unique1 = o.unique1))
+ filter (where o.unique1 < 10))
+from tenk1 o; -- outer query is aggregation query
+ max
+------
+ 9998
+(1 row)
+
+-- subquery in FILTER clause (PostgreSQL extension)
+select sum(unique1) FILTER (WHERE
+ unique1 IN (SELECT unique1 FROM onek where unique1 < 100)) FROM tenk1;
+ sum
+------
+ 4950
+(1 row)
+
+-- exercise lots of aggregate parts with FILTER
+select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1)
+ from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+ generate_series(1,2) i;
+ aggfns
+---------------------------
+ {"(2,2,bar)","(3,1,baz)"}
+(1 row)
+
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index ecc1c2c..7b31d13 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -1020,5 +1020,18 @@ SELECT ntile(0) OVER (ORDER BY ten), ten, four FROM tenk1;
ERROR: argument of ntile must be greater than zero
SELECT nth_value(four, 0) OVER (ORDER BY ten), ten, four FROM tenk1;
ERROR: argument of nth_value must be greater than zero
+-- filter
+SELECT sum(salary), row_number() OVER (ORDER BY depname), sum(
+ sum(salary) FILTER (WHERE enroll_date > '2007-01-01')
+) FILTER (WHERE depname <> 'sales') OVER (ORDER BY depname DESC) AS "filtered_sum",
+ depname
+FROM empsalary GROUP BY depname;
+ sum | row_number | filtered_sum | depname
+-------+------------+--------------+-----------
+ 14600 | 3 | | sales
+ 7400 | 2 | 3500 | personnel
+ 25100 | 1 | 22600 | develop
+(3 rows)
+
-- cleanup
DROP TABLE empsalary;
diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql
index 38d4757..5c0196f 100644
--- a/src/test/regress/sql/aggregates.sql
+++ b/src/test/regress/sql/aggregates.sql
@@ -442,3 +442,41 @@ select string_agg(v, NULL) from bytea_test_table;
select string_agg(v, decode('ee', 'hex')) from bytea_test_table;
drop table bytea_test_table;
+
+-- FILTER tests
+
+select min(unique1) filter (where unique1 > 100) from tenk1;
+
+select ten, sum(distinct four) filter (where four::text ~ '123') from onek a
+group by ten;
+
+select ten, sum(distinct four) filter (where four > 10) from onek a
+group by ten
+having exists (select 1 from onek b where sum(distinct a.four) = b.four);
+
+select max(foo COLLATE "C") filter (where (bar collate "POSIX") > '0')
+from (values ('a', 'b')) AS v(foo,bar);
+
+-- outer reference in FILTER (PostgreSQL extension)
+select (select count(*)
+ from (values (1)) t0(inner_c))
+from (values (2),(3)) t1(outer_c); -- inner query is aggregation query
+select (select count(*) filter (where outer_c <> 0)
+ from (values (1)) t0(inner_c))
+from (values (2),(3)) t1(outer_c); -- outer query is aggregation query
+select (select count(inner_c) filter (where outer_c <> 0)
+ from (values (1)) t0(inner_c))
+from (values (2),(3)) t1(outer_c); -- inner query is aggregation query
+select
+ (select max((select i.unique2 from tenk1 i where i.unique1 = o.unique1))
+ filter (where o.unique1 < 10))
+from tenk1 o; -- outer query is aggregation query
+
+-- subquery in FILTER clause (PostgreSQL extension)
+select sum(unique1) FILTER (WHERE
+ unique1 IN (SELECT unique1 FROM onek where unique1 < 100)) FROM tenk1;
+
+-- exercise lots of aggregate parts with FILTER
+select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1)
+ from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c),
+ generate_series(1,2) i;
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 769be0f..6ee3696 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -264,5 +264,13 @@ SELECT ntile(0) OVER (ORDER BY ten), ten, four FROM tenk1;
SELECT nth_value(four, 0) OVER (ORDER BY ten), ten, four FROM tenk1;
+-- filter
+
+SELECT sum(salary), row_number() OVER (ORDER BY depname), sum(
+ sum(salary) FILTER (WHERE enroll_date > '2007-01-01')
+) FILTER (WHERE depname <> 'sales') OVER (ORDER BY depname DESC) AS "filtered_sum",
+ depname
+FROM empsalary GROUP BY depname;
+
-- cleanup
DROP TABLE empsalary;
On Sun, Jul 14, 2013 at 10:15:12PM -0400, Noah Misch wrote:
On Sun, Jul 07, 2013 at 06:37:26PM -0700, David Fetter wrote:
On Sat, Jul 06, 2013 at 11:49:21AM +0100, Dean Rasheed wrote:
Overall I think this patch offers useful additional functionality, in
compliance with the SQL spec, which should be handy to simplify
complex grouping queries.As I understand this feature, it is syntactic sugar for the typical case of an
aggregate with a strict transition function. For example, "min(x) FILTER
(WHERE y > 0)" is rigorously equivalent to "min(CASE y > 0 THEN x END)".
Every SQL aggregate is strict (ISO 9075-2 section 4.15.4), so for standard
queries it is *only* syntactic sugar. In PostgreSQL, it lets you do novel
things with, for example, array_agg(). Is that accurate?I think this is ready for committer.
The patch was thorough. I updated applicable comments, revisited some
cosmetic choices, and made these functional changes:1. The pg_stat_statements "query jumble" should incorporate the filter.
2. The patch did not update costing. I made it add the cost of the filter
expression the same way we add the cost of the argument expressions. This
makes "min(x) FILTER (WHERE y > 0)" match "min(case y > 0 THEN x end)" in
terms of cost, which is a fair start. At some point, we could do better by
reducing the argument cost by the filter selectivity.
Thanks!
A few choices/standard interpretations may deserve discussion. The patch
makes filter clauses contribute to the subquery level chosen to be the
"aggregation query". This is observable through the behavior of these two
standard-conforming queries:select (select count(outer_c)
from (values (1)) t0(inner_c))
from (values (2),(3)) t1(outer_c); -- outer query is aggregation query
select (select count(outer_c) filter (where inner_c <> 0)
from (values (1)) t0(inner_c))
from (values (2),(3)) t1(outer_c); -- inner query is aggregation queryI believe SQL (ISO 9075-2 section 6.9 SR 3,4,6) does require this. Since that
still isn't crystal-clear to me, I mention it in case anyone has a different
reading.Distinct from that, albeit in a similar vein, SQL does not permit outer
references in a filter clause. This patch permits them; I think that
qualifies as a reasonable PostgreSQL extension.
Thanks again.
--- a/doc/src/sgml/keywords.sgml +++ b/doc/src/sgml/keywords.sgml@@ -3200,7 +3200,7 @@ </row> <row> <entry><token>OVER</token></entry> - <entry>reserved (can be function or type)</entry> + <entry>non-reserved</entry>I committed this one-line correction separately.
--- a/src/backend/optimizer/plan/planagg.c +++ b/src/backend/optimizer/plan/planagg.c @@ -314,7 +314,7 @@ find_minmax_aggs_walker(Node *node, List **context) ListCell *l;Assert(aggref->agglevelsup == 0); - if (list_length(aggref->args) != 1 || aggref->aggorder != NIL) + if (list_length(aggref->args) != 1 || aggref->aggorder != NIL || aggref->agg_filter != NULL) return true; /* it couldn't be MIN/MAX */ /* note: we do not care if DISTINCT is mentioned ... */I twitched upon reading this, because neither ORDER BY nor FILTER preclude the
aggregate being MIN or MAX. Perhaps Andrew can explain why he put aggorder
there back in 2009. All I can figure is that writing max(c ORDER BY x) is so
unlikely that we'd too often waste the next syscache lookup. But the same
argument would apply to DISTINCT. With FILTER, the rationale is entirely
different. The aggregate could well be MIN/MAX; we just haven't implemented
the necessary support elsewhere in this file.
Excellent reasoning, and good catch.
See attached patch revisions. The first patch edits
find_minmax_aggs_walker() per my comments just now. The second is
an update of your FILTER patch with the changes to which I alluded
above; it applies atop the first patch. Would you verify that I
didn't ruin anything? Barring problems, will commit.Are you the sole named author of this patch? That's what the CF
page says, but that wasn't fully clear to me from the list
discussions.
While Andrew's help was invaluable and pervasive, I wrote the patch,
and all flaws in it are my fault.
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sun, Jul 14, 2013 at 10:15:12PM -0400, Noah Misch wrote:
On Sun, Jul 07, 2013 at 06:37:26PM -0700, David Fetter wrote:
On Sat, Jul 06, 2013 at 11:49:21AM +0100, Dean Rasheed wrote:
Overall I think this patch offers useful additional functionality, in
compliance with the SQL spec, which should be handy to simplify
complex grouping queries.As I understand this feature, it is syntactic sugar for the typical case of an
aggregate with a strict transition function. For example, "min(x) FILTER
(WHERE y > 0)" is rigorously equivalent to "min(CASE y > 0 THEN x END)".
Every SQL aggregate is strict (ISO 9075-2 section 4.15.4), so for standard
queries it is *only* syntactic sugar. In PostgreSQL, it lets you do novel
things with, for example, array_agg(). Is that accurate?I think this is ready for committer.
The patch was thorough. I updated applicable comments, revisited some
cosmetic choices, and made these functional changes:1. The pg_stat_statements "query jumble" should incorporate the filter.
2. The patch did not update costing. I made it add the cost of the filter
expression the same way we add the cost of the argument expressions. This
makes "min(x) FILTER (WHERE y > 0)" match "min(case y > 0 THEN x end)" in
terms of cost, which is a fair start. At some point, we could do better by
reducing the argument cost by the filter selectivity.A few choices/standard interpretations may deserve discussion. The patch
makes filter clauses contribute to the subquery level chosen to be the
"aggregation query". This is observable through the behavior of these two
standard-conforming queries:select (select count(outer_c)
from (values (1)) t0(inner_c))
from (values (2),(3)) t1(outer_c); -- outer query is aggregation query
select (select count(outer_c) filter (where inner_c <> 0)
from (values (1)) t0(inner_c))
from (values (2),(3)) t1(outer_c); -- inner query is aggregation queryI believe SQL (ISO 9075-2 section 6.9 SR 3,4,6) does require this. Since that
still isn't crystal-clear to me, I mention it in case anyone has a different
reading.Distinct from that, albeit in a similar vein, SQL does not permit outer
references in a filter clause. This patch permits them; I think that
qualifies as a reasonable PostgreSQL extension.--- a/doc/src/sgml/keywords.sgml +++ b/doc/src/sgml/keywords.sgml@@ -3200,7 +3200,7 @@ </row> <row> <entry><token>OVER</token></entry> - <entry>reserved (can be function or type)</entry> + <entry>non-reserved</entry>I committed this one-line correction separately.
--- a/src/backend/optimizer/plan/planagg.c +++ b/src/backend/optimizer/plan/planagg.c @@ -314,7 +314,7 @@ find_minmax_aggs_walker(Node *node, List **context) ListCell *l;Assert(aggref->agglevelsup == 0); - if (list_length(aggref->args) != 1 || aggref->aggorder != NIL) + if (list_length(aggref->args) != 1 || aggref->aggorder != NIL || aggref->agg_filter != NULL) return true; /* it couldn't be MIN/MAX */ /* note: we do not care if DISTINCT is mentioned ... */I twitched upon reading this, because neither ORDER BY nor FILTER preclude the
aggregate being MIN or MAX. Perhaps Andrew can explain why he put aggorder
there back in 2009. All I can figure is that writing max(c ORDER BY x) is so
unlikely that we'd too often waste the next syscache lookup. But the same
argument would apply to DISTINCT. With FILTER, the rationale is entirely
different. The aggregate could well be MIN/MAX; we just haven't implemented
the necessary support elsewhere in this file.See attached patch revisions. The first patch edits find_minmax_aggs_walker()
per my comments just now. The second is an update of your FILTER patch with
the changes to which I alluded above; it applies atop the first patch. Would
you verify that I didn't ruin anything? Barring problems, will commit.Are you the sole named author of this patch? That's what the CF page says,
but that wasn't fully clear to me from the list discussions.Thanks,
nm
Tested your changes. They pass regression, etc. :)
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Noah Misch said:
I twitched upon reading this, because neither ORDER BY nor FILTER preclude
the aggregate being MIN or MAX. Perhaps Andrew can explain why he put
aggorder there back in 2009.
The bottom line is that I intentionally avoided assuming that an agg with an
aggsortop could only be min() or max() and that having an order by clause
would always be harmless in such cases. I can't think of an actual use case
where it would matter, but I've seen people define some pretty strange aggs
recently.
So I mildly object to simply throwing away the ORDER BY clause in such cases.
--
Andrew (irc:RhodiumToad)
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Jul 15, 2013 at 06:56:17PM +0000, Andrew Gierth wrote:
Noah Misch said:
I twitched upon reading this, because neither ORDER BY nor FILTER preclude
the aggregate being MIN or MAX. Perhaps Andrew can explain why he put
aggorder there back in 2009.The bottom line is that I intentionally avoided assuming that an agg with an
aggsortop could only be min() or max() and that having an order by clause
would always be harmless in such cases. I can't think of an actual use case
where it would matter, but I've seen people define some pretty strange aggs
recently.So I mildly object to simply throwing away the ORDER BY clause in such cases.
I can't think of another use for aggsortop as defined today. However, on
further reflection, min(x ORDER BY y) is not identical to min(x) when the
B-tree operator class of the aggsortop can find non-identical datums to be
equal. This affects at least min(numeric) and min(interval). min(x) chooses
an unspecified x among those equal to the smallest x, while min(x ORDER BY y)
can be used to narrow the choice. I will update the comments along those
lines and not change semantics after all.
Thanks,
nm
--
Noah Misch
EnterpriseDB http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Jul 15, 2013 at 11:43:04AM -0700, David Fetter wrote:
On Sun, Jul 14, 2013 at 10:15:12PM -0400, Noah Misch wrote:
See attached patch revisions. The first patch edits find_minmax_aggs_walker()
per my comments just now. The second is an update of your FILTER patch with
the changes to which I alluded above; it applies atop the first patch. Would
you verify that I didn't ruin anything? Barring problems, will commit.
Tested your changes. They pass regression, etc. :)
Committed.
--
Noah Misch
EnterpriseDB http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers