OSDN Git Service

Add forgot upgrade sql file
[pgstoreplans/pg_store_plans.git] / pgsp_json_text.c
index 6455955..5002943 100644 (file)
@@ -1,11 +1,11 @@
 /*-------------------------------------------------------------------------
  *
- * 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"
@@ -32,11 +37,36 @@ static void print_prop_if_nz(StringInfo s, char *prepstr,
                                                         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;
@@ -55,9 +85,12 @@ SETTERDECL(node_type)
 SETTERDECL(strategy)
 {
        word_table *p;
-       
+
        p = search_word_table(strategies, val, PGSP_JSON_TEXTIZE);
 
+       if (!p)
+               return;
+
        switch (vals->nodetag)
        {
                case T_Agg:
@@ -67,6 +100,8 @@ SETTERDECL(strategy)
                                        vals->node_type = "HashAggregate"; break;
                                case S_Sorted:
                                        vals->node_type = "GroupAggregate"; break;
+                               case S_Mixed:
+                                       vals->node_type = "MixedAggregate"; break;
                                default:
                                        break;
                        }
@@ -91,6 +126,10 @@ CONVERSION_SETTER(join_type, conv_jointype);
 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);
@@ -144,7 +183,21 @@ DEFAULT_SETTER(conflict_resolution);
 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)
@@ -227,6 +280,31 @@ print_propstr_if_exists(StringInfo s, char *prepstr,
 }
 
 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)
 {
@@ -234,7 +312,7 @@ print_prop_if_nz(StringInfo s, char *prepstr,
                print_prop(s, prepstr, prop, level, exind);
 }
 
-static void 
+static void
 print_current_node(pgspParserContext *ctx)
 {
        node_vals *v = ctx->nodevals;
@@ -244,7 +322,13 @@ print_current_node(pgspParserContext *ctx)
        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)
@@ -263,6 +347,12 @@ print_current_node(pgspParserContext *ctx)
        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:
@@ -311,7 +401,21 @@ print_current_node(pgspParserContext *ctx)
                        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;
        }
 
@@ -366,16 +470,31 @@ print_current_node(pgspParserContext *ctx)
                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");
@@ -395,6 +514,21 @@ print_current_node(pgspParserContext *ctx)
        }
 
        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);
@@ -569,7 +703,7 @@ print_current_node(pgspParserContext *ctx)
                /* 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: ");
 
@@ -617,6 +751,23 @@ json_text_objstart(void *state)
 {
        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
@@ -650,12 +801,86 @@ json_text_objend(void *state)
                                                                   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;
@@ -666,17 +891,29 @@ json_text_ofstart(void *state, char *fname, bool isnull)
 
        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);
@@ -694,11 +931,18 @@ json_text_ofstart(void *state, char *fname, bool isnull)
                        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);
@@ -725,6 +969,7 @@ json_text_ofend(void *state, char *fname, bool isnull)
                        v->schema_name = v->tmp_schema_name;
                        v->alias = v->tmp_alias;
                }
+
                ctx->list_fname = NULL;
                ctx->current_list = P_Invalid;
        }
@@ -773,8 +1018,8 @@ pgsp_json_textize(char *json)
        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;
@@ -790,7 +1035,7 @@ pgsp_json_textize(char *json)
                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