"Sort Method": "external merge",
"Sort Method": "still in progress",
"Sort Key": "a",
+ "Group Key": "a",
+ "Grouping Sets": "a",
+ "Group Keys": "a",
"Filter": "a",
"Join Filter": "a",
"Hash Cond": "a",
\echo ###### ON CONFLICT
explain (analyze on, buffers on, verbose on, format :format)
INSERT INTO ct1 VALUES (1,1) ON CONFLICT (a) DO UPDATE SET b = EXCLUDED.b + 1;
+\echo ###### GROUP BY
+explain (analyze on, buffers on, verbose on, format :format)
+ SELECT a, b, max(c) FROM tt1 GROUP BY a, b;
+\echo ###### GROUPING SETS
+explain (analyze on, buffers on, verbose on, format :format)
+ SELECT a, b, max(c) FROM tt1 GROUP BY GROUPING SETS ((a), (b), ());
-- BitmapAnd/Inner/Right/ForegnScan
{P_ConstraintName, "x" ,"Constraint Name", NULL, true, NULL, NULL},
{P_Plans, "l" ,"Plans", NULL, true, NULL, NULL},
{P_Plan, "p" ,"Plan", NULL, true, NULL, NULL},
+ {P_GroupKey, "-" ,"Group Key", NULL, true, NULL, SETTER(group_key)},
+ {P_GroupSets, "=" ,"Grouping Sets", NULL, true, NULL, NULL},
+ {P_GroupKeys, "\\" ,"Group Keys", NULL, true, NULL, SETTER(group_key)},
/* Values of these properties are ignored on normalization */
{P_FunctionCall, "y" ,"Function Call", NULL, false, NULL, SETTER(func_call)},
{
pgspParserContext *ctx = (pgspParserContext *)state;
+ if (ctx->current_list == P_GroupKeys)
+ ctx->wlist_level++;
+
appendStringInfoChar(ctx->dest, '[');
ctx->fname = NULL;
ctx->level++;
json_arrend(void *state)
{
pgspParserContext *ctx = (pgspParserContext *)state;
- if (ctx->mode == PGSP_JSON_INFLATE &&
- ctx->last_elem_is_object)
+
+ if (ctx->current_list == P_GroupKeys)
+ ctx->wlist_level--;
+
+ if (ctx->current_list == P_GroupKeys ? ctx->wlist_level == 0 :
+ (ctx->mode == PGSP_JSON_INFLATE && ctx->last_elem_is_object))
{
appendStringInfoChar(ctx->dest, '\n');
appendStringInfoSpaces(ctx->dest, (ctx->level - 1) * INDENT_STEP);
if (ctx->mode == PGSP_JSON_INFLATE)
appendStringInfoChar(ctx->dest, ' ');
+
+ if (p && p->tag == P_GroupKeys)
+ {
+ ctx->current_list = p->tag;
+ ctx->list_fname = fname;
+ ctx->wlist_level = 0;
+ }
+}
+
+static void
+json_ofend(void *state, char *fname, bool isnull)
+{
+ pgspParserContext *ctx = (pgspParserContext *)state;
+
+ if (ctx->list_fname && strcmp(fname, ctx->list_fname) == 0)
+ {
+ ctx->list_fname = NULL;
+ ctx->current_list = P_Invalid;
+ }
}
static void
if (ctx->remove)
return;
- if (!bms_is_member(ctx->level, ctx->first))
+ if (ctx->current_list == P_GroupKeys &&
+ ctx->wlist_level == 1)
{
- appendStringInfoChar(ctx->dest, ',');
- if (ctx->mode == PGSP_JSON_INFLATE &&
- !ctx->last_elem_is_object)
- appendStringInfoChar(ctx->dest, ' ');
+ if (!bms_is_member(ctx->level, ctx->first))
+ appendStringInfoChar(ctx->dest, ',');
+
+ appendStringInfoChar(ctx->dest, '\n');
+ appendStringInfoSpaces(ctx->dest, (ctx->level) * INDENT_STEP);
}
else
- ctx->first = bms_del_member(ctx->first, ctx->level);
+ {
+ if (!bms_is_member(ctx->level, ctx->first))
+ {
+ appendStringInfoChar(ctx->dest, ',');
+
+ if (ctx->mode == PGSP_JSON_INFLATE &&
+ !ctx->last_elem_is_object)
+ appendStringInfoChar(ctx->dest, ' ');
+ }
+ }
+
+ ctx->first = bms_del_member(ctx->first, ctx->level);
}
static void
sem->array_start = json_arrstart;
sem->array_end = json_arrend;
sem->object_field_start = json_ofstart;
- sem->object_field_end = NULL;
+ sem->object_field_end = json_ofend;
sem->array_element_start= json_aestart;
sem->array_element_end = NULL;
sem->scalar = json_scalar;
P_Command,
P_SortMethod,
P_SortKey,
+ P_GroupKey,
+ P_GroupKeys,
+ P_GroupSets,
P_Filter,
P_JoinFilter,
P_HashCond,
bool remove; /* True if the current node is not shown in
* the result */
bool last_elem_is_object; /* True if the last processed element
- * was not an object */
+ * was an object */
pgsp_prop_tags section; /* explain section under processing */
pgsp_prop_tags current_list; /* current list tag that needs special treat*/
StringInfo work_str; /* StringInfor for very-short term usage */
char *fname; /* Field name*/
char *wbuf; /* Working buffer */
int wbuflen; /* Length of the working buffer */
+ int wlist_level; /* Nest level of list for Grouping Sets */
+ grouping_set *tmp_gset; /* Working area for grouping sets */
+
converter_t *valconverter; /* field name converter for the current
* element */
setter_t *setter; /* value converter for the current element */
const char *prop, int leve, int exind);
static void json_text_objstart(void *state);
static void json_text_objend(void *state);
+static void json_text_arrstart(void *state);
+static void json_text_arrend(void *state);
static void json_text_ofstart(void *state, char *fname, bool isnull);
static void json_text_ofend(void *state, char *fname, bool isnull);
static void json_text_scalar(void *state, char *token, JsonTokenType tokentype);
CONVERSION_SETTER(setopcommand, conv_setsetopcommand);
CONVERSION_SETTER(sort_method, conv_sortmethod);
LIST_SETTER(sort_key);
+LIST_SETTER(group_key);
SQLQUOTE_SETTER(index_name);
DEFAULT_SETTER(startup_cost);
DEFAULT_SETTER(total_cost);
}
static void
+print_groupingsets_if_exists(StringInfo s, List *gss, int level, int exind)
+{
+ ListCell *lc;
+
+ foreach (lc, gss)
+ {
+ ListCell *lcg;
+ grouping_set *gs = (grouping_set *)lfirst (lc);
+
+ if (gs->sort_keys)
+ {
+ print_prop_if_exists(s, "Sort Key: ", gs->sort_keys, level, exind);
+ exind += 2;
+ }
+
+ foreach (lcg, gs->group_keys)
+ {
+ const char *gk = (const char *)lfirst (lcg);
+ print_prop_if_exists(s, "Group Key: ", gk, level, exind);
+ }
+ }
+}
+
+static void
print_prop_if_nz(StringInfo s, char *prepstr,
const char *prop, int level, int exind)
{
}
print_propstr_if_exists(s, "Output: ", v->output, level, exind);
+ print_propstr_if_exists(s, "Group Key: ", v->group_key, level, exind);
+ print_groupingsets_if_exists(s, v->grouping_sets, level, exind);
print_prop_if_exists(s, "Merge Cond: ", v->merge_cond, level, exind);
print_prop_if_exists(s, "Hash Cond: " , v->hash_cond, level, exind);
print_prop_if_exists(s, "Tid Cond: " , v->tid_cond, level, exind);
{
pgspParserContext *ctx = (pgspParserContext *)state;
ctx->level++;
+
+ /* Create new grouping sets or reset existing ones */
+ if (ctx->current_list == P_GroupSets)
+ {
+ node_vals *v = ctx->nodevals;
+
+ ctx->tmp_gset = (grouping_set*) palloc0(sizeof(grouping_set));
+ if (!v->sort_key)
+ v->sort_key = makeStringInfo();
+ if (!v->group_key)
+ v->group_key = makeStringInfo();
+ resetStringInfo(v->sort_key);
+ resetStringInfo(v->group_key);
+ }
}
static void
pstrdup(ctx->work_str->data));
resetStringInfo(ctx->work_str);
}
+ else if (ctx->current_list == P_GroupSets && ctx->tmp_gset)
+ {
+ /* Move working grouping set into nodevals */
+ node_vals *v = ctx->nodevals;
+
+ /* Copy sort key if any */
+ if (v->sort_key->data[0])
+ {
+ ctx->tmp_gset->sort_keys = strdup(v->sort_key->data);
+ resetStringInfo(v->sort_key);
+ }
+
+ /* Move working grouping set into nodevals */
+ ctx->nodevals->grouping_sets =
+ lappend(v->grouping_sets, ctx->tmp_gset);
+ ctx->tmp_gset = NULL;
+ }
ctx->last_elem_is_object = true;
ctx->level--;
}
+static void
+json_text_arrstart(void *state)
+{
+ pgspParserContext *ctx = (pgspParserContext *)state;
+
+ if (ctx->current_list == P_GroupSets)
+ {
+ ctx->wlist_level++;
+ }
+}
+
+static void
+json_text_arrend(void *state)
+{
+ pgspParserContext *ctx = (pgspParserContext *)state;
+
+ if (ctx->current_list == P_GroupSets)
+ {
+ /*
+ * wlist_level means that now at the end of innermost list of Group
+ * Keys
+ */
+ if (ctx->wlist_level == 3)
+ {
+ node_vals *v = ctx->nodevals;
+
+ /*
+ * At this point, v->group_key holds the keys in "Group Keys". The
+ * item holds a double-nested list and the innermost lists are to
+ * go into individual "Group Key" lines. Empty innermost list is
+ * represented as "()" there. See explain.c of PostgreSQL.
+ */
+ ctx->tmp_gset->group_keys =
+ lappend(ctx->tmp_gset->group_keys,
+ (v->group_key->data[0] ?
+ pstrdup(v->group_key->data) : "()"));
+ resetStringInfo(ctx->nodevals->group_key);
+ }
+ ctx->wlist_level--;
+ }
+}
+
static void
json_text_ofstart(void *state, char *fname, bool isnull)
{
v->tmp_schema_name = v->schema_name;
v->tmp_alias = v->alias;
}
+ else if (p->tag == P_GroupSets)
+ {
+ ctx->current_list = p->tag;
+ ctx->list_fname = fname;
+ ctx->wlist_level = 0;
+ }
/*
* This paser prints partial result at the end of every P_Plan object,
v->schema_name = v->tmp_schema_name;
v->alias = v->tmp_alias;
}
+
ctx->list_fname = NULL;
ctx->current_list = P_Invalid;
}
sem.semstate = (void*)&ctx;
sem.object_start = json_text_objstart;
sem.object_end = json_text_objend;
- sem.array_start = NULL;
- sem.array_end = NULL;
+ sem.array_start = json_text_arrstart;
+ sem.array_end = json_text_arrend;
sem.object_field_start = json_text_ofstart;
sem.object_field_end = json_text_ofend;
sem.array_element_start= NULL;
typedef struct
{
+ const char *sort_keys;
+ List *group_keys;
+} grouping_set;
+
+typedef struct
+{
NodeTag nodetag;
const char *node_type;
const char *operation;
const char *func_call;
const char *sort_method;
StringInfo sort_key;
+ StringInfo group_key;
+ List *grouping_sets;
const char *index_cond;
const char *merge_cond;
const char *hash_cond;
#define LIST_SETTER(name) \
SETTERDECL(name) { \
- if (!vals->name)\
+ if (!vals->name || !vals->name->data[0])\
{ \
vals->name = makeStringInfo(); \
appendStringInfoString(vals->name, val); \
SETTERDECL(setopcommand);
SETTERDECL(sort_method);
SETTERDECL(sort_key);
+SETTERDECL(group_key);
+SETTERDECL(group_keys);
SETTERDECL(index_name);
SETTERDECL(startup_cost);
SETTERDECL(total_cost);