GROUPING SETS revisited
In case anyone's interested, I've taken the CTE-based grouping sets patch from
[1]: http://archives.postgresql.org/pgsql-hackers/2009-05/msg00700.php
it for whitespace consistency, style conformity, or anything else, but (tuits
permitting) hope to figure out how it works and get it closer to commitability
in some upcoming commitfest.
I mention it here so that if someone else is working on it, we can avoid
duplicated effort, and to see if a CTE-based grouping sets implementation is
really the way we think we want to go.
[1]: http://archives.postgresql.org/pgsql-hackers/2009-05/msg00700.php
--
Joshua Tolley / eggyknap
End Point Corporation
http://www.endpoint.com
Attachments:
gs91.patchtext/x-diff; charset=us-asciiDownload
diff --git a/src/backend/parser/Makefile b/src/backend/parser/Makefile
index a8f4c07..fb248a6 100644
*** a/src/backend/parser/Makefile
--- b/src/backend/parser/Makefile
*************** override CPPFLAGS := -I. -I$(srcdir) $(C
*** 15,21 ****
OBJS= analyze.o gram.o keywords.o kwlookup.o parser.o \
parse_agg.o parse_clause.o parse_coerce.o parse_cte.o parse_expr.o \
parse_func.o parse_node.o parse_oper.o parse_param.o parse_relation.o \
! parse_target.o parse_type.o parse_utilcmd.o scansup.o
FLEXFLAGS = -CF
--- 15,21 ----
OBJS= analyze.o gram.o keywords.o kwlookup.o parser.o \
parse_agg.o parse_clause.o parse_coerce.o parse_cte.o parse_expr.o \
parse_func.o parse_node.o parse_oper.o parse_param.o parse_relation.o \
! parse_target.o parse_type.o parse_utilcmd.o scansup.o parse_gsets.o
FLEXFLAGS = -CF
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6b99a10..1b579a8 100644
*** a/src/backend/parser/analyze.c
--- b/src/backend/parser/analyze.c
***************
*** 34,39 ****
--- 34,40 ----
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_cte.h"
+ #include "parser/parse_gsets.h"
#include "parser/parse_oper.h"
#include "parser/parse_param.h"
#include "parser/parse_relation.h"
*************** parse_sub_analyze(Node *parseTree, Parse
*** 150,155 ****
--- 151,313 ----
}
/*
+ * process GROUPING SETS
+ */
+ static SelectStmt *
+ makeSelectStmt(List *targetList, List *fromClause)
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+ n->distinctClause = NULL;
+ n->intoClause = NULL;
+ n->targetList = targetList;
+ n->fromClause = fromClause;
+ n->whereClause = NULL;
+ n->groupClause = NULL;
+ n->havingClause = NULL;
+ n->windowClause = NIL;
+ n->withClause = NULL;
+ n->valuesLists = NIL;
+ n->sortClause = NIL;
+ n->limitOffset = NULL;
+ n->limitCount = NULL;
+ n->lockingClause = NIL;
+ n->op = SETOP_NONE;
+ n->all = false;
+ n->larg = NULL;
+ n->rarg = NULL;
+ return n;
+ }
+
+ static List *
+ makeStarTargetList(void)
+ {
+ ResTarget *rt = makeNode(ResTarget);
+
+ rt->name = NULL;
+ rt->indirection = NIL;
+ rt->val = (Node *) makeNode(ColumnRef);
+ ((ColumnRef *) rt->val)->fields = list_make1(makeNode(A_Star));
+ rt->location = -1;
+
+ return list_make1(rt);
+ }
+
+ static SelectStmt *
+ transformGroupingSets(ParseState *pstate, SelectStmt *stmt)
+ {
+ if (stmt->groupClause && IsA(stmt->groupClause, GroupByClause))
+ {
+ GroupingSetsSpec *gss = (GroupingSetsSpec *) expandGroupingSets(pstate,
+ (List *)((GroupByClause *)stmt->groupClause)->fields);
+
+ if (pstate->p_hasGroupingSets)
+ {
+ CommonTableExpr *cte = makeNode(CommonTableExpr);
+ SelectStmt *cteedstmt;
+ int ngroupingsets = list_length(gss->set_list) + (gss->has_empty_set ? 1 : 0);
+ bool all = ((GroupByClause *) stmt->groupClause)->all;
+
+ cteedstmt = makeSelectStmt(NIL, NIL);
+ cteedstmt->intoClause = stmt->intoClause;
+ cteedstmt->sortClause = stmt->sortClause;
+ cteedstmt->limitOffset = stmt->limitOffset;
+ cteedstmt->limitCount = stmt->limitCount;
+ cteedstmt->lockingClause = stmt->lockingClause;
+
+ cte->ctename = "**g**";
+ cte->ctequery = (Node *) stmt;
+ cte->location = -1;
+
+ cteedstmt->withClause = makeNode(WithClause);
+ cteedstmt->withClause->ctes = list_make1(cte);
+ cteedstmt->withClause->recursive = false;
+ cteedstmt->withClause->location = -1;
+
+ /* when is more than one grouping set, then we should generate setop node */
+ if (ngroupingsets > 1)
+ {
+ /* add quuery under union all for every grouping set */
+ SelectStmt *larg = NULL;
+ SelectStmt *rarg;
+ ListCell *lc;
+
+ foreach(lc, gss->set_list)
+ {
+ List *groupClause;
+
+ Assert(IsA(lfirst(lc), List));
+ groupClause = (List *) lfirst(lc);
+
+ if (larg == NULL)
+ {
+ larg = makeSelectStmt(copyObject(stmt->targetList),
+ list_make1(makeRangeVar(NULL, "**g**", -1)));
+ larg->groupClause = (Node *) groupClause;
+ larg->havingClause = copyObject(stmt->havingClause);
+ }
+ else
+ {
+ SelectStmt *setop = makeSelectStmt(NIL, NIL);
+
+ rarg = makeSelectStmt(copyObject(stmt->targetList),
+ list_make1(makeRangeVar(NULL, "**g**", -1)));
+ rarg->groupClause = (Node *) groupClause;
+ rarg->havingClause = copyObject(stmt->havingClause);
+
+ setop->op = SETOP_UNION;
+ setop->larg = larg;
+ setop->rarg = rarg;
+ setop->all = all;
+
+ larg = setop;
+ }
+ }
+ if (gss->has_empty_set)
+ {
+ SelectStmt *setop = makeSelectStmt(NIL, NIL);
+
+ rarg = makeSelectStmt(copyObject(stmt->targetList),
+ list_make1(makeRangeVar(NULL, "**g**", -1)));
+ rarg->havingClause = copyObject(stmt->havingClause);
+
+ setop->op = SETOP_UNION;
+ setop->larg = larg;
+ setop->rarg = rarg;
+ setop->all = all;
+
+ larg = setop;
+ }
+ /* merge larg to result */
+ cteedstmt->op = larg->op;
+ cteedstmt->larg = larg->larg;
+ cteedstmt->rarg = larg->rarg;
+ cteedstmt->all = larg->all;
+ }
+ else
+ {
+ /* there isn't used setop node */
+ cteedstmt->targetList = copyObject(stmt->targetList);
+ cteedstmt->fromClause = list_make1(makeRangeVar(NULL, "**g**", -1));
+ }
+
+ ((SelectStmt *)cte->ctequery)->targetList = makeStarTargetList();
+ ((SelectStmt *)cte->ctequery)->groupClause = NULL;
+ ((SelectStmt *)cte->ctequery)->sortClause = NIL;
+ ((SelectStmt *)cte->ctequery)->limitOffset = stmt->limitOffset;
+ ((SelectStmt *)cte->ctequery)->limitCount = stmt->limitCount;
+ ((SelectStmt *)cte->ctequery)->lockingClause = stmt->lockingClause;
+
+ return cteedstmt;
+ }
+ else
+ /* trim GroupByClause to groupByClause */
+ stmt->groupClause = (Node *)((GroupByClause *)stmt->groupClause)->fields;
+ }
+
+ return stmt;
+ }
+
+ /*
* transformStmt -
* transform a Parse tree into a Query tree.
*/
*************** transformStmt(ParseState *pstate, Node *
*** 179,184 ****
--- 337,344 ----
{
SelectStmt *n = (SelectStmt *) parseTree;
+ n = transformGroupingSets(pstate, n);
+
if (n->valuesLists)
result = transformValuesClause(pstate, n);
else if (n->op == SETOP_NONE)
*************** transformSelectStmt(ParseState *pstate,
*** 827,833 ****
false /* allow SQL92 rules */ );
qry->groupClause = transformGroupClause(pstate,
! stmt->groupClause,
&qry->targetList,
qry->sortClause,
false /* allow SQL92 rules */ );
--- 987,993 ----
false /* allow SQL92 rules */ );
qry->groupClause = transformGroupClause(pstate,
! (List *) stmt->groupClause,
&qry->targetList,
qry->sortClause,
false /* allow SQL92 rules */ );
*************** transformValuesClause(ParseState *pstate
*** 924,930 ****
Assert(stmt->targetList == NIL);
Assert(stmt->fromClause == NIL);
Assert(stmt->whereClause == NULL);
! Assert(stmt->groupClause == NIL);
Assert(stmt->havingClause == NULL);
Assert(stmt->windowClause == NIL);
Assert(stmt->op == SETOP_NONE);
--- 1084,1090 ----
Assert(stmt->targetList == NIL);
Assert(stmt->fromClause == NIL);
Assert(stmt->whereClause == NULL);
! Assert(stmt->groupClause == NULL);
Assert(stmt->havingClause == NULL);
Assert(stmt->windowClause == NIL);
Assert(stmt->op == SETOP_NONE);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3f6eeeb..1d907c5 100644
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
*************** static Node *makeBitStringConst(char *st
*** 111,116 ****
--- 111,118 ----
static Node *makeNullAConst(int location);
static Node *makeAConst(Value *v, int location);
static Node *makeBoolAConst(bool state, int location);
+ static Node *makeGroupingSetsFunc(GroupingSetsFuncIdentity identity, Node *expr,
+ List *expr_list, int location);
static FuncCall *makeOverlaps(List *largs, List *rargs,
int location, core_yyscan_t yyscanner);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
*************** static TypeName *TableFuncTypeName(List
*** 292,298 ****
target_list insert_column_list set_target_list
set_clause_list set_clause multiple_set_clause
ctext_expr_list ctext_row def_list indirection opt_indirection
! reloption_list group_clause TriggerFuncArgs select_limit
opt_select_limit opclass_item_list opclass_drop_list
opt_opfamily transaction_mode_list_or_empty
TableFuncElementList opt_type_modifiers
--- 294,300 ----
target_list insert_column_list set_target_list
set_clause_list set_clause multiple_set_clause
ctext_expr_list ctext_row def_list indirection opt_indirection
! reloption_list TriggerFuncArgs select_limit
opt_select_limit opclass_item_list opclass_drop_list
opt_opfamily transaction_mode_list_or_empty
TableFuncElementList opt_type_modifiers
*************** static TypeName *TableFuncTypeName(List
*** 437,442 ****
--- 439,449 ----
opt_frame_clause frame_extent frame_bound
%type <str> opt_existing_window_name
+ %type <node> grouping_element empty_grouping_set grouping_sets_spec
+ group_clause
+ %type <list> grouping_element_list
+ %type <boolean> opt_grouping_set_quantifier
+
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
*************** static TypeName *TableFuncTypeName(List
*** 472,478 ****
COMMITTED CONCURRENTLY CONFIGURATION CONNECTION CONSTRAINT CONSTRAINTS
CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE CREATEDB
CREATEROLE CREATEUSER CROSS CSV CURRENT_P
! CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
--- 479,485 ----
COMMITTED CONCURRENTLY CONFIGURATION CONNECTION CONSTRAINT CONSTRAINTS
CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE CREATEDB
CREATEROLE CREATEUSER CROSS CSV CURRENT_P
! CUBE CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
*************** static TypeName *TableFuncTypeName(List
*** 485,491 ****
FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
FREEZE FROM FULL FUNCTION FUNCTIONS
! GLOBAL GRANT GRANTED GREATEST GROUP_P
HANDLER HAVING HEADER_P HOLD HOUR_P
--- 492,498 ----
FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
FREEZE FROM FULL FUNCTION FUNCTIONS
! GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING GROUPING_ID
HANDLER HAVING HEADER_P HOLD HOUR_P
*************** static TypeName *TableFuncTypeName(List
*** 519,528 ****
RANGE READ REAL REASSIGN RECHECK RECURSIVE REFERENCES REINDEX
RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA RESET RESTART
! RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROW ROWS RULE
SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
! SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE
SHOW SIMILAR SIMPLE SMALLINT SOME STABLE STANDALONE_P START STATEMENT
STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING SUPERUSER_P
SYMMETRIC SYSID SYSTEM_P
--- 526,535 ----
RANGE READ REAL REASSIGN RECHECK RECURSIVE REFERENCES REINDEX
RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA RESET RESTART
! RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP ROW ROWS RULE
SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
! SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SETS SHARE
SHOW SIMILAR SIMPLE SMALLINT SOME STABLE STANDALONE_P START STATEMENT
STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING SUPERUSER_P
SYMMETRIC SYSID SYSTEM_P
*************** first_or_next: FIRST_P { $$ = 0;
*** 7764,7772 ****
group_clause:
! GROUP_P BY expr_list { $$ = $3; }
! | /*EMPTY*/ { $$ = NIL; }
! ;
having_clause:
HAVING a_expr { $$ = $2; }
--- 7771,7825 ----
group_clause:
! GROUP_P BY opt_grouping_set_quantifier grouping_element_list
! {
! GroupByClause *clause = makeNode(GroupByClause);
! clause->all = $3;
! clause->fields = $4;
! clause->location = @1;
! $$ = (Node *) clause;
! }
! | /*EMPTY*/
! {
! $$ = NULL;
! }
! ;
!
! grouping_sets_spec:
! GROUPING SETS '(' grouping_element_list ')'
! {
! /* We cannot identify and drop empty sets yet. */
! GroupingSetsSpec *gss = makeNode(GroupingSetsSpec);
! gss->set_list = $4;
! gss->has_empty_set = false;
! gss->location = @1;
! $$ = (Node *) gss;
! }
! ;
!
! grouping_element_list:
! grouping_element { $$ = list_make1($1); }
! | grouping_element_list ',' grouping_element { $$ = lappend($1, $3); }
! ;
!
! grouping_element:
! a_expr { $$ = $1; }
! | grouping_sets_spec { $$ = $1; }
! | empty_grouping_set { $$ = $1; }
! ;
!
! empty_grouping_set:
! '(' ')'
! {
! $$ = makeNode(EmptySet);
! }
! ;
!
! opt_grouping_set_quantifier:
! ALL { $$ = true; }
! | DISTINCT { $$ = false; }
! | /*EMPTY*/ { $$ = true; }
! ;
having_clause:
HAVING a_expr { $$ = $2; }
*************** c_expr: columnref { $$ = $1; }
*** 9293,9299 ****
r->location = @1;
$$ = (Node *)r;
}
! ;
/*
* func_expr is split out from c_expr just so that we have a classification
--- 9346,9375 ----
r->location = @1;
$$ = (Node *)r;
}
! | GROUPING '(' a_expr ')'
! {
! $$ = makeGroupingSetsFunc(FUNCTION_GROUPING, $3, NIL, @1);
! }
! | GROUPING_ID '(' expr_list ')'
! {
! $$ = makeGroupingSetsFunc(FUNCTION_GROUPING_ID, NULL, $3, @1);
! }
! | CUBE '(' expr_list ')'
! {
! /*
! * Cube should be used in two different contexts. First,
! * as part of Grouping Sets specification. Second, as
! * normal UDF function from contrib cube module. When isnot
! * grouping sets context, then node is transformated to
! * FuncCall node later.
! */
! $$ = makeGroupingSetsFunc(FUNCTION_CUBE, NULL, $3, @1);
! }
! | ROLLUP '(' expr_list ')'
! {
! $$ = makeGroupingSetsFunc(FUNCTION_ROLLUP, NULL, $3, @1);
! }
! ;
/*
* func_expr is split out from c_expr just so that we have a classification
*************** unreserved_keyword:
*** 11010,11015 ****
--- 11086,11092 ----
| SERVER
| SESSION
| SET
+ | SETS
| SHARE
| SHOW
| SIMPLE
*************** col_name_keyword:
*** 11087,11092 ****
--- 11164,11170 ----
| EXTRACT
| FLOAT_P
| GREATEST
+ | GROUPING_ID
| INOUT
| INT_P
| INTEGER
*************** reserved_keyword:
*** 11181,11186 ****
--- 11259,11265 ----
| COLUMN
| CONSTRAINT
| CREATE
+ | CUBE
| CURRENT_CATALOG
| CURRENT_DATE
| CURRENT_ROLE
*************** reserved_keyword:
*** 11202,11207 ****
--- 11281,11287 ----
| FROM
| GRANT
| GROUP_P
+ | GROUPING
| HAVING
| IN_P
| INITIALLY
*************** reserved_keyword:
*** 11223,11228 ****
--- 11303,11309 ----
| PRIMARY
| REFERENCES
| RETURNING
+ | ROLLUP
| SELECT
| SESSION_USER
| SOME
*************** makeXmlExpr(XmlExprOp op, char *name, Li
*** 11728,11733 ****
--- 11809,11826 ----
return (Node *) x;
}
+ static Node *
+ makeGroupingSetsFunc(GroupingSetsFuncIdentity identity, Node *expr, List *expr_list, int location)
+ {
+ GroupingSetsFunc *gsfunc = makeNode(GroupingSetsFunc);
+
+ gsfunc->identity = identity;
+ gsfunc->expr = expr;
+ gsfunc->expr_list = expr_list;
+ gsfunc->location = location;
+ return (Node *) gsfunc;
+ }
+
/* parser_init()
* Initialize to parse one query string
*/
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 0a69bde..3aeea45 100644
*** a/src/backend/parser/parse_agg.c
--- b/src/backend/parser/parse_agg.c
*************** typedef struct
*** 33,44 ****
--- 33,52 ----
bool have_non_var_grouping;
int sublevels_up;
} check_ungrouped_columns_context;
+
+ typedef struct
+ {
+ ParseState *pstate;
+ List *groupClause;
+ } transform_ungrouped_target_context;
static void check_ungrouped_columns(Node *node, ParseState *pstate,
List *groupClauses, bool have_non_var_grouping);
static bool check_ungrouped_columns_walker(Node *node,
check_ungrouped_columns_context *context);
+ static Node * transform_ungrouped_target(Node *node, ParseState *pstate,
+ List *groupClauses);
/*
* transformAggregateCall -
*************** parseCheckAggregates(ParseState *pstate,
*** 405,411 ****
* WINDOW clauses. For that matter, it's also going to examine the
* grouping expressions themselves --- but they'll all pass the test ...
*/
! clause = (Node *) qry->targetList;
if (hasJoinRTEs)
clause = flatten_join_alias_vars(root, clause);
check_ungrouped_columns(clause, pstate,
--- 413,428 ----
* WINDOW clauses. For that matter, it's also going to examine the
* grouping expressions themselves --- but they'll all pass the test ...
*/
! if (pstate->parentParseState && pstate->parentParseState->p_hasGroupingSets)
! {
! clause = (Node *) transform_ungrouped_target((Node *) qry->targetList,
! pstate,
! groupClauses);
! /* HACK!!! - move to transform part*/
! qry->targetList = clause;
! }
! else
! clause = (Node *) qry->targetList;
if (hasJoinRTEs)
clause = flatten_join_alias_vars(root, clause);
check_ungrouped_columns(clause, pstate,
*************** parseCheckWindowFuncs(ParseState *pstate
*** 514,519 ****
--- 531,613 ----
}
/*
+ * transform_ungrouped_cols_mutator -
+ * All non aggregates non constatnt columns are replaced by NULL,
+ * grouping and grouping_id functions are replaced by constatnts.
+ */
+ static Node *
+ transform_ungrouped_target_mutator(Node *node,
+ transform_ungrouped_target_context *context)
+ {
+ if (node == NULL)
+ return NULL;
+ if (IsA(node, Aggref))
+ return node;
+
+ if (IsA(node, GroupingSetsFunc))
+ {
+ GroupingSetsFunc *gsf = (GroupingSetsFunc *) node;
+ int result = 0;
+
+ if (gsf->identity == FUNCTION_GROUPING)
+ {
+ result = list_member(context->groupClause, gsf->expr) ? 0 : 1;
+ return (Node *) make_const(context->pstate, makeInteger(result), gsf->location);
+ }
+ else if (gsf->identity == FUNCTION_GROUPING_ID)
+ {
+ ListCell *el;
+
+ foreach(el, gsf->expr_list)
+ {
+ result = result << 1;
+ if (!list_member(context->groupClause, lfirst(el)))
+ result = result | 0x01;
+ }
+ return (Node *) make_const(context->pstate, makeInteger(result), gsf->location);
+ }
+ else /* replace Cube and Rollup by FuncCall node */
+ {
+ /* ToDo: Support cube function */
+ }
+ }
+
+ if (IsA(node, Var))
+ {
+ Var *var = (Var *) node;
+
+ if (list_member(context->groupClause, node))
+ return node;
+ else
+ return (Node *) makeNullConst(var->vartype, var->vartypmod);
+ }
+ else if (IsA(node, FuncExpr))
+ {
+ FuncExpr *fexpr = (FuncExpr *) node;
+
+ if (list_member(context->groupClause, node))
+ return node;
+ else
+ return (Node *) makeNullConst(fexpr->funcresulttype, -1);
+ }
+
+ return expression_tree_mutator(node,
+ transform_ungrouped_target_mutator, context);
+ }
+
+ static Node *
+ transform_ungrouped_target(Node *node, ParseState *pstate,
+ List *groupClauses)
+ {
+ transform_ungrouped_target_context context;
+
+ context.pstate = pstate;
+ context.groupClause = groupClauses;
+
+ return transform_ungrouped_target_mutator(node, &context);
+ }
+
+ /*
* check_ungrouped_columns -
* Scan the given expression tree for ungrouped variables (variables
* that are not listed in the groupClauses list and are not within
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 888b526..72762b1 100644
*** a/src/backend/parser/parse_expr.c
--- b/src/backend/parser/parse_expr.c
*************** transformExpr(ParseState *pstate, Node *
*** 288,293 ****
--- 288,312 ----
case T_BooleanTest:
result = transformBooleanTest(pstate, (BooleanTest *) expr);
break;
+
+ case T_GroupingSetsFunc:
+ {
+ GroupingSetsFunc *gsf = (GroupingSetsFunc *) expr;
+ ListCell *lc;
+ List *expr_list = NIL;
+
+ gsf->expr = (Node *) transformExpr(pstate, (Node *) gsf->expr);
+
+ foreach(lc, gsf->expr_list)
+ {
+ expr_list = lappend(expr_list, transformExpr(pstate,
+ (Node *) lfirst(lc)));
+ }
+ gsf->expr_list = expr_list;
+ result = expr;
+ break;
+ }
+
case T_CurrentOfExpr:
result = transformCurrentOfExpr(pstate, (CurrentOfExpr *) expr);
*************** transformExpr(ParseState *pstate, Node *
*** 324,329 ****
--- 343,349 ----
case T_CoerceToDomain:
case T_CoerceToDomainValue:
case T_SetToDefault:
+ case T_EmptySet:
{
result = (Node *) expr;
break;
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index e542dc0..1a3e969 100644
*** a/src/backend/parser/parse_target.c
--- b/src/backend/parser/parse_target.c
*************** FigureColnameInternal(Node *node, char *
*** 1586,1591 ****
--- 1586,1607 ----
case T_XmlSerialize:
*name = "xmlserialize";
return 2;
+ case T_GroupingSetsFunc:
+ switch (((GroupingSetsFunc *) node)->identity)
+ {
+ case FUNCTION_GROUPING:
+ *name = "grouping";
+ return 2;
+ case FUNCTION_GROUPING_ID:
+ *name = "grouping_id";
+ return 2;
+ case FUNCTION_CUBE: /* by compiler quite */
+ case FUNCTION_ROLLUP:
+ /* nothing */
+ break;
+ }
+ break;
+
default:
break;
}
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index a5f5df5..836e38d 100644
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
*************** typedef enum NodeTag
*** 385,390 ****
--- 385,394 ----
T_XmlSerialize,
T_WithClause,
T_CommonTableExpr,
+ T_GroupingSetsFunc,
+ T_GroupingSetsSpec,
+ T_EmptySet,
+ T_GroupByClause,
/*
* TAGS FOR RANDOM OTHER STUFF
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b591073..9efa9c7 100644
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
*************** typedef struct SortBy
*** 378,383 ****
--- 378,410 ----
} SortBy;
/*
+ * GroupingSetsSpec - for GROUP BY GROUPING SETS clause
+ */
+ typedef struct GroupingSetsSpec
+ {
+ NodeTag type;
+ List *set_list;
+ bool has_empty_set; /* true when grouping sets contains empty set */
+ int location;
+ } GroupingSetsSpec;
+
+ typedef struct EmptySet
+ {
+ NodeTag type;
+ } EmptySet;
+
+ /*
+ * GroupByClause for GROUP BY clause
+ */
+ typedef struct GroupByClause
+ {
+ NodeTag type;
+ bool all;
+ List *fields;
+ int location;
+ } GroupByClause;
+
+ /*
* WindowDef - raw representation of WINDOW and OVER clauses
*
* For entries in a WINDOW list, "name" is the window name being defined.
*************** typedef struct WindowDef
*** 431,436 ****
--- 458,483 ----
FRAMEOPTION_END_CURRENT_ROW)
/*
+ * GroupingSetsFunc - parser node for Grouping, Grouping_id, Cube and Rollup quasy functions
+ */
+ typedef enum GroupingSetsFuncIdentity
+ {
+ FUNCTION_GROUPING,
+ FUNCTION_GROUPING_ID,
+ FUNCTION_CUBE,
+ FUNCTION_ROLLUP
+ } GroupingSetsFuncIdentity;
+
+ typedef struct GroupingSetsFunc
+ {
+ NodeTag type;
+ GroupingSetsFuncIdentity identity;
+ Node *expr;
+ List *expr_list;
+ int location;
+ } GroupingSetsFunc;
+
+ /*
* RangeSubselect - subquery appearing in a FROM clause
*/
typedef struct RangeSubselect
*************** typedef struct SelectStmt
*** 956,962 ****
List *targetList; /* the target list (of ResTarget) */
List *fromClause; /* the FROM clause */
Node *whereClause; /* WHERE qualification */
! List *groupClause; /* GROUP BY clauses */
Node *havingClause; /* HAVING conditional-expression */
List *windowClause; /* WINDOW window_name AS (...), ... */
WithClause *withClause; /* WITH clause */
--- 1003,1009 ----
List *targetList; /* the target list (of ResTarget) */
List *fromClause; /* the FROM clause */
Node *whereClause; /* WHERE qualification */
! Node *groupClause; /* GROUP BY clauses */
Node *havingClause; /* HAVING conditional-expression */
List *windowClause; /* WINDOW window_name AS (...), ... */
WithClause *withClause; /* WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 49d4b6c..d0dcfe7 100644
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
*************** PG_KEYWORD("createrole", CREATEROLE, UNR
*** 99,104 ****
--- 99,105 ----
PG_KEYWORD("createuser", CREATEUSER, UNRESERVED_KEYWORD)
PG_KEYWORD("cross", CROSS, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("csv", CSV, UNRESERVED_KEYWORD)
+ PG_KEYWORD("cube", CUBE, RESERVED_KEYWORD)
PG_KEYWORD("current", CURRENT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("current_catalog", CURRENT_CATALOG, RESERVED_KEYWORD)
PG_KEYWORD("current_date", CURRENT_DATE, RESERVED_KEYWORD)
*************** PG_KEYWORD("grant", GRANT, RESERVED_KEYW
*** 171,176 ****
--- 172,179 ----
PG_KEYWORD("granted", GRANTED, UNRESERVED_KEYWORD)
PG_KEYWORD("greatest", GREATEST, COL_NAME_KEYWORD)
PG_KEYWORD("group", GROUP_P, RESERVED_KEYWORD)
+ PG_KEYWORD("grouping", GROUPING, RESERVED_KEYWORD)
+ PG_KEYWORD("grouping_id", GROUPING_ID, COL_NAME_KEYWORD)
PG_KEYWORD("handler", HANDLER, UNRESERVED_KEYWORD)
PG_KEYWORD("having", HAVING, RESERVED_KEYWORD)
PG_KEYWORD("header", HEADER_P, UNRESERVED_KEYWORD)
*************** PG_KEYWORD("revoke", REVOKE, UNRESERVED_
*** 318,323 ****
--- 321,327 ----
PG_KEYWORD("right", RIGHT, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("role", ROLE, UNRESERVED_KEYWORD)
PG_KEYWORD("rollback", ROLLBACK, UNRESERVED_KEYWORD)
+ PG_KEYWORD("rollup", ROLLUP, RESERVED_KEYWORD)
PG_KEYWORD("row", ROW, COL_NAME_KEYWORD)
PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD)
PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD)
*************** PG_KEYWORD("session", SESSION, UNRESERVE
*** 336,341 ****
--- 340,346 ----
PG_KEYWORD("session_user", SESSION_USER, RESERVED_KEYWORD)
PG_KEYWORD("set", SET, UNRESERVED_KEYWORD)
PG_KEYWORD("setof", SETOF, COL_NAME_KEYWORD)
+ PG_KEYWORD("sets", SETS, UNRESERVED_KEYWORD)
PG_KEYWORD("share", SHARE, UNRESERVED_KEYWORD)
PG_KEYWORD("show", SHOW, UNRESERVED_KEYWORD)
PG_KEYWORD("similar", SIMILAR, TYPE_FUNC_NAME_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 1c1383b..f2b80bd 100644
*** a/src/include/parser/parse_node.h
--- b/src/include/parser/parse_node.h
*************** struct ParseState
*** 107,112 ****
--- 107,113 ----
bool p_is_update;
bool p_locked_from_parent;
Relation p_target_relation;
+ bool p_hasGroupingSets;
RangeTblEntry *p_target_rangetblentry;
/*
Hello
2010/8/3 Joshua Tolley <eggyknap@gmail.com>:
In case anyone's interested, I've taken the CTE-based grouping sets patch from
[1] and made it apply to 9.1, attached. I haven't yet done things like checked
it for whitespace consistency, style conformity, or anything else, but (tuits
permitting) hope to figure out how it works and get it closer to commitability
in some upcoming commitfest.I mention it here so that if someone else is working on it, we can avoid
duplicated effort, and to see if a CTE-based grouping sets implementation is
really the way we think we want to go.
I am plaing with it now :). I have a plan to replace CTE with similar
but explicit executor node. The main issue of existing patch was using
just transformation to CTE. I agree, so it isn't too much extensiable
in future. Now I am cleaning identifiers little bit. Any colaboration
is welcome.
My plan:
1) clean CTE patch
2) replace CTE with explicit executor node, but still based on tuplestore
3) when will be possible parallel processing based on hash agg - then
we don't need to use tuplestore
comments??
Regards
Pavel
Show quoted text
[1] http://archives.postgresql.org/pgsql-hackers/2009-05/msg00700.php
--
Joshua Tolley / eggyknap
End Point Corporation
http://www.endpoint.com-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)iEYEARECAAYFAkxXrggACgkQRiRfCGf1UMMlCQCglaIdtPj8Qe6G60V2LHn5pFNn
kgIAniXRgIQEbVrK/eDVZnmKCzw33lT9
=XVVV
-----END PGP SIGNATURE-----
2010/8/3 Pavel Stehule <pavel.stehule@gmail.com>:
Hello
2010/8/3 Joshua Tolley <eggyknap@gmail.com>:
In case anyone's interested, I've taken the CTE-based grouping sets patch from
[1] and made it apply to 9.1, attached. I haven't yet done things like checked
it for whitespace consistency, style conformity, or anything else, but (tuits
permitting) hope to figure out how it works and get it closer to commitability
in some upcoming commitfest.I mention it here so that if someone else is working on it, we can avoid
duplicated effort, and to see if a CTE-based grouping sets implementation is
really the way we think we want to go.I am plaing with it now :). I have a plan to replace CTE with similar
but explicit executor node. The main issue of existing patch was using
just transformation to CTE. I agree, so it isn't too much extensiable
in future. Now I am cleaning identifiers little bit. Any colaboration
is welcome.My plan:
1) clean CTE patch
2) replace CTE with explicit executor node, but still based on tuplestore
3) when will be possible parallel processing based on hash agg - then
we don't need to use tuplestore
Couldn't you explain what exactly "explicit executor node"? I hope we
can share your image to develop it further than only transformation to
CTE.
Regards,
--
Hitoshi Harada
2010/8/3 Hitoshi Harada <umi.tanuki@gmail.com>:
2010/8/3 Pavel Stehule <pavel.stehule@gmail.com>:
Hello
2010/8/3 Joshua Tolley <eggyknap@gmail.com>:
In case anyone's interested, I've taken the CTE-based grouping sets patch from
[1] and made it apply to 9.1, attached. I haven't yet done things like checked
it for whitespace consistency, style conformity, or anything else, but (tuits
permitting) hope to figure out how it works and get it closer to commitability
in some upcoming commitfest.I mention it here so that if someone else is working on it, we can avoid
duplicated effort, and to see if a CTE-based grouping sets implementation is
really the way we think we want to go.I am plaing with it now :). I have a plan to replace CTE with similar
but explicit executor node. The main issue of existing patch was using
just transformation to CTE. I agree, so it isn't too much extensiable
in future. Now I am cleaning identifiers little bit. Any colaboration
is welcome.My plan:
1) clean CTE patch
2) replace CTE with explicit executor node, but still based on tuplestore
3) when will be possible parallel processing based on hash agg - then
we don't need to use tuplestoreCouldn't you explain what exactly "explicit executor node"? I hope we
can share your image to develop it further than only transformation to
CTE.
I have a one reason
Implementation based on CTE doesn't create space for possible
optimalisations (I think now, maybe it isn't true). It is good for
initial or referencial implementation - but it can be too complex,
when we will try to append some optimalizations - like parallel hash
agg processing, direct data reading without tuplestore. If you are, as
CTE author, thinking so these features are possible in non recursive
CTE too, I am not agains. I hope so this week I'll have a CTE based
patch - and we can talk about next direction. I see as possible
performance issue using a tuplestore - there are lot of cases where
repeating of source query can be faster.
If I remember well, Tom has a objection, so transformation to CTE is
too early - in parser. So It will be first change. Executor node can
be CTE.
regards
Pavel
p.s. I am sure, so there are lot of task, that can be solved together
with non recursive CTE.
Show quoted text
Regards,
--
Hitoshi Harada
On Mon, Aug 02, 2010 at 11:50:00PM -0600, Josh Tolley wrote:
In case anyone's interested, I've taken the CTE-based grouping sets
patch from [1] and made it apply to 9.1, attached. I haven't yet
done things like checked it for whitespace consistency, style
conformity, or anything else, but (tuits permitting) hope to figure
out how it works and get it closer to commitability in some upcoming
commitfest.I mention it here so that if someone else is working on it, we can
avoid duplicated effort, and to see if a CTE-based grouping sets
implementation is really the way we think we want to go.[1]
http://archives.postgresql.org/pgsql-hackers/2009-05/msg00700.php
I've added back one file in the patch enclosed here. I'm still
getting compile fails from
CC="ccache gcc" ./configure --prefix=$PG_PREFIX --with-pgport=$PGPORT --with-perl --with-libxml --enable-debug --enable-cassert
make
Log from that also enclosed.
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:
gs91_02.patchtext/plain; charset=us-asciiDownload
diff --git a/src/backend/parser/Makefile b/src/backend/parser/Makefile
index a8f4c07..fb248a6 100644
--- a/src/backend/parser/Makefile
+++ b/src/backend/parser/Makefile
@@ -15,7 +15,7 @@ override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS)
OBJS= analyze.o gram.o keywords.o kwlookup.o parser.o \
parse_agg.o parse_clause.o parse_coerce.o parse_cte.o parse_expr.o \
parse_func.o parse_node.o parse_oper.o parse_param.o parse_relation.o \
- parse_target.o parse_type.o parse_utilcmd.o scansup.o
+ parse_target.o parse_type.o parse_utilcmd.o scansup.o parse_gsets.o
FLEXFLAGS = -CF
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6b99a10..1b579a8 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -34,6 +34,7 @@
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_cte.h"
+#include "parser/parse_gsets.h"
#include "parser/parse_oper.h"
#include "parser/parse_param.h"
#include "parser/parse_relation.h"
@@ -150,6 +151,163 @@ parse_sub_analyze(Node *parseTree, ParseState *parentParseState,
}
/*
+ * process GROUPING SETS
+ */
+static SelectStmt *
+makeSelectStmt(List *targetList, List *fromClause)
+{
+ SelectStmt *n = makeNode(SelectStmt);
+ n->distinctClause = NULL;
+ n->intoClause = NULL;
+ n->targetList = targetList;
+ n->fromClause = fromClause;
+ n->whereClause = NULL;
+ n->groupClause = NULL;
+ n->havingClause = NULL;
+ n->windowClause = NIL;
+ n->withClause = NULL;
+ n->valuesLists = NIL;
+ n->sortClause = NIL;
+ n->limitOffset = NULL;
+ n->limitCount = NULL;
+ n->lockingClause = NIL;
+ n->op = SETOP_NONE;
+ n->all = false;
+ n->larg = NULL;
+ n->rarg = NULL;
+ return n;
+}
+
+static List *
+makeStarTargetList(void)
+{
+ ResTarget *rt = makeNode(ResTarget);
+
+ rt->name = NULL;
+ rt->indirection = NIL;
+ rt->val = (Node *) makeNode(ColumnRef);
+ ((ColumnRef *) rt->val)->fields = list_make1(makeNode(A_Star));
+ rt->location = -1;
+
+ return list_make1(rt);
+}
+
+static SelectStmt *
+transformGroupingSets(ParseState *pstate, SelectStmt *stmt)
+{
+ if (stmt->groupClause && IsA(stmt->groupClause, GroupByClause))
+ {
+ GroupingSetsSpec *gss = (GroupingSetsSpec *) expandGroupingSets(pstate,
+ (List *)((GroupByClause *)stmt->groupClause)->fields);
+
+ if (pstate->p_hasGroupingSets)
+ {
+ CommonTableExpr *cte = makeNode(CommonTableExpr);
+ SelectStmt *cteedstmt;
+ int ngroupingsets = list_length(gss->set_list) + (gss->has_empty_set ? 1 : 0);
+ bool all = ((GroupByClause *) stmt->groupClause)->all;
+
+ cteedstmt = makeSelectStmt(NIL, NIL);
+ cteedstmt->intoClause = stmt->intoClause;
+ cteedstmt->sortClause = stmt->sortClause;
+ cteedstmt->limitOffset = stmt->limitOffset;
+ cteedstmt->limitCount = stmt->limitCount;
+ cteedstmt->lockingClause = stmt->lockingClause;
+
+ cte->ctename = "**g**";
+ cte->ctequery = (Node *) stmt;
+ cte->location = -1;
+
+ cteedstmt->withClause = makeNode(WithClause);
+ cteedstmt->withClause->ctes = list_make1(cte);
+ cteedstmt->withClause->recursive = false;
+ cteedstmt->withClause->location = -1;
+
+ /* when is more than one grouping set, then we should generate setop node */
+ if (ngroupingsets > 1)
+ {
+ /* add quuery under union all for every grouping set */
+ SelectStmt *larg = NULL;
+ SelectStmt *rarg;
+ ListCell *lc;
+
+ foreach(lc, gss->set_list)
+ {
+ List *groupClause;
+
+ Assert(IsA(lfirst(lc), List));
+ groupClause = (List *) lfirst(lc);
+
+ if (larg == NULL)
+ {
+ larg = makeSelectStmt(copyObject(stmt->targetList),
+ list_make1(makeRangeVar(NULL, "**g**", -1)));
+ larg->groupClause = (Node *) groupClause;
+ larg->havingClause = copyObject(stmt->havingClause);
+ }
+ else
+ {
+ SelectStmt *setop = makeSelectStmt(NIL, NIL);
+
+ rarg = makeSelectStmt(copyObject(stmt->targetList),
+ list_make1(makeRangeVar(NULL, "**g**", -1)));
+ rarg->groupClause = (Node *) groupClause;
+ rarg->havingClause = copyObject(stmt->havingClause);
+
+ setop->op = SETOP_UNION;
+ setop->larg = larg;
+ setop->rarg = rarg;
+ setop->all = all;
+
+ larg = setop;
+ }
+ }
+ if (gss->has_empty_set)
+ {
+ SelectStmt *setop = makeSelectStmt(NIL, NIL);
+
+ rarg = makeSelectStmt(copyObject(stmt->targetList),
+ list_make1(makeRangeVar(NULL, "**g**", -1)));
+ rarg->havingClause = copyObject(stmt->havingClause);
+
+ setop->op = SETOP_UNION;
+ setop->larg = larg;
+ setop->rarg = rarg;
+ setop->all = all;
+
+ larg = setop;
+ }
+ /* merge larg to result */
+ cteedstmt->op = larg->op;
+ cteedstmt->larg = larg->larg;
+ cteedstmt->rarg = larg->rarg;
+ cteedstmt->all = larg->all;
+ }
+ else
+ {
+ /* there isn't used setop node */
+ cteedstmt->targetList = copyObject(stmt->targetList);
+ cteedstmt->fromClause = list_make1(makeRangeVar(NULL, "**g**", -1));
+ }
+
+ ((SelectStmt *)cte->ctequery)->targetList = makeStarTargetList();
+ ((SelectStmt *)cte->ctequery)->groupClause = NULL;
+ ((SelectStmt *)cte->ctequery)->sortClause = NIL;
+ ((SelectStmt *)cte->ctequery)->limitOffset = stmt->limitOffset;
+ ((SelectStmt *)cte->ctequery)->limitCount = stmt->limitCount;
+ ((SelectStmt *)cte->ctequery)->lockingClause = stmt->lockingClause;
+
+ return cteedstmt;
+ }
+ else
+ /* trim GroupByClause to groupByClause */
+ stmt->groupClause = (Node *)((GroupByClause *)stmt->groupClause)->fields;
+ }
+
+ return stmt;
+}
+
+/*
* transformStmt -
* transform a Parse tree into a Query tree.
*/
@@ -179,6 +337,8 @@ transformStmt(ParseState *pstate, Node *parseTree)
{
SelectStmt *n = (SelectStmt *) parseTree;
+ n = transformGroupingSets(pstate, n);
+
if (n->valuesLists)
result = transformValuesClause(pstate, n);
else if (n->op == SETOP_NONE)
@@ -827,7 +987,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
false /* allow SQL92 rules */ );
qry->groupClause = transformGroupClause(pstate,
- stmt->groupClause,
+ (List *) stmt->groupClause,
&qry->targetList,
qry->sortClause,
false /* allow SQL92 rules */ );
@@ -924,7 +1084,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
Assert(stmt->targetList == NIL);
Assert(stmt->fromClause == NIL);
Assert(stmt->whereClause == NULL);
- Assert(stmt->groupClause == NIL);
+ Assert(stmt->groupClause == NULL);
Assert(stmt->havingClause == NULL);
Assert(stmt->windowClause == NIL);
Assert(stmt->op == SETOP_NONE);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 1722036..0c9eff1 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -111,6 +111,8 @@ static Node *makeBitStringConst(char *str, int location);
static Node *makeNullAConst(int location);
static Node *makeAConst(Value *v, int location);
static Node *makeBoolAConst(bool state, int location);
+static Node *makeGroupingSetsFunc(GroupingSetsFuncIdentity identity, Node *expr,
+ List *expr_list, int location);
static FuncCall *makeOverlaps(List *largs, List *rargs,
int location, core_yyscan_t yyscanner);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
@@ -292,7 +294,7 @@ static TypeName *TableFuncTypeName(List *columns);
target_list insert_column_list set_target_list
set_clause_list set_clause multiple_set_clause
ctext_expr_list ctext_row def_list indirection opt_indirection
- reloption_list group_clause TriggerFuncArgs select_limit
+ reloption_list TriggerFuncArgs select_limit
opt_select_limit opclass_item_list opclass_drop_list
opt_opfamily transaction_mode_list_or_empty
TableFuncElementList opt_type_modifiers
@@ -437,6 +439,11 @@ static TypeName *TableFuncTypeName(List *columns);
opt_frame_clause frame_extent frame_bound
%type <str> opt_existing_window_name
+%type <node> grouping_element empty_grouping_set grouping_sets_spec
+ group_clause
+%type <list> grouping_element_list
+%type <boolean> opt_grouping_set_quantifier
+
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -472,7 +479,7 @@ static TypeName *TableFuncTypeName(List *columns);
COMMITTED CONCURRENTLY CONFIGURATION CONNECTION CONSTRAINT CONSTRAINTS
CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE CREATEDB
CREATEROLE CREATEUSER CROSS CSV CURRENT_P
- CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
+ CUBE CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
@@ -485,7 +492,7 @@ static TypeName *TableFuncTypeName(List *columns);
FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
FREEZE FROM FULL FUNCTION FUNCTIONS
- GLOBAL GRANT GRANTED GREATEST GROUP_P
+ GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING GROUPING_ID
HANDLER HAVING HEADER_P HOLD HOUR_P
@@ -519,10 +526,10 @@ static TypeName *TableFuncTypeName(List *columns);
RANGE READ REAL REASSIGN RECHECK RECURSIVE REFERENCES REINDEX
RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA RESET RESTART
- RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROW ROWS RULE
+ RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP ROW ROWS RULE
SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
- SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE
+ SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SETS SHARE
SHOW SIMILAR SIMPLE SMALLINT SOME STABLE STANDALONE_P START STATEMENT
STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING SUPERUSER_P
SYMMETRIC SYSID SYSTEM_P
@@ -7797,9 +7804,55 @@ first_or_next: FIRST_P { $$ = 0; }
group_clause:
- GROUP_P BY expr_list { $$ = $3; }
- | /*EMPTY*/ { $$ = NIL; }
- ;
+ GROUP_P BY opt_grouping_set_quantifier grouping_element_list
+ {
+ GroupByClause *clause = makeNode(GroupByClause);
+ clause->all = $3;
+ clause->fields = $4;
+ clause->location = @1;
+ $$ = (Node *) clause;
+ }
+ | /*EMPTY*/
+ {
+ $$ = NULL;
+ }
+ ;
+
+grouping_sets_spec:
+ GROUPING SETS '(' grouping_element_list ')'
+ {
+ /* We cannot identify and drop empty sets yet. */
+ GroupingSetsSpec *gss = makeNode(GroupingSetsSpec);
+ gss->set_list = $4;
+ gss->has_empty_set = false;
+ gss->location = @1;
+ $$ = (Node *) gss;
+ }
+ ;
+
+grouping_element_list:
+ grouping_element { $$ = list_make1($1); }
+ | grouping_element_list ',' grouping_element { $$ = lappend($1, $3); }
+ ;
+
+grouping_element:
+ a_expr { $$ = $1; }
+ | grouping_sets_spec { $$ = $1; }
+ | empty_grouping_set { $$ = $1; }
+ ;
+
+empty_grouping_set:
+ '(' ')'
+ {
+ $$ = makeNode(EmptySet);
+ }
+ ;
+
+opt_grouping_set_quantifier:
+ ALL { $$ = true; }
+ | DISTINCT { $$ = false; }
+ | /*EMPTY*/ { $$ = true; }
+ ;
having_clause:
HAVING a_expr { $$ = $2; }
@@ -9326,7 +9379,30 @@ c_expr: columnref { $$ = $1; }
r->location = @1;
$$ = (Node *)r;
}
- ;
+ | GROUPING '(' a_expr ')'
+ {
+ $$ = makeGroupingSetsFunc(FUNCTION_GROUPING, $3, NIL, @1);
+ }
+ | GROUPING_ID '(' expr_list ')'
+ {
+ $$ = makeGroupingSetsFunc(FUNCTION_GROUPING_ID, NULL, $3, @1);
+ }
+ | CUBE '(' expr_list ')'
+ {
+ /*
+ * Cube should be used in two different contexts. First,
+ * as part of Grouping Sets specification. Second, as
+ * normal UDF function from contrib cube module. When isnot
+ * grouping sets context, then node is transformated to
+ * FuncCall node later.
+ */
+ $$ = makeGroupingSetsFunc(FUNCTION_CUBE, NULL, $3, @1);
+ }
+ | ROLLUP '(' expr_list ')'
+ {
+ $$ = makeGroupingSetsFunc(FUNCTION_ROLLUP, NULL, $3, @1);
+ }
+ ;
/*
* func_expr is split out from c_expr just so that we have a classification
@@ -11043,6 +11119,7 @@ unreserved_keyword:
| SERVER
| SESSION
| SET
+ | SETS
| SHARE
| SHOW
| SIMPLE
@@ -11120,6 +11197,7 @@ col_name_keyword:
| EXTRACT
| FLOAT_P
| GREATEST
+ | GROUPING_ID
| INOUT
| INT_P
| INTEGER
@@ -11214,6 +11292,7 @@ reserved_keyword:
| COLUMN
| CONSTRAINT
| CREATE
+ | CUBE
| CURRENT_CATALOG
| CURRENT_DATE
| CURRENT_ROLE
@@ -11235,6 +11314,7 @@ reserved_keyword:
| FROM
| GRANT
| GROUP_P
+ | GROUPING
| HAVING
| IN_P
| INITIALLY
@@ -11256,6 +11336,7 @@ reserved_keyword:
| PRIMARY
| REFERENCES
| RETURNING
+ | ROLLUP
| SELECT
| SESSION_USER
| SOME
@@ -11761,6 +11842,18 @@ makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args,
return (Node *) x;
}
+static Node *
+makeGroupingSetsFunc(GroupingSetsFuncIdentity identity, Node *expr, List *expr_list, int location)
+{
+ GroupingSetsFunc *gsfunc = makeNode(GroupingSetsFunc);
+
+ gsfunc->identity = identity;
+ gsfunc->expr = expr;
+ gsfunc->expr_list = expr_list;
+ gsfunc->location = location;
+ return (Node *) gsfunc;
+}
+
/* parser_init()
* Initialize to parse one query string
*/
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 0a69bde..3aeea45 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -33,12 +33,20 @@ typedef struct
bool have_non_var_grouping;
int sublevels_up;
} check_ungrouped_columns_context;
+
+typedef struct
+{
+ ParseState *pstate;
+ List *groupClause;
+} transform_ungrouped_target_context;
static void check_ungrouped_columns(Node *node, ParseState *pstate,
List *groupClauses, bool have_non_var_grouping);
static bool check_ungrouped_columns_walker(Node *node,
check_ungrouped_columns_context *context);
+static Node * transform_ungrouped_target(Node *node, ParseState *pstate,
+ List *groupClauses);
/*
* transformAggregateCall -
@@ -405,7 +413,16 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
* WINDOW clauses. For that matter, it's also going to examine the
* grouping expressions themselves --- but they'll all pass the test ...
*/
- clause = (Node *) qry->targetList;
+ if (pstate->parentParseState && pstate->parentParseState->p_hasGroupingSets)
+ {
+ clause = (Node *) transform_ungrouped_target((Node *) qry->targetList,
+ pstate,
+ groupClauses);
+ /* HACK!!! - move to transform part*/
+ qry->targetList = clause;
+ }
+ else
+ clause = (Node *) qry->targetList;
if (hasJoinRTEs)
clause = flatten_join_alias_vars(root, clause);
check_ungrouped_columns(clause, pstate,
@@ -514,6 +531,83 @@ parseCheckWindowFuncs(ParseState *pstate, Query *qry)
}
/*
+ * transform_ungrouped_cols_mutator -
+ * All non aggregates non constatnt columns are replaced by NULL,
+ * grouping and grouping_id functions are replaced by constatnts.
+ */
+static Node *
+transform_ungrouped_target_mutator(Node *node,
+ transform_ungrouped_target_context *context)
+{
+ if (node == NULL)
+ return NULL;
+ if (IsA(node, Aggref))
+ return node;
+
+ if (IsA(node, GroupingSetsFunc))
+ {
+ GroupingSetsFunc *gsf = (GroupingSetsFunc *) node;
+ int result = 0;
+
+ if (gsf->identity == FUNCTION_GROUPING)
+ {
+ result = list_member(context->groupClause, gsf->expr) ? 0 : 1;
+ return (Node *) make_const(context->pstate, makeInteger(result), gsf->location);
+ }
+ else if (gsf->identity == FUNCTION_GROUPING_ID)
+ {
+ ListCell *el;
+
+ foreach(el, gsf->expr_list)
+ {
+ result = result << 1;
+ if (!list_member(context->groupClause, lfirst(el)))
+ result = result | 0x01;
+ }
+ return (Node *) make_const(context->pstate, makeInteger(result), gsf->location);
+ }
+ else /* replace Cube and Rollup by FuncCall node */
+ {
+ /* ToDo: Support cube function */
+ }
+ }
+
+ if (IsA(node, Var))
+ {
+ Var *var = (Var *) node;
+
+ if (list_member(context->groupClause, node))
+ return node;
+ else
+ return (Node *) makeNullConst(var->vartype, var->vartypmod);
+ }
+ else if (IsA(node, FuncExpr))
+ {
+ FuncExpr *fexpr = (FuncExpr *) node;
+
+ if (list_member(context->groupClause, node))
+ return node;
+ else
+ return (Node *) makeNullConst(fexpr->funcresulttype, -1);
+ }
+
+ return expression_tree_mutator(node,
+ transform_ungrouped_target_mutator, context);
+}
+
+static Node *
+transform_ungrouped_target(Node *node, ParseState *pstate,
+ List *groupClauses)
+{
+ transform_ungrouped_target_context context;
+
+ context.pstate = pstate;
+ context.groupClause = groupClauses;
+
+ return transform_ungrouped_target_mutator(node, &context);
+}
+
+/*
* check_ungrouped_columns -
* Scan the given expression tree for ungrouped variables (variables
* that are not listed in the groupClauses list and are not within
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 5e60374..609be1d 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -284,6 +284,25 @@ transformExpr(ParseState *pstate, Node *expr)
case T_BooleanTest:
result = transformBooleanTest(pstate, (BooleanTest *) expr);
break;
+
+ case T_GroupingSetsFunc:
+ {
+ GroupingSetsFunc *gsf = (GroupingSetsFunc *) expr;
+ ListCell *lc;
+ List *expr_list = NIL;
+
+ gsf->expr = (Node *) transformExpr(pstate, (Node *) gsf->expr);
+
+ foreach(lc, gsf->expr_list)
+ {
+ expr_list = lappend(expr_list, transformExpr(pstate,
+ (Node *) lfirst(lc)));
+ }
+ gsf->expr_list = expr_list;
+ result = expr;
+ break;
+ }
+
case T_CurrentOfExpr:
result = transformCurrentOfExpr(pstate, (CurrentOfExpr *) expr);
@@ -320,6 +339,7 @@ transformExpr(ParseState *pstate, Node *expr)
case T_CoerceToDomain:
case T_CoerceToDomainValue:
case T_SetToDefault:
+ case T_EmptySet:
{
result = (Node *) expr;
break;
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index e542dc0..1a3e969 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1586,6 +1586,22 @@ FigureColnameInternal(Node *node, char **name)
case T_XmlSerialize:
*name = "xmlserialize";
return 2;
+ case T_GroupingSetsFunc:
+ switch (((GroupingSetsFunc *) node)->identity)
+ {
+ case FUNCTION_GROUPING:
+ *name = "grouping";
+ return 2;
+ case FUNCTION_GROUPING_ID:
+ *name = "grouping_id";
+ return 2;
+ case FUNCTION_CUBE: /* by compiler quite */
+ case FUNCTION_ROLLUP:
+ /* nothing */
+ break;
+ }
+ break;
+
default:
break;
}
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index a5f5df5..836e38d 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -385,6 +385,10 @@ typedef enum NodeTag
T_XmlSerialize,
T_WithClause,
T_CommonTableExpr,
+ T_GroupingSetsFunc,
+ T_GroupingSetsSpec,
+ T_EmptySet,
+ T_GroupByClause,
/*
* TAGS FOR RANDOM OTHER STUFF
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index fec8d3c..904cb4a 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -378,6 +378,33 @@ typedef struct SortBy
} SortBy;
/*
+ * GroupingSetsSpec - for GROUP BY GROUPING SETS clause
+ */
+typedef struct GroupingSetsSpec
+{
+ NodeTag type;
+ List *set_list;
+ bool has_empty_set; /* true when grouping sets contains empty set */
+ int location;
+} GroupingSetsSpec;
+
+typedef struct EmptySet
+{
+ NodeTag type;
+} EmptySet;
+
+/*
+ * GroupByClause for GROUP BY clause
+ */
+typedef struct GroupByClause
+{
+ NodeTag type;
+ bool all;
+ List *fields;
+ int location;
+} GroupByClause;
+
+/*
* WindowDef - raw representation of WINDOW and OVER clauses
*
* For entries in a WINDOW list, "name" is the window name being defined.
@@ -431,6 +458,26 @@ typedef struct WindowDef
FRAMEOPTION_END_CURRENT_ROW)
/*
+ * GroupingSetsFunc - parser node for Grouping, Grouping_id, Cube and Rollup quasy functions
+ */
+typedef enum GroupingSetsFuncIdentity
+{
+ FUNCTION_GROUPING,
+ FUNCTION_GROUPING_ID,
+ FUNCTION_CUBE,
+ FUNCTION_ROLLUP
+} GroupingSetsFuncIdentity;
+
+typedef struct GroupingSetsFunc
+{
+ NodeTag type;
+ GroupingSetsFuncIdentity identity;
+ Node *expr;
+ List *expr_list;
+ int location;
+} GroupingSetsFunc;
+
+/*
* RangeSubselect - subquery appearing in a FROM clause
*/
typedef struct RangeSubselect
@@ -956,7 +1003,7 @@ typedef struct SelectStmt
List *targetList; /* the target list (of ResTarget) */
List *fromClause; /* the FROM clause */
Node *whereClause; /* WHERE qualification */
- List *groupClause; /* GROUP BY clauses */
+ Node *groupClause; /* GROUP BY clauses */
Node *havingClause; /* HAVING conditional-expression */
List *windowClause; /* WINDOW window_name AS (...), ... */
WithClause *withClause; /* WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 49d4b6c..d0dcfe7 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -99,6 +99,7 @@ PG_KEYWORD("createrole", CREATEROLE, UNRESERVED_KEYWORD)
PG_KEYWORD("createuser", CREATEUSER, UNRESERVED_KEYWORD)
PG_KEYWORD("cross", CROSS, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("csv", CSV, UNRESERVED_KEYWORD)
+PG_KEYWORD("cube", CUBE, RESERVED_KEYWORD)
PG_KEYWORD("current", CURRENT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("current_catalog", CURRENT_CATALOG, RESERVED_KEYWORD)
PG_KEYWORD("current_date", CURRENT_DATE, RESERVED_KEYWORD)
@@ -171,6 +172,8 @@ PG_KEYWORD("grant", GRANT, RESERVED_KEYWORD)
PG_KEYWORD("granted", GRANTED, UNRESERVED_KEYWORD)
PG_KEYWORD("greatest", GREATEST, COL_NAME_KEYWORD)
PG_KEYWORD("group", GROUP_P, RESERVED_KEYWORD)
+PG_KEYWORD("grouping", GROUPING, RESERVED_KEYWORD)
+PG_KEYWORD("grouping_id", GROUPING_ID, COL_NAME_KEYWORD)
PG_KEYWORD("handler", HANDLER, UNRESERVED_KEYWORD)
PG_KEYWORD("having", HAVING, RESERVED_KEYWORD)
PG_KEYWORD("header", HEADER_P, UNRESERVED_KEYWORD)
@@ -318,6 +321,7 @@ PG_KEYWORD("revoke", REVOKE, UNRESERVED_KEYWORD)
PG_KEYWORD("right", RIGHT, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("role", ROLE, UNRESERVED_KEYWORD)
PG_KEYWORD("rollback", ROLLBACK, UNRESERVED_KEYWORD)
+PG_KEYWORD("rollup", ROLLUP, RESERVED_KEYWORD)
PG_KEYWORD("row", ROW, COL_NAME_KEYWORD)
PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD)
PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD)
@@ -336,6 +340,7 @@ PG_KEYWORD("session", SESSION, UNRESERVED_KEYWORD)
PG_KEYWORD("session_user", SESSION_USER, RESERVED_KEYWORD)
PG_KEYWORD("set", SET, UNRESERVED_KEYWORD)
PG_KEYWORD("setof", SETOF, COL_NAME_KEYWORD)
+PG_KEYWORD("sets", SETS, UNRESERVED_KEYWORD)
PG_KEYWORD("share", SHARE, UNRESERVED_KEYWORD)
PG_KEYWORD("show", SHOW, UNRESERVED_KEYWORD)
PG_KEYWORD("similar", SIMILAR, TYPE_FUNC_NAME_KEYWORD)
diff --git a/src/include/parser/parse_gsets.h b/src/include/parser/parse_gsets.h
new file mode 100644
index 0000000..98a9161
--- /dev/null
+++ b/src/include/parser/parse_gsets.h
@@ -0,0 +1,12 @@
+#include "parser/parse_node.h"
+#include "nodes/pg_list.h"
+
+#ifndef PARSE_GETS_H
+#define PARSE_GETS_H
+
+Node *expandGroupingSets(ParseState *pstate, List *grouplist);
+List *transformGroupingSetsSpec(ParseState *pstate, List *groupClause,
+ List **GroupClauses, List **targetLists,
+ List **targetList, List *sortClause);
+
+#endif /* PARSE_GETS_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 1c1383b..f2b80bd 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -107,6 +107,7 @@ struct ParseState
bool p_is_update;
bool p_locked_from_parent;
Relation p_target_relation;
+ bool p_hasGroupingSets;
RangeTblEntry *p_target_rangetblentry;
/*
On Tue, Aug 03, 2010 at 12:58:03PM -0700, David Fetter wrote:
On Mon, Aug 02, 2010 at 11:50:00PM -0600, Josh Tolley wrote:
In case anyone's interested, I've taken the CTE-based grouping sets
patch from [1] and made it apply to 9.1, attached. I haven't yet
done things like checked it for whitespace consistency, style
conformity, or anything else, but (tuits permitting) hope to figure
out how it works and get it closer to commitability in some upcoming
commitfest.I mention it here so that if someone else is working on it, we can
avoid duplicated effort, and to see if a CTE-based grouping sets
implementation is really the way we think we want to go.[1]
http://archives.postgresql.org/pgsql-hackers/2009-05/msg00700.phpI've added back one file in the patch enclosed here. I'm still
getting compile fails fromCC="ccache gcc" ./configure --prefix=$PG_PREFIX --with-pgport=$PGPORT --with-perl --with-libxml --enable-debug --enable-cassert
makeLog from that also enclosed.
Yeah, I seem to have done a poor job of producing the patch based on the
repository I was working from. That said, it seems Pavel's working actively on
a patch anyway, so perhaps my updating the old one isn't all that worthwhile.
Pavel, is your code somewhere that we can get to it?
--
Joshua Tolley / eggyknap
End Point Corporation
http://www.endpoint.com
2010/8/3 Joshua Tolley <eggyknap@gmail.com>:
On Tue, Aug 03, 2010 at 12:58:03PM -0700, David Fetter wrote:
On Mon, Aug 02, 2010 at 11:50:00PM -0600, Josh Tolley wrote:
In case anyone's interested, I've taken the CTE-based grouping sets
patch from [1] and made it apply to 9.1, attached. I haven't yet
done things like checked it for whitespace consistency, style
conformity, or anything else, but (tuits permitting) hope to figure
out how it works and get it closer to commitability in some upcoming
commitfest.I mention it here so that if someone else is working on it, we can
avoid duplicated effort, and to see if a CTE-based grouping sets
implementation is really the way we think we want to go.[1]
http://archives.postgresql.org/pgsql-hackers/2009-05/msg00700.phpI've added back one file in the patch enclosed here. I'm still
getting compile fails fromCC="ccache gcc" ./configure --prefix=$PG_PREFIX --with-pgport=$PGPORT --with-perl --with-libxml --enable-debug --enable-cassert
makeLog from that also enclosed.
Yeah, I seem to have done a poor job of producing the patch based on the
repository I was working from. That said, it seems Pavel's working actively on
a patch anyway, so perhaps my updating the old one isn't all that worthwhile.
Pavel, is your code somewhere that we can get to it?
not now. please wait a week.
Regards
Pavel
Show quoted text
--
Joshua Tolley / eggyknap
End Point Corporation
http://www.endpoint.com-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)iEYEARECAAYFAkxYeiQACgkQRiRfCGf1UMPlEQCff+I4sCGtR+lzUs6Wb5JKi7Uu
3qYAnjLHzHzyMSHHX55QsphkaBbEJ0Zf
=uRqV
-----END PGP SIGNATURE-----
On Wed, Aug 04, 2010 at 04:44:05AM +0200, Pavel Stehule wrote:
Yeah, I seem to have done a poor job of producing the patch based on the
repository I was working from. That said, it seems Pavel's working actively on
a patch anyway, so perhaps my updating the old one isn't all that worthwhile.
Pavel, is your code somewhere that we can get to it?not now. please wait a week.
That works for me. I'm glad to try doing a better job of putting together my
version of the patch, if anyone thinks it's useful, but it seems that since
Pavel's code is due to appear sometime in the foreseeable future, there's not
much point in my doing that.
--
Joshua Tolley / eggyknap
End Point Corporation
http://www.endpoint.com
2010/8/4 Joshua Tolley <eggyknap@gmail.com>:
On Wed, Aug 04, 2010 at 04:44:05AM +0200, Pavel Stehule wrote:
Yeah, I seem to have done a poor job of producing the patch based on the
repository I was working from. That said, it seems Pavel's working actively on
a patch anyway, so perhaps my updating the old one isn't all that worthwhile.
Pavel, is your code somewhere that we can get to it?not now. please wait a week.
That works for me. I'm glad to try doing a better job of putting together my
version of the patch, if anyone thinks it's useful, but it seems that since
Pavel's code is due to appear sometime in the foreseeable future, there's not
much point in my doing that.
I hope, so next week you can do own work on this job - I am not a
native speaker, and my code will need a checking and fixing comments
Regards
Pavel
Show quoted text
--
Joshua Tolley / eggyknap
End Point Corporation
http://www.endpoint.com-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)iEYEARECAAYFAkxZkp8ACgkQRiRfCGf1UMMUcwCfcPayQbWRUYwhpCF1f24LsdD9
H/gAnRzCEq6yLX/RVLLi88ROhurOzbhK
=gUPx
-----END PGP SIGNATURE-----
On Thu, Aug 05, 2010 at 06:21:18AM +0200, Pavel Stehule wrote:
I hope, so next week you can do own work on this job - I am not a
native speaker, and my code will need a checking and fixing comments
I haven't entirely figured out how the code in the old patch works, but I
promise I *can* edit comments/docs :)
--
Joshua Tolley / eggyknap
End Point Corporation
http://www.endpoint.com
Hello
I am sending a updated version.
i hope so there is more comments, longer and more descriptive
identifiers and I fixed a few bugs. But I found some new bugs :(
What is ok:
create table cars(name varchar, place varchar, count integer);
insert into cars values('skoda', 'czech rep.', 10000);
insert into cars values('skoda', 'germany', 5000);
insert into cars values('bmw', 'czech rep.', 100);
insert into cars values('bmw', 'germany', 1000);
insert into cars values('opel', 'czech rep.', 7000);
insert into cars values('opel', 'germany', 7000);
postgres=# select name, place, sum(count) from cars group by ();
name | place | sum
------+-------+-------
| | 30100
(1 row)
postgres=# select name, place, sum(count) from cars group by cube(name, place);
name | place | sum
-------+------------+-------
bmw | czech rep. | 100
skoda | germany | 5000
opel | czech rep. | 7000
opel | germany | 7000
skoda | czech rep. | 10000
bmw | germany | 1000
bmw | | 1100
skoda | | 15000
opel | | 14000
| germany | 13000
| czech rep. | 17100
| | 30100
(12 rows)
postgres=# select name, place, sum(count) from cars group by grouping
sets(name, place),();
name | place | sum
-------+------------+-------
bmw | | 1100
skoda | | 15000
opel | | 14000
| germany | 13000
| czech rep. | 17100
| | 30100
(6 rows)
postgres=# select name, place, sum(count) from cars group by grouping
sets(name, place,()),();
name | place | sum
-------+------------+-------
bmw | | 1100
skoda | | 15000
opel | | 14000
| germany | 13000
| czech rep. | 17100
| | 30100
(6 rows)
postgres=# select name, place, sum(count), grouping(name) from cars
group by grouping sets(name);
name | place | sum | grouping
-------+-------+-------+----------
bmw | | 1100 | 0
skoda | | 15000 | 0
opel | | 14000 | 0
(3 rows)
what is wrong:
postgres=# select name, place from cars group by ();
name | place
-------+------------
skoda | czech rep.
skoda | germany
bmw | czech rep.
bmw | germany
opel | czech rep.
opel | germany
(6 rows)
have to be NULL, NULL
postgres=# select name, place, sum(count), grouping(name) from cars
group by grouping sets(name) having grouping(name) = 1;
ERROR: unrecognized node type: 934
my rewriting rule is applied too late and maybe isn't optimal. I
replace a grouping(x) by const. maybe is better to use a variable.
Same issue is with ORDER BY clause.
So Joshua, can you look on code?
Regards
Pavel Stehule
2010/8/5 Joshua Tolley <eggyknap@gmail.com>:
Show quoted text
On Thu, Aug 05, 2010 at 06:21:18AM +0200, Pavel Stehule wrote:
I hope, so next week you can do own work on this job - I am not a
native speaker, and my code will need a checking and fixing commentsI haven't entirely figured out how the code in the old patch works, but I
promise I *can* edit comments/docs :)--
Joshua Tolley / eggyknap
End Point Corporation
http://www.endpoint.com-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)iEYEARECAAYFAkxaSjEACgkQRiRfCGf1UMM9dQCZASYJUmXLe5i7L4aQnMicwMfy
cu8An3fMdR/ISezw5YV3KsCAOM+BILO1
=uZb+
-----END PGP SIGNATURE-----
Attachments:
verze002.difftext/x-patch; charset=US-ASCII; name=verze002.diffDownload
*** ./src/backend/nodes/copyfuncs.c.orig 2010-07-26 01:21:21.000000000 +0200
--- ./src/backend/nodes/copyfuncs.c 2010-08-02 19:14:28.120786164 +0200
***************
*** 2089,2094 ****
--- 2089,2141 ----
return newnode;
}
+ static GroupBy *
+ _copyGroupBy(GroupBy *from)
+ {
+ GroupBy *newnode = makeNode(GroupBy);
+
+ COPY_SCALAR_FIELD(all);
+ COPY_NODE_FIELD(grouping_sets);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+ }
+
+ static GroupingSetsFunction *
+ _copyGroupingSetsFunction(GroupingSetsFunction *from)
+ {
+ GroupingSetsFunction *newnode = makeNode(GroupingSetsFunction);
+
+ COPY_SCALAR_FIELD(kind);
+ COPY_NODE_FIELD(expr);
+ COPY_NODE_FIELD(expr_list);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+ }
+
+ static GroupingSetsSpecification *
+ _copyGroupingSetsSpecification(GroupingSetsSpecification *from)
+ {
+ GroupingSetsSpecification *newnode = makeNode(GroupingSetsSpecification);
+
+ COPY_NODE_FIELD(grouping_sets);
+ COPY_SCALAR_FIELD(has_empty_set);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+ }
+
+ static GroupingSetsEmptySet *
+ _copyGroupingSetsEmptySet(GroupingSetsEmptySet *from)
+ {
+ GroupingSetsEmptySet *newnode = makeNode(GroupingSetsEmptySet);
+
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+ }
+
static WindowDef *
_copyWindowDef(WindowDef *from)
{
***************
*** 4208,4213 ****
--- 4255,4272 ----
case T_SortBy:
retval = _copySortBy(from);
break;
+ case T_GroupBy:
+ retval = _copyGroupBy(from);
+ break;
+ case T_GroupingSetsFunction:
+ retval = _copyGroupingSetsFunction(from);
+ break;
+ case T_GroupingSetsSpecification:
+ retval = _copyGroupingSetsSpecification(from);
+ break;
+ case T_GroupingSetsEmptySet:
+ retval = _copyGroupingSetsEmptySet(from);
+ break;
case T_WindowDef:
retval = _copyWindowDef(from);
break;
*** ./src/backend/nodes/equalfuncs.c.orig 2010-07-26 01:21:21.000000000 +0200
--- ./src/backend/nodes/equalfuncs.c 2010-08-02 19:17:38.057786174 +0200
***************
*** 2052,2057 ****
--- 2052,2096 ----
}
static bool
+ _equalGroupBy(GroupBy *a, GroupBy *b)
+ {
+ COMPARE_SCALAR_FIELD(all);
+ COMPARE_NODE_FIELD(grouping_sets);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+ }
+
+ static bool
+ _equalGroupingSetsFunction(GroupingSetsFunction *a, GroupingSetsFunction *b)
+ {
+ COMPARE_SCALAR_FIELD(kind);
+ COMPARE_NODE_FIELD(expr);
+ COMPARE_NODE_FIELD(expr_list);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+ }
+
+ static bool
+ _equalGroupingSetsSpecification(GroupingSetsSpecification *a, GroupingSetsSpecification *b)
+ {
+ COMPARE_NODE_FIELD(grouping_sets);
+ COMPARE_SCALAR_FIELD(has_empty_set);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+ }
+
+ static bool
+ _equalGroupingSetsEmptySet(GroupingSetsEmptySet *a, GroupingSetsEmptySet *b)
+ {
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+ }
+
+ static bool
_equalWindowDef(WindowDef *a, WindowDef *b)
{
COMPARE_STRING_FIELD(name);
***************
*** 2875,2880 ****
--- 2914,2931 ----
case T_SortBy:
retval = _equalSortBy(a, b);
break;
+ case T_GroupBy:
+ retval = _equalGroupBy(a, b);
+ break;
+ case T_GroupingSetsFunction:
+ retval = _equalGroupingSetsFunction(a, b);
+ break;
+ case T_GroupingSetsSpecification:
+ retval = _equalGroupingSetsSpecification(a, b);
+ break;
+ case T_GroupingSetsEmptySet:
+ retval = _equalGroupingSetsEmptySet(a, b);
+ break;
case T_WindowDef:
retval = _equalWindowDef(a, b);
break;
*** ./src/backend/nodes/outfuncs.c.orig 2010-07-26 01:21:21.000000000 +0200
--- ./src/backend/nodes/outfuncs.c 2010-08-02 19:20:23.721785739 +0200
***************
*** 2327,2332 ****
--- 2327,2371 ----
}
static void
+ _outGroupBy(StringInfo str, GroupBy *node)
+ {
+ WRITE_NODE_TYPE("GROUPBY");
+
+ WRITE_BOOL_FIELD(all);
+ WRITE_NODE_FIELD(grouping_sets);
+ WRITE_LOCATION_FIELD(location);
+ }
+
+ static void
+ _outGroupingSetsFunction(StringInfo str, GroupingSetsFunction *node)
+ {
+ WRITE_NODE_TYPE("GS_FUNCTION");
+
+ WRITE_ENUM_FIELD(kind, GroupingSets_func_kind);
+ WRITE_NODE_FIELD(expr);
+ WRITE_NODE_FIELD(expr_list);
+ WRITE_LOCATION_FIELD(location);
+ }
+
+ static void
+ _outGroupingSetsSpecification(StringInfo str, GroupingSetsSpecification *node)
+ {
+ WRITE_NODE_TYPE("GS_SPECIFICATION");
+
+ WRITE_NODE_FIELD(grouping_sets);
+ WRITE_BOOL_FIELD(has_empty_set);
+ WRITE_LOCATION_FIELD(location);
+ }
+
+ static void
+ _outGroupingSetsEmptySet(StringInfo str, GroupingSetsEmptySet *node)
+ {
+ WRITE_NODE_TYPE("GS_EMPTY_SET");
+
+ WRITE_LOCATION_FIELD(location);
+ }
+
+ static void
_outWindowDef(StringInfo str, WindowDef *node)
{
WRITE_NODE_TYPE("WINDOWDEF");
***************
*** 2881,2886 ****
--- 2920,2937 ----
case T_SortBy:
_outSortBy(str, obj);
break;
+ case T_GroupBy:
+ _outGroupBy(str, obj);
+ break;
+ case T_GroupingSetsFunction:
+ _outGroupingSetsFunction(str, obj);
+ break;
+ case T_GroupingSetsSpecification:
+ _outGroupingSetsSpecification(str, obj);
+ break;
+ case T_GroupingSetsEmptySet:
+ _outGroupingSetsEmptySet(str, obj);
+ break;
case T_WindowDef:
_outWindowDef(str, obj);
break;
*** ./src/backend/nodes/readfuncs.c.orig 2010-02-16 23:34:43.000000000 +0100
--- ./src/backend/nodes/readfuncs.c 2010-08-02 19:23:33.522911219 +0200
***************
*** 268,273 ****
--- 268,323 ----
}
/*
+ * _readGroupBy
+ */
+ static GroupBy *
+ _readGroupBy(void)
+ {
+ READ_LOCALS(GroupBy);
+
+ READ_BOOL_FIELD(all);
+ READ_NODE_FIELD(grouping_sets);
+ READ_LOCATION_FIELD(location);
+
+ READ_DONE();
+ }
+
+ static GroupingSetsFunction *
+ _readGroupingSetsFunction(void)
+ {
+ READ_LOCALS(GroupingSetsFunction);
+
+ READ_ENUM_FIELD(kind, GroupingSets_func_kind);
+ READ_NODE_FIELD(expr);
+ READ_NODE_FIELD(expr_list);
+ READ_LOCATION_FIELD(location);
+
+ READ_DONE();
+ }
+
+ static GroupingSetsSpecification *
+ _readGroupingSetsSpecification(void)
+ {
+ READ_LOCALS(GroupingSetsSpecification);
+
+ READ_NODE_FIELD(grouping_sets);
+ READ_BOOL_FIELD(has_empty_set);
+ READ_LOCATION_FIELD(location);
+
+ READ_DONE();
+ }
+
+ static GroupingSetsEmptySet *
+ _readGroupingSetsEmptySet(void)
+ {
+ READ_LOCALS(GroupingSetsEmptySet);
+
+ READ_LOCATION_FIELD(location);
+
+ READ_DONE();
+ }
+
+ /*
* _readWindowClause
*/
static WindowClause *
***************
*** 1200,1205 ****
--- 1250,1263 ----
return_value = _readQuery();
else if (MATCH("SORTGROUPCLAUSE", 15))
return_value = _readSortGroupClause();
+ else if (MATCH("GROUPBY", 7))
+ return_value = _readGroupBy();
+ else if (MATCH("GS_FUNCTION", 11))
+ return_value = _readGroupingSetsFunction();
+ else if (MATCH("GS_SPECIFICATION", 16))
+ return_value = _readGroupingSetsSpecification();
+ else if (MATCH("GS_EMPTY_SET", 12))
+ return_value = _readGroupingSetsEmptySet();
else if (MATCH("WINDOWCLAUSE", 12))
return_value = _readWindowClause();
else if (MATCH("ROWMARKCLAUSE", 13))
*** ./src/backend/parser/analyze.c.orig 2010-08-05 12:54:54.343032233 +0200
--- ./src/backend/parser/analyze.c 2010-08-05 16:14:39.134031905 +0200
***************
*** 63,68 ****
--- 63,69 ----
ExplainStmt *stmt);
static void transformLockingClause(ParseState *pstate, Query *qry,
LockingClause *lc, bool pushedDown);
+ static SelectStmt *transformGroupingSets(ParseState *pstate, SelectStmt *stmt);
/*
***************
*** 178,183 ****
--- 179,186 ----
case T_SelectStmt:
{
SelectStmt *n = (SelectStmt *) parseTree;
+
+ n = transformGroupingSets(pstate, n);
if (n->valuesLists)
result = transformValuesClause(pstate, n);
***************
*** 924,930 ****
Assert(stmt->targetList == NIL);
Assert(stmt->fromClause == NIL);
Assert(stmt->whereClause == NULL);
! Assert(stmt->groupClause == NIL);
Assert(stmt->havingClause == NULL);
Assert(stmt->windowClause == NIL);
Assert(stmt->op == SETOP_NONE);
--- 927,933 ----
Assert(stmt->targetList == NIL);
Assert(stmt->fromClause == NIL);
Assert(stmt->whereClause == NULL);
! Assert(stmt->groupClause == NULL);
Assert(stmt->havingClause == NULL);
Assert(stmt->windowClause == NIL);
Assert(stmt->op == SETOP_NONE);
***************
*** 2242,2244 ****
--- 2245,2417 ----
qry->rowMarks = lappend(qry->rowMarks, rc);
}
+
+ /*
+ * generate parser's node for simple SELECT
+ */
+ static SelectStmt *
+ makeSelectStmt(List *targetList, List *fromClause)
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+ n->distinctClause = NULL;
+ n->intoClause = NULL;
+ n->targetList = targetList;
+ n->fromClause = fromClause;
+ n->whereClause = NULL;
+ n->groupClause = NULL;
+ n->havingClause = NULL;
+ n->windowClause = NIL;
+ n->withClause = NULL;
+ n->valuesLists = NIL;
+ n->sortClause = NIL;
+ n->limitOffset = NULL;
+ n->limitCount = NULL;
+ n->lockingClause = NIL;
+ n->op = SETOP_NONE;
+ n->all = false;
+ n->larg = NULL;
+ n->rarg = NULL;
+ return n;
+ }
+
+ /*
+ * generate parser's node for star target
+ */
+ static List *
+ makeStarTargetList(void)
+ {
+ ResTarget *rt = makeNode(ResTarget);
+
+ rt->name = NULL;
+ rt->indirection = NIL;
+ rt->val = (Node *) makeNode(ColumnRef);
+ ((ColumnRef *) rt->val)->fields = list_make1(makeNode(A_Star));
+ rt->location = -1;
+
+ return list_make1(rt);
+ }
+
+ /*
+ * transform a GROUP BY GROUPING SETS to CTE
+ */
+ static SelectStmt *
+ transformGroupingSets(ParseState *pstate, SelectStmt *stmt)
+ {
+ if (stmt->groupClause && IsA(stmt->groupClause, GroupBy))
+ {
+ GroupingSetsSpecification *gss = (GroupingSetsSpecification *) evalGroupingSets(pstate,
+ (List *)((GroupBy *)stmt->groupClause)->grouping_sets);
+
+ if (pstate->p_hasGroupingSets)
+ {
+ CommonTableExpr *cte = makeNode(CommonTableExpr);
+ SelectStmt *cteedstmt;
+ int ngroupingsets = list_length(gss->grouping_sets) + (gss->has_empty_set ? 1 : 0);
+ bool all = ((GroupBy *) stmt->groupClause)->all;
+
+ cteedstmt = makeSelectStmt(NIL, NIL);
+ cteedstmt->intoClause = stmt->intoClause;
+ cteedstmt->sortClause = stmt->sortClause;
+ cteedstmt->limitOffset = stmt->limitOffset;
+ cteedstmt->limitCount = stmt->limitCount;
+ cteedstmt->lockingClause = stmt->lockingClause;
+
+ cte->ctename = "GroupingSets";
+ cte->ctequery = (Node *) stmt;
+ cte->location = -1;
+
+ cteedstmt->withClause = makeNode(WithClause);
+ cteedstmt->withClause->ctes = list_make1(cte);
+ cteedstmt->withClause->recursive = false;
+ cteedstmt->withClause->location = -1;
+
+ /* when is more than one grouping set, then we should generate setop node */
+ if (ngroupingsets > 1)
+ {
+ /* add quuery under union all for every grouping set */
+ SelectStmt *larg = NULL;
+ SelectStmt *rarg;
+ ListCell *lc;
+
+ foreach(lc, gss->grouping_sets)
+ {
+ List *groupClause;
+
+ Assert(IsA(lfirst(lc), List));
+ groupClause = (List *) lfirst(lc);
+
+ if (larg == NULL)
+ {
+ larg = makeSelectStmt(copyObject(stmt->targetList),
+ list_make1(makeRangeVar(NULL, "GroupingSets", -1)));
+ larg->groupClause = (Node *) groupClause;
+ larg->havingClause = copyObject(stmt->havingClause);
+ }
+ else
+ {
+ SelectStmt *setop = makeSelectStmt(NIL, NIL);
+
+ rarg = makeSelectStmt(copyObject(stmt->targetList),
+ list_make1(makeRangeVar(NULL, "GroupingSets", -1)));
+ rarg->groupClause = (Node *) groupClause;
+ rarg->havingClause = copyObject(stmt->havingClause);
+
+ setop->op = SETOP_UNION;
+ setop->larg = larg;
+ setop->rarg = rarg;
+ setop->all = all;
+
+ larg = setop;
+ }
+ }
+ if (gss->has_empty_set)
+ {
+ SelectStmt *setop = makeSelectStmt(NIL, NIL);
+
+ rarg = makeSelectStmt(copyObject(stmt->targetList),
+ list_make1(makeRangeVar(NULL, "GroupingSets", -1)));
+ rarg->havingClause = copyObject(stmt->havingClause);
+
+ setop->op = SETOP_UNION;
+ setop->larg = larg;
+ setop->rarg = rarg;
+ setop->all = all;
+
+ larg = setop;
+ }
+ /* merge larg to result */
+ cteedstmt->op = larg->op;
+ cteedstmt->larg = larg->larg;
+ cteedstmt->rarg = larg->rarg;
+ cteedstmt->all = larg->all;
+ }
+ else if (ngroupingsets == 1)
+ {
+ /* there isn't used setop node */
+ cteedstmt->targetList = copyObject(stmt->targetList);
+ cteedstmt->fromClause = list_make1(makeRangeVar(NULL, "GroupingSets", -1));
+ cteedstmt->havingClause = copyObject(stmt->havingClause);
+
+ if (gss->grouping_sets != NIL)
+ {
+ cteedstmt->groupClause = linitial(gss->grouping_sets);
+
+ }
+ }
+
+ ((SelectStmt *)cte->ctequery)->targetList = makeStarTargetList();
+ ((SelectStmt *)cte->ctequery)->groupClause = NULL;
+ ((SelectStmt *)cte->ctequery)->sortClause = NIL;
+ ((SelectStmt *)cte->ctequery)->limitOffset = stmt->limitOffset;
+ ((SelectStmt *)cte->ctequery)->limitCount = stmt->limitCount;
+ ((SelectStmt *)cte->ctequery)->lockingClause = stmt->lockingClause;
+
+ return cteedstmt;
+ }
+ else
+ /* trim GroupByClause to groupByClause */
+ stmt->groupClause = (Node *)((GroupBy *)stmt->groupClause)->grouping_sets;
+ }
+
+ return stmt;
+ }
*** ./src/backend/parser/gram.y.orig 2010-07-26 01:21:21.000000000 +0200
--- ./src/backend/parser/gram.y 2010-08-05 13:48:40.565033096 +0200
***************
*** 437,442 ****
--- 437,446 ----
opt_frame_clause frame_extent frame_bound
%type <str> opt_existing_window_name
+ %type <node> gsets_element gsets_empty_set gsets_specification
+ %type <list> gsets_element_list
+ %type <boolean> opt_gsets_quantifier
+
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
***************
*** 471,477 ****
CLUSTER COALESCE COLLATE COLUMN COMMENT COMMENTS COMMIT
COMMITTED CONCURRENTLY CONFIGURATION CONNECTION CONSTRAINT CONSTRAINTS
CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE CREATEDB
! CREATEROLE CREATEUSER CROSS CSV CURRENT_P
CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
--- 475,481 ----
CLUSTER COALESCE COLLATE COLUMN COMMENT COMMENTS COMMIT
COMMITTED CONCURRENTLY CONFIGURATION CONNECTION CONSTRAINT CONSTRAINTS
CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE CREATEDB
! CREATEROLE CREATEUSER CROSS CSV CUBE CURRENT_P
CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
***************
*** 485,491 ****
FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
FREEZE FROM FULL FUNCTION FUNCTIONS
! GLOBAL GRANT GRANTED GREATEST GROUP_P
HANDLER HAVING HEADER_P HOLD HOUR_P
--- 489,495 ----
FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
FREEZE FROM FULL FUNCTION FUNCTIONS
! GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING GROUPING_ID
HANDLER HAVING HEADER_P HOLD HOUR_P
***************
*** 519,528 ****
RANGE READ REAL REASSIGN RECHECK RECURSIVE REFERENCES REINDEX
RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA RESET RESTART
! RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROW ROWS RULE
SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
! SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE
SHOW SIMILAR SIMPLE SMALLINT SOME STABLE STANDALONE_P START STATEMENT
STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING SUPERUSER_P
SYMMETRIC SYSID SYSTEM_P
--- 523,532 ----
RANGE READ REAL REASSIGN RECHECK RECURSIVE REFERENCES REINDEX
RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA RESET RESTART
! RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP ROW ROWS RULE
SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
! SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SETS SHARE
SHOW SIMILAR SIMPLE SMALLINT SOME STABLE STANDALONE_P START STATEMENT
STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING SUPERUSER_P
SYMMETRIC SYSID SYSTEM_P
***************
*** 7795,7804 ****
| NEXT { $$ = 0; }
;
-
group_clause:
! GROUP_P BY expr_list { $$ = $3; }
! | /*EMPTY*/ { $$ = NIL; }
;
having_clause:
--- 7799,7851 ----
| NEXT { $$ = 0; }
;
group_clause:
! GROUP_P BY opt_gsets_quantifier gsets_element_list
! {
! GroupBy *n = makeNode(GroupBy);
! n->all = $3;
! n->grouping_sets = $4;
! n->location = @1;
! $$ = (Node *) n;
! }
! | /*EMPTY*/
! {
! $$ = NIL;
! }
! ;
!
! gsets_specification:
! GROUPING SETS '(' gsets_element_list ')'
! {
! GroupingSetsSpecification *gss = makeNode(GroupingSetsSpecification);
! gss->grouping_sets = $4;
! gss->has_empty_set = false; /* not known now */
! gss->location = @1;
! $$ = (Node *) gss;
! }
!
! gsets_element_list:
! gsets_element { $$ = list_make1($1); }
! | gsets_element_list ',' gsets_element { $$ = lappend($1, $3); }
! ;
!
! gsets_element:
! a_expr { $$ = $1; }
! | gsets_specification { $$ = $1; }
! | gsets_empty_set { $$ = $1; }
! ;
!
! gsets_empty_set:
! '(' ')'
! {
! $$ = (Node *) makeNode(GroupingSetsEmptySet);
! }
! ;
!
! opt_gsets_quantifier:
! ALL { $$ = true; }
! | DISTINCT { $$ = false; }
! | /*EMPTY*/ { $$ = true; }
;
having_clause:
***************
*** 9874,9879 ****
--- 9921,9970 ----
n->location = @1;
$$ = (Node *)n;
}
+ | GROUPING '(' a_expr ')'
+ {
+ GroupingSetsFunction *gsf = makeNode(GroupingSetsFunction);
+ gsf->kind = GROUPINGSETS_FUNCTION_GROUPING;
+ gsf->expr = $3;
+ gsf->expr_list = NIL;
+ gsf->location = @1;
+ $$ = (Node *) gsf;
+ }
+ | GROUPING_ID '(' expr_list ')'
+ {
+ GroupingSetsFunction *gsf = makeNode(GroupingSetsFunction);
+ gsf->kind = GROUPINGSETS_FUNCTION_GROUPING_ID;
+ gsf->expr = NULL;
+ gsf->expr_list = $3;
+ gsf->location = @1;
+ $$ = (Node *) gsf;
+ }
+ | CUBE '(' expr_list ')'
+ {
+ /*
+ * Cube() and Rollup() are processed differently - depends on
+ * context - it can be used as Grouping Sets Operator (inside
+ * grouping sets specification) or it can be used as FuncCall.
+ * CUBE is keyword in ANSI SQL, but it cannot be a reserved
+ * keyword, because contrib "cube" module uses it. There isn't
+ * reason to process ROLLUP diferently than CUBE.
+ */
+ GroupingSetsFunction *gsf = makeNode(GroupingSetsFunction);
+ gsf->kind = GROUPINGSETS_FUNCTION_CUBE;
+ gsf->expr = NULL;
+ gsf->expr_list = $3;
+ gsf->location = @1;
+ $$ = (Node *) gsf;
+ }
+ | ROLLUP '(' expr_list ')'
+ {
+ GroupingSetsFunction *gsf = makeNode(GroupingSetsFunction);
+ gsf->kind = GROUPINGSETS_FUNCTION_ROLLUP;
+ gsf->expr = NULL;
+ gsf->expr_list = $3;
+ gsf->location = @1;
+ $$ = (Node *) gsf;
+ }
;
/*
***************
*** 11043,11048 ****
--- 11134,11140 ----
| SERVER
| SESSION
| SET
+ | SETS
| SHARE
| SHOW
| SIMPLE
***************
*** 11120,11125 ****
--- 11212,11218 ----
| EXTRACT
| FLOAT_P
| GREATEST
+ | GROUPING_ID
| INOUT
| INT_P
| INTEGER
***************
*** 11214,11219 ****
--- 11307,11313 ----
| COLUMN
| CONSTRAINT
| CREATE
+ | CUBE
| CURRENT_CATALOG
| CURRENT_DATE
| CURRENT_ROLE
***************
*** 11235,11240 ****
--- 11329,11335 ----
| FROM
| GRANT
| GROUP_P
+ | GROUPING
| HAVING
| IN_P
| INITIALLY
***************
*** 11256,11261 ****
--- 11351,11357 ----
| PRIMARY
| REFERENCES
| RETURNING
+ | ROLLUP
| SELECT
| SESSION_USER
| SOME
*** ./src/backend/parser/Makefile.orig 2010-01-05 04:56:52.000000000 +0100
--- ./src/backend/parser/Makefile 2010-08-05 08:30:53.039043502 +0200
***************
*** 15,21 ****
OBJS= analyze.o gram.o keywords.o kwlookup.o parser.o \
parse_agg.o parse_clause.o parse_coerce.o parse_cte.o parse_expr.o \
parse_func.o parse_node.o parse_oper.o parse_param.o parse_relation.o \
! parse_target.o parse_type.o parse_utilcmd.o scansup.o
FLEXFLAGS = -CF
--- 15,21 ----
OBJS= analyze.o gram.o keywords.o kwlookup.o parser.o \
parse_agg.o parse_clause.o parse_coerce.o parse_cte.o parse_expr.o \
parse_func.o parse_node.o parse_oper.o parse_param.o parse_relation.o \
! parse_target.o parse_type.o parse_utilcmd.o scansup.o parse_gsets.o
FLEXFLAGS = -CF
*** ./src/backend/parser/parse_agg.c.orig 2010-03-17 17:52:38.000000000 +0100
--- ./src/backend/parser/parse_agg.c 2010-08-05 16:09:45.804033853 +0200
***************
*** 34,43 ****
--- 34,51 ----
int sublevels_up;
} check_ungrouped_columns_context;
+ typedef struct
+ {
+ ParseState *pstate;
+ List *groupClause;
+ } transform_ungrouped_target_context;
+
+
static void check_ungrouped_columns(Node *node, ParseState *pstate,
List *groupClauses, bool have_non_var_grouping);
static bool check_ungrouped_columns_walker(Node *node,
check_ungrouped_columns_context *context);
+ static Node *transform_ungrouped_target(Node *node, ParseState *pstate, List *groupClauses);
/*
***************
*** 397,402 ****
--- 405,422 ----
}
}
+ /* check this state (for one set grouping sets), or parent state (for multiple grouping sets */
+ if (pstate->p_hasGroupingSets || (pstate->parentParseState && pstate->parentParseState->p_hasGroupingSets))
+ {
+ clause = (Node *) transform_ungrouped_target((Node *) qry->targetList,
+ pstate,
+ groupClauses);
+ /* HACK!!! - move to transform part*/
+ qry->targetList = (List *) clause;
+ }
+ else
+ clause = (Node *) qry->targetList;
+
/*
* Check the targetlist and HAVING clause for ungrouped variables.
*
***************
*** 405,411 ****
* WINDOW clauses. For that matter, it's also going to examine the
* grouping expressions themselves --- but they'll all pass the test ...
*/
- clause = (Node *) qry->targetList;
if (hasJoinRTEs)
clause = flatten_join_alias_vars(root, clause);
check_ungrouped_columns(clause, pstate,
--- 425,430 ----
***************
*** 741,743 ****
--- 760,835 ----
args,
COERCE_DONTCARE);
}
+
+ /*
+ * transform_ungrouped_cols_mutator -
+ * All non aggregates, all non constatnt columns are replaced by NULL,
+ * grouping and grouping_id functions are replaced by adequate constatnts.
+ */
+ static Node *
+ transform_ungrouped_target_mutator(Node *node,
+ transform_ungrouped_target_context *context)
+ {
+ if (node == NULL)
+ return NULL;
+ if (IsA(node, Aggref))
+ return node;
+
+ if (IsA(node, GroupingSetsFunction))
+ {
+ GroupingSetsFunction *gsf = (GroupingSetsFunction *) node;
+ int result = 0;
+
+ if (gsf->kind == GROUPINGSETS_FUNCTION_GROUPING)
+ {
+ result = list_member(context->groupClause, gsf->expr) ? 0 : 1;
+ return (Node *) make_const(context->pstate, makeInteger(result), gsf->location);
+ }
+ else if (gsf->kind == GROUPINGSETS_FUNCTION_GROUPING_ID)
+ {
+ ListCell *el;
+
+ foreach(el, gsf->expr_list)
+ {
+ result = result << 1;
+ if (!list_member(context->groupClause, lfirst(el)))
+ result = result | 0x01;
+ }
+ return (Node *) make_const(context->pstate, makeInteger(result), gsf->location);
+ }
+ }
+
+ if (IsA(node, Var))
+ {
+ Var *var = (Var *) node;
+
+ if (list_member(context->groupClause, node))
+ return node;
+ else
+ return (Node *) makeNullConst(var->vartype, var->vartypmod);
+ }
+ else if (IsA(node, FuncExpr))
+ {
+ FuncExpr *fexpr = (FuncExpr *) node;
+
+ if (list_member(context->groupClause, node))
+ return node;
+ else
+ return (Node *) makeNullConst(fexpr->funcresulttype, -1);
+ }
+
+ return expression_tree_mutator(node,
+ transform_ungrouped_target_mutator, context);
+ }
+
+ static Node *
+ transform_ungrouped_target(Node *node, ParseState *pstate,
+ List *groupClauses)
+ {
+ transform_ungrouped_target_context context;
+
+ context.pstate = pstate;
+ context.groupClause = groupClauses;
+
+ return transform_ungrouped_target_mutator(node, &context);
+ }
*** ./src/backend/parser/parse_expr.c.orig 2010-07-06 21:18:57.000000000 +0200
--- ./src/backend/parser/parse_expr.c 2010-08-05 13:29:22.273031825 +0200
***************
*** 293,298 ****
--- 293,357 ----
result = transformCurrentOfExpr(pstate, (CurrentOfExpr *) expr);
break;
+ case T_GroupingSetsFunction:
+ {
+ GroupingSetsFunction *gsf = (GroupingSetsFunction *) expr;
+
+ /*
+ * Because there are parser's conflict between a_expr rules and CUBE()
+ * and ROLLUP() operator, we have to do decision later by context. This
+ * transformation replace unevaluated gs operators by typical funccall.
+ * "cube" cannot be a reserved keyword. Contrib module contains a few
+ * functions "cube".
+ */
+ if (gsf->kind == GROUPINGSETS_FUNCTION_CUBE || gsf->kind == GROUPINGSETS_FUNCTION_ROLLUP)
+ {
+ FuncCall *fc = makeNode(FuncCall);
+
+ if (gsf->kind == GROUPINGSETS_FUNCTION_CUBE)
+ {
+ /* for cube() */
+ fc->funcname = list_make1(makeString("cube"));
+ }
+ else
+ {
+ /* for rollup() */
+ fc->funcname = list_make1(makeString("rollup"));
+ }
+
+ fc->args = gsf->expr_list;
+ fc->agg_order = NIL;
+ fc->agg_star = false;
+ fc->agg_distinct = false;
+ fc->func_variadic = false;
+ fc->over = NULL;
+ fc->location = gsf->location;
+ result = transformFuncCall(pstate, (FuncCall *) fc);
+ }
+ else if (gsf->kind == GROUPINGSETS_FUNCTION_GROUPING)
+ {
+ /* this parser's node will be replaced later by NULL or constant */
+ gsf->expr = transformExpr(pstate, gsf->expr);
+ result = (Node *) gsf;
+ }
+ else if (gsf->kind == GROUPINGSETS_FUNCTION_GROUPING_ID)
+ {
+ List *expr_list = NIL;
+ ListCell *lc;
+
+ /* this parser's node will be replaced later by NULL or constant */
+ foreach(lc, gsf->expr_list)
+ {
+ expr_list = lappend(expr_list,
+ transformExpr(pstate,
+ (Node *) lfirst(lc)));
+ }
+ gsf->expr_list = expr_list;
+ result = (Node *) gsf;
+ }
+ }
+ break;
+
/*********************************************
* Quietly accept node types that may be presented when we are
* called on an already-transformed tree.
***************
*** 324,329 ****
--- 383,389 ----
case T_CoerceToDomain:
case T_CoerceToDomainValue:
case T_SetToDefault:
+ case T_GroupingSetsEmptySet:
{
result = (Node *) expr;
break;
*** ./src/backend/parser/parse_gsets.c.orig 2010-08-03 14:24:18.042985208 +0200
--- ./src/backend/parser/parse_gsets.c 2010-08-05 15:09:44.722157284 +0200
***************
*** 0 ****
--- 1,637 ----
+ /*-------------------------------------------------------------------------
+ *
+ * parse_gsets.c
+ * parser grouping sets transformations
+ *
+ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * $PostgreSQL: $
+ *
+ *-------------------------------------------------------------------------
+ */
+ #include "postgres.h"
+ #include "catalog/namespace.h"
+ #include "nodes/nodes.h"
+ #include "nodes/makefuncs.h"
+ #include "nodes/nodeFuncs.h"
+ #include "nodes/value.h"
+ #include "nodes/pg_list.h"
+ #include "nodes/parsenodes.h"
+ #include "optimizer/tlist.h"
+ #include "parser/parse_clause.h"
+ #include "parser/parse_node.h"
+ #include "rewrite/rewriteManip.h"
+
+ #include <string.h>
+
+ #define MAX_CUBE_FIELDS 8
+
+ #define is_empty_list(n) (n == NIL)
+ #define is_undef_loc(loc) (loc == -1)
+ #define IsGroupingSetsSpecification(n) (IsA(n, GroupingSetsSpecification))
+
+ typedef struct
+ {
+ ParseState *pstate;
+ List *groupClause;
+ bool isGlobalTL;
+ } transform_ungroup_cols_context;
+
+ static List *add_distinct_set(List *gsets_list, List *gsets);
+ static List *add_distinct_sets(List *cum_gsets_list, List *gsets_list);
+ static Node *evalGroupByClause(List *element_list, bool has_empty_set);
+ static Node *evalGroupingSetsOp(ParseState *pstate, Node *node);
+
+ /*
+ * return true, when GroupingSet is empty
+ */
+ static bool
+ is_empty_grouping_set(Node *node)
+ {
+ if (node == NULL)
+ return true;
+ if (IsA(node, GroupingSetsEmptySet))
+ return true;
+ if (IsGroupingSetsSpecification(node))
+ return is_empty_list(((GroupingSetsSpecification *) node)->grouping_sets);
+
+ return false;
+ }
+
+ /*
+ * expandGroupingSets do:
+ * a) search end replace cube and rollup funccall nodes by GroupingSetsSpec
+ * nodes,
+ * b) multiply all set in GroupBy list,
+ * c) drop all None nodes.
+ *
+ */
+ Node *
+ evalGroupingSets(ParseState *pstate, List *gsets_element_list)
+ {
+ ListCell *g;
+ List *grouping_sets = NIL;
+ bool has_empty_set = false;
+ int location = -1; /* undefined location now */
+ bool use_grouping_sets = false;
+
+ /* leave fast when grouplist is NIL */
+ if (is_empty_list(gsets_element_list))
+ return NULL;
+
+ /* detect single group by grouping set */
+ foreach(g, gsets_element_list)
+ {
+ Node *el = (Node *) lfirst(g);
+ Node *evaluated_element;
+
+ /*
+ * Is it empty set. NULL is short cut for empty set.
+ * This has disadvantage - we lost location, but it is short.
+ */
+ if (is_empty_grouping_set(el))
+ {
+ has_empty_set = true;
+ continue;
+ }
+
+ /* take location from first item of list */
+ if (is_undef_loc(location))
+ location = exprLocation(el);
+
+ evaluated_element = evalGroupingSetsOp(pstate, el);
+ if (is_empty_grouping_set(evaluated_element))
+ {
+ has_empty_set = true;
+ continue;
+ }
+
+ if (IsA(el, GroupingSetsSpecification))
+ use_grouping_sets = true;
+ else if (IsA(el, GroupingSetsFunction))
+ {
+ switch (((GroupingSetsFunction *) el)->kind)
+ {
+ case GROUPINGSETS_FUNCTION_CUBE:
+ case GROUPINGSETS_FUNCTION_ROLLUP:
+ use_grouping_sets = true;
+ break;
+ default:
+ break; /* be compiler quiete */
+ }
+ }
+
+ grouping_sets = lappend(grouping_sets, evaluated_element);
+ }
+
+ /* return original list of fields, when grouping sets are not used */
+ if (!use_grouping_sets && !has_empty_set)
+ return (Node *) gsets_element_list;
+
+ /* multiple all sets in GROUP BY list */
+ pstate->p_hasGroupingSets = true;
+
+ return (Node *) evalGroupByClause(grouping_sets, has_empty_set);
+ }
+
+ /*
+ * This function has some purposes - first take list from row expression,
+ * second - remove all empty sets and remove all duplicate sets,
+ * ToDo: add flag to RowExpr that carry info about explicit ROW keyword
+ * because GROUPING SETS (a, (a,b)) is different from
+ * GROUPING SETS (a, ROW(a,b))
+ */
+ static List **
+ prepareElements(List *elements, int *nfields, bool *has_empty_set)
+ {
+ ListCell *l;
+ List *felements = NIL; /* filtered elements, without empty sets */
+ int _nfields;
+ List **result;
+ int i = 0;
+
+ *has_empty_set = false;
+ foreach(l, elements)
+ {
+ if (is_empty_grouping_set((Node *) lfirst(l)))
+ {
+ *has_empty_set = true;
+ continue;
+ }
+
+ felements = list_append_unique(felements, lfirst(l));
+ }
+
+ if (is_empty_list(felements))
+ return NULL;
+
+ _nfields = list_length(felements);
+ result = palloc(_nfields * sizeof(List *));
+
+ foreach(l, felements)
+ {
+ Node *el = (Node *) lfirst(l);
+
+ switch (nodeTag(el))
+ {
+ case T_RowExpr:
+ result[i++] = ((RowExpr *) el)->args;
+ break;
+ default:
+ result[i++] = list_make1(el);
+
+ }
+ }
+
+ *nfields = _nfields;
+ return result;
+ }
+
+ /*
+ * This function add one grouping set to other. Result is list of distinct sets,
+ * etc list of distinct list.
+ *
+ * Note: the distinct in function name is not related to ALL|DISTINCT quantifier
+ * of GROUP BY clause
+ */
+ static List *
+ add_distinct_set(List *gsets_list, List *gsets)
+ {
+
+ List *uniqset = NIL; /* unique gsets */
+ ListCell *l;
+ int act_len;
+ bool found = false;
+
+ /*
+ * ensure so all fields in gsets_list are unique,
+ * all new items are unique there.
+ */
+ foreach (l, gsets)
+ uniqset = list_append_unique(uniqset, lfirst(l));
+
+ /* same lists has same length */
+ act_len = list_length(uniqset);
+
+ foreach(l, gsets_list)
+ {
+ Node *node = (Node *) lfirst(l);
+
+ Assert(IsA(node, List));
+ if (list_length((List *) node) == act_len)
+ {
+ ListCell *lc;
+
+ found = true;
+ foreach(lc, (List *) node)
+ if (!list_member(uniqset, (Node *) lfirst(lc)))
+ {
+ found = false;
+ break;
+ }
+ if (found)
+ break;
+ }
+ }
+
+ if (!found)
+ return lappend(gsets_list, uniqset);
+ else
+ return gsets_list;
+ }
+
+ /*
+ * This used in grouping sets(a, rollup(a,b))
+ */
+ static List *
+ add_distinct_sets(List *cum_gsets_list, List *gsets_list)
+ {
+ ListCell *l;
+
+ foreach(l, gsets_list)
+ {
+ Node *el = (Node *) lfirst(l);
+ Assert(IsA(el, List));
+ cum_gsets_list = add_distinct_set(cum_gsets_list, (List *) el);
+ }
+ return cum_gsets_list;
+ }
+
+ /*
+ * Evaluate CUBE and ROLLUP operators
+ * replace all GroupingSetsFunction nodes (inside GROUP BY clause) by
+ * GroupingSetsSpecification nodes.
+ *
+ * This routine hasn't effect on GroupingSetsFunction nodes used in target list. These
+ * nodes are transformed to function calls - together with evaluation of Grouping, and
+ * Grouping_id functions under target list transformation.
+ */
+ static Node *
+ evalGroupingSetsOp(ParseState *pstate, Node *node)
+ {
+ bool has_empty_set = false;
+ List *grouping_sets = NIL;
+ GroupingSetsSpecification *result;
+ int location = -1; /* be a compiler quiete */
+
+ /* leave when node is actually empty set */
+ if (is_empty_grouping_set(node))
+ return NULL;
+
+ switch (nodeTag(node))
+ {
+ case T_GroupingSetsFunction:
+ {
+ List **elements_vect;
+
+ GroupingSetsFunction *gsf = (GroupingSetsFunction *) node;
+ int nfields = list_length(gsf->expr_list);
+
+ if (gsf->kind == GROUPINGSETS_FUNCTION_CUBE && nfields > MAX_CUBE_FIELDS)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("too much parameters for CUBE operator"),
+ parser_errposition(pstate, gsf->location)));
+
+ /*
+ * Because we will access to grouping set elements via index repeatly, then
+ * store pointers to elements to vector. prepareElements does little bit more
+ * it reduce duplicit elements and ensure so node type of every element will
+ * be a List (native list of one field's list) - de facto GroupingSets is
+ * list of lists. Leave when all elements are empty sets.
+ */
+ elements_vect = prepareElements(gsf->expr_list, &nfields, &has_empty_set);
+ if (elements_vect == NULL)
+ return NULL;
+
+ if (gsf->kind == GROUPINGSETS_FUNCTION_GROUPING || gsf->kind == GROUPINGSETS_FUNCTION_GROUPING_ID)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("function \"grouping\" or \"grouping_id\" is used in wrong context"),
+ parser_errposition(pstate, gsf->location)));
+ }
+ else if (gsf->kind == GROUPINGSETS_FUNCTION_CUBE)
+ {
+ /*
+ * Grouping sets CUBE operator evaluates parameters to set of parameter
+ * combinations like CUBE(a,b) -> {{a,b},{a},{b},{}}. The simple way to
+ * generate parameter combinations is using a bitmap shift:
+ * 111 7 {a,b,c}, 110 6 {a,b}, 101 5 {a,c}, 100 4 {a},
+ * ... 001 1 c, 000 0 {}
+ *
+ * btab is array of initial values for n cycles iteration (based on
+ * number of unique grouping element)
+ */
+ unsigned int btab[] = {0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff};
+ unsigned int bitmap = btab[nfields - 1];
+ unsigned int masc = bitmap;
+ unsigned int selector = 1 << (nfields - 1);
+ List *current_set;
+ int i;
+
+ grouping_sets = NIL;
+ while (bitmap > 0)
+ {
+ unsigned int processed_bitmap = bitmap--;
+
+ current_set = NIL;
+ for (i = 0; processed_bitmap > 0; i++)
+ {
+ if (processed_bitmap & selector)
+ current_set = list_union(current_set, elements_vect[i]);
+ processed_bitmap = (processed_bitmap << 1) & masc;
+ }
+ grouping_sets = lappend(grouping_sets, current_set);
+
+ /* append a empty set */
+ has_empty_set = true;
+ }
+ }
+ else if (gsf->kind == GROUPINGSETS_FUNCTION_ROLLUP)
+ {
+ /*
+ * Grouping Sets ROLLUP operator
+ * This operator evaluate set {a,b} to sets {{a,b},{a},{}}
+ */
+ int i;
+ int j;
+ List *current_set;
+
+ grouping_sets = NIL;
+ for (i = 0; i < nfields; i++)
+ {
+ current_set = NIL;
+ for (j = 0; j < nfields - i; j++)
+ current_set = list_union(current_set, elements_vect[j]);
+
+ grouping_sets = lappend(grouping_sets, current_set);
+ }
+
+ /* append a empty set */
+ has_empty_set = true;
+ }
+ }
+ break;
+ case T_GroupingSetsSpecification:
+ {
+ /*
+ * Grouping Sets (xxxx)
+ * a) evaluates inner CUBE and ROLLUP operator
+ * b) drop duplicate sets
+ * c) detect inner empty sets
+ */
+ ListCell *gse;
+ GroupingSetsSpecification *gss = (GroupingSetsSpecification *) node;
+
+ grouping_sets = NIL;
+ location = gss->location;
+
+ foreach(gse, gss->grouping_sets)
+ {
+ Node *grouping_element = (Node *) lfirst(gse);
+ Node *evaluated_element = evalGroupingSetsOp(pstate, grouping_element);
+
+ if (is_empty_grouping_set(evaluated_element))
+ {
+ has_empty_set = true;
+ continue;
+ }
+
+ if (IsGroupingSetsSpecification(evaluated_element))
+ {
+ GroupingSetsSpecification *egss = (GroupingSetsSpecification *) evaluated_element;
+
+ if (egss->has_empty_set)
+ has_empty_set = true;
+
+ if (egss->grouping_sets != NIL)
+ grouping_sets = add_distinct_sets(grouping_sets, egss->grouping_sets);
+ }
+ else if (IsA(evaluated_element, RowExpr))
+ {
+ /*
+ * There are not possible to parse inner grouping sets
+ * directly (like grouping sets(a,b, >>(c,b)<< ) because
+ * there are a conflict with implicit row parse rule. So
+ * in this context we have to transform any RowExpr to
+ * grouping set element.
+ */
+ ListCell *l;
+ RowExpr *row_expr = (RowExpr *) evaluated_element;
+
+ foreach(l, row_expr->args)
+ {
+ if (is_empty_grouping_set((Node *) lfirst(l)))
+ {
+ has_empty_set = true;
+ break;
+ }
+ }
+
+ grouping_sets = add_distinct_set(grouping_sets, row_expr->args);
+ }
+ else
+ grouping_sets = add_distinct_set(grouping_sets, list_make1(evaluated_element));
+ }
+ }
+ break;
+
+ default:
+ return node;
+ }
+
+ if (grouping_sets == NIL)
+ return NULL;
+
+ result = (GroupingSetsSpecification *) makeNode(GroupingSetsSpecification);
+ result->has_empty_set = has_empty_set;
+ result->grouping_sets = grouping_sets;
+ result->location = location;
+
+ return (Node *) result;
+ }
+
+ /* multiple two Grouping Sets elements
+ */
+ static Node *
+ multiple_grouping_sets(Node *a, Node *b)
+ {
+ ListCell *l;
+ ListCell *lj;
+ List *grouping_sets = NIL;
+ GroupingSetsSpecification *result;
+ GroupingSetsSpecification *gss_a;
+ GroupingSetsSpecification *gss_b;
+
+ /* leave when nodes are equal */
+ if (equal(a, b))
+ return a;
+
+ if (!IsGroupingSetsSpecification(a) && !IsGroupingSetsSpecification(b))
+ {
+ /*
+ * when both items are elements, then create new set as union
+ * a x b = (a,b)
+ */
+ GroupingSetsSpecification *gss = (GroupingSetsSpecification *) makeNode(GroupingSetsSpecification);
+ gss->grouping_sets = list_make1(list_make2(a,b));
+ gss->has_empty_set = false;
+ gss->location = exprLocation(a);
+
+ return (Node *) gss;
+ }
+ if (IsGroupingSetsSpecification(a) && !IsGroupingSetsSpecification(b))
+ {
+ Node *aux = a;
+
+ /* when left operand is set and right not, then swap operands */
+ a = b; b = aux;
+ }
+
+ if (!IsGroupingSetsSpecification(a) && IsGroupingSetsSpecification(b))
+ {
+ /*
+ * when left operand isn't set and right yes, copy a to every set in b
+ * a x (b,()) = ((a,b),(a))
+ */
+ gss_b = (GroupingSetsSpecification *) b;
+
+ foreach(l, gss_b->grouping_sets)
+ {
+ List *s = (List *) lfirst(l);
+
+ Assert(IsA(lfirst(l), List));
+
+ /* add item to all grouping sets */
+ s = list_append_unique(s, a);
+ grouping_sets = add_distinct_set(grouping_sets, s);
+ }
+
+ /* replace possible empty set in b by a */
+ if (gss_b->has_empty_set)
+ {
+ grouping_sets = add_distinct_set(grouping_sets, (list_make1(a)));
+ gss_b->has_empty_set = false;
+ }
+
+ gss_b->grouping_sets = grouping_sets;
+
+ return (Node *) b;
+ }
+
+ /*
+ * Both operand are grouping sets
+ * ((A,B),(C)) * ((X,Y),()) = ((A,B,X,Y),(A,B),(C,X,Y),(C))
+ */
+ Assert(IsGroupingSetsSpecification(a) && IsGroupingSetsSpecification(b));
+
+ gss_a = (GroupingSetsSpecification *) a;
+ gss_b = (GroupingSetsSpecification *) b;
+
+ result = (GroupingSetsSpecification *) makeNode(GroupingSetsSpecification);
+
+ foreach(l, gss_a->grouping_sets)
+ {
+ foreach(lj, gss_b->grouping_sets)
+ grouping_sets = add_distinct_set(grouping_sets,
+ list_union((List *) lfirst(l),
+ (List *) lfirst(lj)));
+ if (gss_b->has_empty_set)
+ grouping_sets = add_distinct_set(grouping_sets,
+ (List *) lfirst(l));
+ }
+
+ if (gss_a->has_empty_set)
+ foreach(l, gss_b->grouping_sets)
+ grouping_sets = add_distinct_set(grouping_sets, (List *) lfirst(l));
+
+ result->grouping_sets = grouping_sets;
+ /* when both operands has a empty set, then result will have a empty set too */
+ result->has_empty_set = gss_a->has_empty_set && gss_b->has_empty_set;
+ result->location = gss_a->location;
+
+ return (Node *) result;
+ }
+
+ /*
+ * evaluate list of GROUP BY elements
+ *
+ * When any Grouping Sets is used, then multiply all parameters
+ * of GROUP BY A, B, C = ((A x B) x C)
+ */
+ static Node *
+ evalGroupByClause(List *element_list, bool has_empty_set)
+ {
+ int nfields;
+ ListCell *lc;
+ bool initialized_stack = false;
+ Node *stack = NULL;
+
+ /* fast leave when element_list is empty */
+ if (has_empty_set)
+ {
+ if (is_empty_list(element_list))
+ {
+ GroupingSetsSpecification *gss = makeNode(GroupingSetsSpecification);
+ gss->grouping_sets = NIL;
+ gss->has_empty_set = true;
+ gss->location = -1;
+
+ return (Node *) gss;
+ }
+ }
+
+ Assert(element_list != NIL);
+ nfields = list_length(element_list);
+
+ foreach(lc, element_list)
+ {
+ Node *el = (Node *) lfirst(lc);
+
+ /* fill arithmetic stack */
+ if (!initialized_stack)
+ {
+ stack = el;
+
+ /*
+ * a has_empty_set value goes from outside. Propagate it to stack,
+ * when it is true. When element isn't of GroupingSetsSpecification type,
+ * then cannot to carry value of has_empty_set, and then we have to wrap
+ * element to implicit GroupingSetsSpecification node.
+ */
+ if (IsGroupingSetsSpecification(el))
+ {
+ if (has_empty_set)
+ ((GroupingSetsSpecification *) stack)->has_empty_set = true;
+ }
+ else
+ {
+ if (has_empty_set)
+ {
+ GroupingSetsSpecification *gss = makeNode(GroupingSetsSpecification);
+ gss->grouping_sets = list_make1(list_make1(el));
+ gss->has_empty_set = true;
+ gss->location = exprLocation(el);
+ stack = (Node *) gss;
+ }
+ }
+
+ /* when there are only one parameter, leave function now */
+ if (nfields == 1)
+ return stack;
+
+ initialized_stack = true;
+ /* go to for next item */
+ continue;
+ }
+
+ Assert(stack != NULL);
+ stack = multiple_grouping_sets(stack, el);
+ }
+
+ return stack;
+ }
*** ./src/backend/parser/parse_target.c.orig 2010-02-26 03:00:52.000000000 +0100
--- ./src/backend/parser/parse_target.c 2010-08-05 13:18:04.358157078 +0200
***************
*** 1586,1591 ****
--- 1586,1602 ----
case T_XmlSerialize:
*name = "xmlserialize";
return 2;
+ case T_GroupingSetsFunction:
+ switch (((GroupingSetsFunction *) node)->kind)
+ {
+ case GROUPINGSETS_FUNCTION_GROUPING:
+ *name = "grouping";
+ break;
+ case GROUPINGSETS_FUNCTION_GROUPING_ID:
+ *name = "grouping_id";
+ break;
+ }
+ break;
default:
break;
}
*** ./src/include/nodes/nodes.h.orig 2010-07-12 19:01:06.000000000 +0200
--- ./src/include/nodes/nodes.h 2010-08-02 19:13:55.583786368 +0200
***************
*** 385,390 ****
--- 385,394 ----
T_XmlSerialize,
T_WithClause,
T_CommonTableExpr,
+ T_GroupingSetsFunction,
+ T_GroupingSetsSpecification,
+ T_GroupingSetsEmptySet,
+ T_GroupBy,
/*
* TAGS FOR RANDOM OTHER STUFF
*** ./src/include/nodes/parsenodes.h.orig 2010-07-26 01:21:22.000000000 +0200
--- ./src/include/nodes/parsenodes.h 2010-08-05 12:51:43.718032608 +0200
***************
*** 378,383 ****
--- 378,435 ----
} SortBy;
/*
+ * GroupBy - for GROUP BY [ GROUPING SETS ] clause
+ */
+ typedef struct GroupBy
+ {
+ NodeTag type;
+ bool all; /* value of grouping sets quantifier - default is true */
+ List *grouping_sets; /* list of grouping sets */
+ int location; /* operator location, or -1 if none/unknown */
+ } GroupBy;
+
+ typedef enum GroupingSets_func_kind
+ {
+ GROUPINGSETS_FUNCTION_GROUPING,
+ GROUPINGSETS_FUNCTION_GROUPING_ID,
+ GROUPINGSETS_FUNCTION_CUBE,
+ GROUPINGSETS_FUNCTION_ROLLUP
+ } GroupingSets_func_kind;
+
+ /*
+ * GroupingSetsFunction - for functions Grouping, Grouping_id and grouping
+ * sets operators Cube, Rollup.
+ */
+ typedef struct GroupingSetsFunction
+ {
+ NodeTag type;
+ GroupingSets_func_kind kind; /* identify kind of grouping sets function or operator */
+ Node *expr; /* a parameter for function Grouping */
+ List *expr_list; /* parameters for Grouping_id and Cube and Rollup op */
+ int location; /* token location, or -1 if unknown */
+ } GroupingSetsFunction;
+
+ /*
+ * GroupingSetsSpecification - for GROUPING SETS clause
+ */
+ typedef struct GroupingSetsSpecification
+ {
+ NodeTag type;
+ List *grouping_sets; /* list of grouping sets */
+ bool has_empty_set; /* true, when grouping sets contains any empty set */
+ int location; /* token location, or -1 if unknown */
+ } GroupingSetsSpecification;
+
+ /*
+ * GroupingSetsEmptySet - for empty parentheses inside grouping sets specification
+ */
+ typedef struct GroupingSetsEmptySet
+ {
+ NodeTag type;
+ int location; /* token location, or -1 if unknown */
+ } GroupingSetsEmptySet;
+
+ /*
* WindowDef - raw representation of WINDOW and OVER clauses
*
* For entries in a WINDOW list, "name" is the window name being defined.
***************
*** 956,962 ****
List *targetList; /* the target list (of ResTarget) */
List *fromClause; /* the FROM clause */
Node *whereClause; /* WHERE qualification */
! List *groupClause; /* GROUP BY clauses */
Node *havingClause; /* HAVING conditional-expression */
List *windowClause; /* WINDOW window_name AS (...), ... */
WithClause *withClause; /* WITH clause */
--- 1008,1014 ----
List *targetList; /* the target list (of ResTarget) */
List *fromClause; /* the FROM clause */
Node *whereClause; /* WHERE qualification */
! Node *groupClause; /* GROUP BY clauses */
Node *havingClause; /* HAVING conditional-expression */
List *windowClause; /* WINDOW window_name AS (...), ... */
WithClause *withClause; /* WITH clause */
*** ./src/include/parser/kwlist.h.orig 2010-02-12 18:33:21.000000000 +0100
--- ./src/include/parser/kwlist.h 2010-08-02 15:00:33.799910696 +0200
***************
*** 99,104 ****
--- 99,105 ----
PG_KEYWORD("createuser", CREATEUSER, UNRESERVED_KEYWORD)
PG_KEYWORD("cross", CROSS, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("csv", CSV, UNRESERVED_KEYWORD)
+ PG_KEYWORD("cube", CUBE, RESERVED_KEYWORD)
PG_KEYWORD("current", CURRENT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("current_catalog", CURRENT_CATALOG, RESERVED_KEYWORD)
PG_KEYWORD("current_date", CURRENT_DATE, RESERVED_KEYWORD)
***************
*** 171,176 ****
--- 172,179 ----
PG_KEYWORD("granted", GRANTED, UNRESERVED_KEYWORD)
PG_KEYWORD("greatest", GREATEST, COL_NAME_KEYWORD)
PG_KEYWORD("group", GROUP_P, RESERVED_KEYWORD)
+ PG_KEYWORD("grouping", GROUPING, RESERVED_KEYWORD)
+ PG_KEYWORD("grouping_id", GROUPING_ID, COL_NAME_KEYWORD)
PG_KEYWORD("handler", HANDLER, UNRESERVED_KEYWORD)
PG_KEYWORD("having", HAVING, RESERVED_KEYWORD)
PG_KEYWORD("header", HEADER_P, UNRESERVED_KEYWORD)
***************
*** 318,323 ****
--- 321,327 ----
PG_KEYWORD("right", RIGHT, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("role", ROLE, UNRESERVED_KEYWORD)
PG_KEYWORD("rollback", ROLLBACK, UNRESERVED_KEYWORD)
+ PG_KEYWORD("rollup", ROLLUP, RESERVED_KEYWORD)
PG_KEYWORD("row", ROW, COL_NAME_KEYWORD)
PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD)
PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD)
***************
*** 336,341 ****
--- 340,346 ----
PG_KEYWORD("session_user", SESSION_USER, RESERVED_KEYWORD)
PG_KEYWORD("set", SET, UNRESERVED_KEYWORD)
PG_KEYWORD("setof", SETOF, COL_NAME_KEYWORD)
+ PG_KEYWORD("sets", SETS, UNRESERVED_KEYWORD)
PG_KEYWORD("share", SHARE, UNRESERVED_KEYWORD)
PG_KEYWORD("show", SHOW, UNRESERVED_KEYWORD)
PG_KEYWORD("similar", SIMILAR, TYPE_FUNC_NAME_KEYWORD)
*** ./src/include/parser/parse_node.h.orig 2010-02-26 03:01:26.000000000 +0100
--- ./src/include/parser/parse_node.h 2010-08-05 10:55:19.581032166 +0200
***************
*** 108,113 ****
--- 108,114 ----
bool p_locked_from_parent;
Relation p_target_relation;
RangeTblEntry *p_target_rangetblentry;
+ bool p_hasGroupingSets;
/*
* Optional hook functions for parser callbacks. These are null unless
***************
*** 149,152 ****
--- 150,155 ----
Node *assignFrom);
extern Const *make_const(ParseState *pstate, Value *value, int location);
+ extern Node *evalGroupingSets(ParseState *pstate, List *gsets_element_list);
+
#endif /* PARSE_NODE_H */
On Thu, Aug 05, 2010 at 04:46:51PM +0200, Pavel Stehule wrote:
So Joshua, can you look on code?
Sure... thanks :)
--
Joshua Tolley / eggyknap
End Point Corporation
http://www.endpoint.com
I found other issue :(
postgres=# select name, place from cars group by grouping sets(name, place,());
name | place
-------+------------
bmw |
skoda |
opel |
| germany
| czech rep.
skoda | czech rep.
skoda | germany
bmw | czech rep.
bmw | germany
opel | czech rep.
opel | germany
(11 rows)
postgres=# explain select name, place from cars group by grouping
sets(name, place,());
QUERY PLAN
------------------------------------------------------------------------------
Append (cost=36.98..88.55 rows=1230 width=54)
CTE GroupingSets
-> Seq Scan on cars (cost=0.00..18.30 rows=830 width=68)
-> HashAggregate (cost=18.68..20.68 rows=200 width=32)
-> CTE Scan on "GroupingSets" (cost=0.00..16.60 rows=830 width=32)
-> HashAggregate (cost=18.68..20.68 rows=200 width=32)
-> CTE Scan on "GroupingSets" (cost=0.00..16.60 rows=830 width=32)
-> CTE Scan on "GroupingSets" (cost=0.00..16.60 rows=830 width=64)
(8 rows)
the combination of nonagregates and empty sets do a problems - because
we can't ensure agg mode without aggregates or group by. But it is
only minor issue
2010/8/5 Joshua Tolley <eggyknap@gmail.com>:
Show quoted text
On Thu, Aug 05, 2010 at 04:46:51PM +0200, Pavel Stehule wrote:
So Joshua, can you look on code?
Sure... thanks :)
--
Joshua Tolley / eggyknap
End Point Corporation
http://www.endpoint.com-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)iEYEARECAAYFAkxa1NsACgkQRiRfCGf1UMPwzQCgjz52P86Yx4ac4aRkKwjn8OHK
6/EAoJ/CjXEyPaLpx39SI5bKQPz+AwBR
=Mi2J
-----END PGP SIGNATURE-----
On Thu, Aug 05, 2010 at 04:46:51PM +0200, Pavel Stehule wrote:
I am sending a updated version.
I've been looking at the changes to gram.y, and noted the comment under func_expr
where you added CUBE and ROLLUP definitions. It says that CUBE can't be a
reserved keyword because it's already used in the cube contrib module. But
then the changes to kwlist.h include this:
+ PG_KEYWORD("cube", CUBE, RESERVED_KEYWORD)
...
+ PG_KEYWORD("rollup", ROLLUP, RESERVED_KEYWORD)
...and CUBE and ROLLUP are added in gram.y under the reserved_keyword list. I
realize things like CURRENT_TIME, that also have special entries in the
func_expr grammar, are also reserved keywords, but this all seems at odds with
the comment. What am I missing? Is the comment simply pointing out that the
designation of CUBE and ROLLUP as reserved keywords will have to change at
some point, but it hasn't been implemented yet (or no one has figured out how
to do it)?
--
Joshua Tolley / eggyknap
End Point Corporation
http://www.endpoint.com
2010/8/7 Joshua Tolley <eggyknap@gmail.com>:
On Thu, Aug 05, 2010 at 04:46:51PM +0200, Pavel Stehule wrote:
I am sending a updated version.
I've been looking at the changes to gram.y, and noted the comment under func_expr
where you added CUBE and ROLLUP definitions. It says that CUBE can't be a
reserved keyword because it's already used in the cube contrib module. But
then the changes to kwlist.h include this:
I am little bit confused now - it's bad comment - and I have to verify
it. What I remember, we cannot to use a two parser's rules, because it
going to a conflict. So there have to be used a trick with a moving to
decision to transform stage, where we have a context info. I have to
recheck a minimal level - probably it can't be a RESERVED_KEYWORD.
Because then we can't to create a function "cube".
Show quoted text
+ PG_KEYWORD("cube", CUBE, RESERVED_KEYWORD) ... + PG_KEYWORD("rollup", ROLLUP, RESERVED_KEYWORD)...and CUBE and ROLLUP are added in gram.y under the reserved_keyword list. I
realize things like CURRENT_TIME, that also have special entries in the
func_expr grammar, are also reserved keywords, but this all seems at odds with
the comment. What am I missing? Is the comment simply pointing out that the
designation of CUBE and ROLLUP as reserved keywords will have to change at
some point, but it hasn't been implemented yet (or no one has figured out how
to do it)?--
Joshua Tolley / eggyknap
End Point Corporation
http://www.endpoint.com-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)iEYEARECAAYFAkxcjSIACgkQRiRfCGf1UMPpCwCcCHBh/1NiLykIcVYgPyfbIegF
xq0AoID75rCPiW8yf29OSkaJVza1FQt5
=PcLs
-----END PGP SIGNATURE-----
Hello
I was confused when I though so I found a solution of 1 shift/reduce conflict :(
All identificators used for buildin functions have to be a
col_name_keywords or reserved keyword. There is conflict with our
(probably obsolete) feature SELECT colname(tabname). So for this
moment the real solution is removing CUBE and ROLLUP from keywords and
dynamically testing a funcname in transformation stage - what is
slower and more ugly.
ideas?
Regards
Pavel Stehule
2010/8/7 Pavel Stehule <pavel.stehule@gmail.com>:
Show quoted text
2010/8/7 Joshua Tolley <eggyknap@gmail.com>:
On Thu, Aug 05, 2010 at 04:46:51PM +0200, Pavel Stehule wrote:
I am sending a updated version.
I've been looking at the changes to gram.y, and noted the comment under func_expr
where you added CUBE and ROLLUP definitions. It says that CUBE can't be a
reserved keyword because it's already used in the cube contrib module. But
then the changes to kwlist.h include this:I am little bit confused now - it's bad comment - and I have to verify
it. What I remember, we cannot to use a two parser's rules, because it
going to a conflict. So there have to be used a trick with a moving to
decision to transform stage, where we have a context info. I have to
recheck a minimal level - probably it can't be a RESERVED_KEYWORD.
Because then we can't to create a function "cube".+ PG_KEYWORD("cube", CUBE, RESERVED_KEYWORD) ... + PG_KEYWORD("rollup", ROLLUP, RESERVED_KEYWORD)...and CUBE and ROLLUP are added in gram.y under the reserved_keyword list. I
realize things like CURRENT_TIME, that also have special entries in the
func_expr grammar, are also reserved keywords, but this all seems at odds with
the comment. What am I missing? Is the comment simply pointing out that the
designation of CUBE and ROLLUP as reserved keywords will have to change at
some point, but it hasn't been implemented yet (or no one has figured out how
to do it)?--
Joshua Tolley / eggyknap
End Point Corporation
http://www.endpoint.com-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)iEYEARECAAYFAkxcjSIACgkQRiRfCGf1UMPpCwCcCHBh/1NiLykIcVYgPyfbIegF
xq0AoID75rCPiW8yf29OSkaJVza1FQt5
=PcLs
-----END PGP SIGNATURE-----
Hello
I found a break in GROUPING SETS implementation. Now I am playing with
own executor and planner node and I can't to go forward :(. Probably
this feature will need a significant update of our agg implementation.
Probably needs a some similar structure like CTE but it can be a
little bit reduced - there are a simple relation between source query
and result query - I am not sure, if this has to be implemented via
subqueries? The second question is relative big differencies between
GROUP BY behave and GROUP BY GROUPING SETS behave. Now I don't know
about way to join GROUP BY and GROUPING SETS together
Any ideas welcome
Regards
Pavel