/*-------------------------------------------------------------------------
*
- * pgsp_json_text.h: Text plan generator for pg_store_plan.
+ * pgsp_json_text.h: Text plan generator for pg_store_plans.
*
- * Copyright (c) 2012-2015, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
+ * Copyright (c) 2012-2021, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
*
* IDENTIFICATION
- * pg_store_plan/pgsp_json_text.c
+ * pg_store_plans/pgsp_json_text.c
*
*-------------------------------------------------------------------------
*/
#include "miscadmin.h"
#include "nodes/nodes.h"
#include "nodes/bitmapset.h"
+#include "nodes/pg_list.h"
#include "utils/json.h"
+#if PG_VERSION_NUM < 130000
#include "utils/jsonapi.h"
+#else
+#include "common/jsonapi.h"
+#endif
#include "utils/builtins.h"
#include "pgsp_json_text.h"
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);
/* Parser callbacks for plan textization */
+
+/*
+ * This setter is used for field names that store_plans doesn't know of.
+ * Unlike the other setters, this holds a list of strings emitted as is in text
+ * explains.
+ */
+SETTERDECL(_undef)
+{
+ StringInfo s;
+
+ if(vals->_undef_newelem)
+ {
+ s = makeStringInfo();
+ vals->_undef = lappend(vals->_undef, s);
+ }
+ else
+ {
+ s = llast (vals->_undef);
+ }
+
+ appendStringInfoString(s, val);
+}
+
SETTERDECL(node_type)
{
word_table *p;
SETTERDECL(strategy)
{
word_table *p;
-
+
p = search_word_table(strategies, val, PGSP_JSON_TEXTIZE);
+ if (!p)
+ return;
+
switch (vals->nodetag)
{
case T_Agg:
vals->node_type = "HashAggregate"; break;
case S_Sorted:
vals->node_type = "GroupAggregate"; break;
+ case S_Mixed:
+ vals->node_type = "MixedAggregate"; break;
default:
break;
}
CONVERSION_SETTER(setopcommand, conv_setsetopcommand);
CONVERSION_SETTER(sort_method, conv_sortmethod);
LIST_SETTER(sort_key);
+LIST_SETTER(group_key);
+LIST_SETTER(hash_key);
+BOOL_SETTER(parallel_aware);
+CONVERSION_SETTER(partial_mode, conv_partialmode);
SQLQUOTE_SETTER(index_name);
DEFAULT_SETTER(startup_cost);
DEFAULT_SETTER(total_cost);
LIST_SETTER(conflict_arbiter_indexes);
DEFAULT_SETTER(tuples_inserted);
DEFAULT_SETTER(conflicting_tuples);
-
+DEFAULT_SETTER(sampling_method);
+LIST_SETTER(sampling_params);
+DEFAULT_SETTER(repeatable_seed);
+DEFAULT_SETTER(worker_number);
+DEFAULT_SETTER(workers_planned);
+DEFAULT_SETTER(workers_launched);
+BOOL_SETTER(inner_unique);
+BOOL_SETTER(async_capable);
+DEFAULT_SETTER(table_func_name);
+LIST_SETTER(presorted_key);
+LIST_SETTER(sortmethod_used);
+DEFAULT_SETTER(sortspace_mem);
+DEFAULT_SETTER(group_count);
+DEFAULT_SETTER(avg_sortspc_used);
+DEFAULT_SETTER(peak_sortspc_used);
#define ISZERO(s) (!s || strcmp(s, "0") == 0 || strcmp(s, "0.000") == 0 )
#define HASSTRING(s) (s && strlen(s) > 0)
}
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, gs->key_type, gk, level, exind);
+ }
+
+ }
+}
+
+static void
print_prop_if_nz(StringInfo s, char *prepstr,
const char *prop, int level, int exind)
{
print_prop(s, prepstr, prop, level, exind);
}
-static void
+static void
print_current_node(pgspParserContext *ctx)
{
node_vals *v = ctx->nodevals;
bool comma = false;
int exind = 0;
- if (v->node_type == T_Invalid)
+ /*
+ * The element objects in "Workers" list doesn't have node type, which
+ * would be named T_Worker if there were in node.h. So it needs a special
+ * treat.
+ */
+
+ if (v->node_type == T_Invalid && !HASSTRING(v->worker_number))
return;
if (s->len > 0)
if (level > 1 && ctx->current_list == P_Invalid)
appendStringInfoString(s, "-> ");
+ if (v->parallel_aware)
+ appendStringInfoString(s, "Parallel ");
+
+ if (v->async_capable)
+ appendStringInfoString(s, "Async ");
+
switch (v->nodetag)
{
case T_ModifyTable:
break;
default:
- appendStringInfoString(s, v->node_type);
+ /* Existence of worker_number suggests this is a Worker node */
+ if (HASSTRING(v->worker_number))
+ {
+ appendStringInfoString(s, "Worker");
+ print_prop_if_exists(s, " ", v->worker_number, 0, 0);
+
+ /*
+ * "Worker"s are individual JSON objects in a JSON list but
+ * should be printed as just a property in text
+ * representaion. Correct indent using exind here.
+ */
+ exind = -4;
+ }
+ else
+ appendStringInfoString(s, v->node_type);
break;
}
appendStringInfoSpaces(s, TEXT_INDENT_DETAILS(level, exind));
appendStringInfoString(s, str);
}
-
+
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);
print_prop_if_exists(s, "Join Filter: " , v->join_filter, level, exind);
print_prop_if_exists(s, "Index Cond: " , v->index_cond, level, exind);
print_prop_if_exists(s, "Recheck Cond: ", v->recheck_cond, level, exind);
- print_propstr_if_exists(s, "Sort Key: ", v->sort_key, level, exind);
+ print_prop_if_exists(s, "Workers Planned: ", v->workers_planned, level, exind);
+ print_prop_if_exists(s, "Workers Launched: ", v->workers_launched, level, exind);
+
+ if (HASSTRING(v->sampling_method))
+ {
+ appendStringInfoString(s, "\n");
+ appendStringInfoSpaces(s, TEXT_INDENT_DETAILS(level, exind));
+ appendStringInfo(s, "Sampling: %s (%s)",
+ v->sampling_method,
+ v->sampling_params ? v->sampling_params->data : "");
+ if (v->repeatable_seed)
+ appendStringInfo(s, " REPEATABLE (%s)", v->repeatable_seed);
+ }
+ print_propstr_if_exists(s, "Sort Key: ", v->sort_key, level, exind);
if (HASSTRING(v->sort_method))
{
appendStringInfoString(s, "\n");
}
print_prop_if_exists(s, "Function Call: ", v->func_call, level, exind);
+
+ /*
+ * Emit unknown properties here. The properties are printed in the same
+ * shape with JSON properties as assumed by explain.c.
+ */
+ foreach (lc, v->_undef)
+ {
+ StringInfo str = (StringInfo) lfirst(lc);
+
+ appendStringInfoString(s, "\n");
+ appendStringInfoSpaces(s, TEXT_INDENT_DETAILS(level, exind));
+ appendStringInfoString(s, str->data);
+ }
+ v->_undef = NULL;
+
print_prop_if_exists(s, "Filter: ", v->filter, level, exind);
print_prop_if_nz(s, "Rows Removed by Filter: ",
v->filter_removed, level, exind);
/* Feed a line if any of Buffers: items has been shown */
if (comma)
appendStringInfoString(s, "\n");
-
+
appendStringInfoSpaces(s, TEXT_INDENT_DETAILS(level, exind));
appendStringInfoString(s, "I/O Timings: ");
{
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();
+ if (!v->hash_key)
+ v->hash_key = makeStringInfo();
+ resetStringInfo(v->sort_key);
+ resetStringInfo(v->group_key);
+ resetStringInfo(v->hash_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->key_type = "Group Key: ";
+ if (v->group_key->data[0])
+ {
+ ctx->tmp_gset->group_keys =
+ lappend(ctx->tmp_gset->group_keys,
+ pstrdup(v->group_key->data));
+ }
+ else if (v->hash_key->data[0])
+ {
+ ctx->tmp_gset->group_keys =
+ lappend(ctx->tmp_gset->group_keys,
+ pstrdup(v->hash_key->data));
+ ctx->tmp_gset->key_type = "Hash Key: ";
+ }
+ else
+ ctx->tmp_gset->group_keys =
+ lappend(ctx->tmp_gset->group_keys, "()");
+
+ resetStringInfo(ctx->nodevals->group_key);
+ resetStringInfo(ctx->nodevals->hash_key);
+ }
+ ctx->wlist_level--;
+ }
+}
+
+static void
json_text_ofstart(void *state, char *fname, bool isnull)
{
word_table *p;
if (!p)
{
- ereport(DEBUG1,
+ ereport(DEBUG2,
(errmsg("Short JSON parser encoutered unknown field name: \"%s\", skipped.", fname),
errdetail_log("INPUT: \"%s\"", ctx->org_string)));
+
+ /*
+ * Unknown properties may be put by foreign data wrappers and assumed
+ * to be printed in the same format to JSON properties. We store in
+ * nodevals a string emittable as-is in text explains.
+ */
+ ctx->setter = SETTER(_undef);
+ ctx->nodevals->_undef_newelem = true;
+ ctx->setter(ctx->nodevals, fname);
+ ctx->nodevals->_undef_newelem = false;
+ ctx->setter(ctx->nodevals, ": ");
}
else
{
/*
- * Print node immediately if the next level of Plan/Plans comes. The
- * plan construct is tail-recursive so this doesn't harm.
+ * Print the current node immediately if the next level of
+ * Plan/Plans/Worers comes. This assumes that the plan output is
+ * strcutured tail-recursively.
*/
- if (p->tag == P_Plan || p->tag == P_Plans)
+ if (p->tag == P_Plan || p->tag == P_Plans || p->tag == P_Workers)
{
print_current_node(ctx);
clear_nodeval(ctx->nodevals);
v->tmp_alias = v->alias;
}
+ if (p->tag == P_GroupSets || p->tag == P_Workers)
+ {
+ 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,
* which includes elements in P_Plans list.
*/
- if (p->tag == P_Plan || p->tag == P_Plans)
+ if (p->tag == P_Plan || p->tag == P_Plans || p->tag == P_Workers)
ctx->plan_levels = bms_add_member(ctx->plan_levels, ctx->level);
else
ctx->plan_levels = bms_del_member(ctx->plan_levels, ctx->level);
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;
if (ctx.dest->len > 0 &&
ctx.dest->data[ctx.dest->len - 1] != '\n')
appendStringInfoChar(ctx.dest, '\n');
-
+
if (ctx.dest->len == 0)
appendStringInfoString(ctx.dest, "<Input was not JSON>");
else