OSDN Git Service

Add forgot upgrade sql file
[pgstoreplans/pg_store_plans.git] / pgsp_json_text.c
index be54e8c..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;
@@ -52,25 +82,15 @@ SETTERDECL(node_type)
        }
 }
 
-SETTERDECL(output)
-{
-       if(!vals->output)
-       {
-               vals->output = makeStringInfo();
-               appendStringInfoString(vals->output, val);
-       }
-       else
-       {
-               appendStringInfoString(vals->output, ", ");
-               appendStringInfoString(vals->output, val);
-       }
-}
 SETTERDECL(strategy)
 {
        word_table *p;
-       
+
        p = search_word_table(strategies, val, PGSP_JSON_TEXTIZE);
 
+       if (!p)
+               return;
+
        switch (vals->nodetag)
        {
                case T_Agg:
@@ -80,20 +100,17 @@ 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;
                        }
                        break;
 
                case T_SetOp:
-                       switch (p->tag)
-                       {
-                               case S_Hashed:
-                                       vals->node_type = "HashSetOp";
-                                       break;
-                               default:
-                                       break;
-                       }
+                       if (p->tag == S_Hashed)
+                               vals->node_type = "HashSetOp";
+                       break;
 
                default:
                        break;
@@ -103,11 +120,16 @@ CONVERSION_SETTER(scan_dir, conv_scandir);
 SQLQUOTE_SETTER(obj_name);
 SQLQUOTE_SETTER(alias);
 SQLQUOTE_SETTER(schema_name);
+LIST_SETTER(output);
 DEFAULT_SETTER(merge_cond);
 CONVERSION_SETTER(join_type, conv_jointype);
 CONVERSION_SETTER(setopcommand, conv_setsetopcommand);
 CONVERSION_SETTER(sort_method, conv_sortmethod);
-DEFAULT_SETTER(sort_key);
+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);
@@ -128,6 +150,7 @@ DEFAULT_SETTER(filter_removed);
 DEFAULT_SETTER(idxrchk_removed);
 DEFAULT_SETTER(peak_memory_usage);
 DEFAULT_SETTER(org_hash_batches);
+DEFAULT_SETTER(org_hash_buckets);
 DEFAULT_SETTER(hash_batches);
 DEFAULT_SETTER(hash_buckets);
 DEFAULT_SETTER(actual_startup_time);
@@ -156,45 +179,71 @@ DEFAULT_SETTER(exec_time);
 DEFAULT_SETTER(exact_heap_blks);
 DEFAULT_SETTER(lossy_heap_blks);
 DEFAULT_SETTER(joinfilt_removed);
+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)
 #define TEXT_LEVEL_STEP 6
 #define TEXT_INDENT_OFFSET 2
 #define TEXT_INDENT_BASE(l, e)                                                                                 \
-       ((l < 2) ? 0 : (TEXT_LEVEL_STEP * (l - 2) + TEXT_INDENT_OFFSET) + e)
+       (((l < 2) ? 0 : (TEXT_LEVEL_STEP * (l - 2) + TEXT_INDENT_OFFSET)) + e)
 #define TEXT_INDENT_DETAILS(l, e)                                      \
        (TEXT_INDENT_BASE(l, e) + ((l < 2) ? 2 : 6))
 
 static void
-print_obj_name(pgspParserContext *ctx)
+print_obj_name0(StringInfo s,
+                               const char *obj_name, const char *schema_name, const char *alias)
 {
-       node_vals *v = ctx->nodevals;
-       StringInfo s = ctx->dest;
        bool on_written = false;
 
-       if (HASSTRING(v->obj_name))
+       if (HASSTRING(obj_name))
        {
                on_written = true;
                appendStringInfoString(s, " on ");
-               if (HASSTRING(v->schema_name))
+               if (HASSTRING(schema_name))
                {
-                       appendStringInfoString(s, v->schema_name);
+                       appendStringInfoString(s, schema_name);
                        appendStringInfoChar(s, '.');
                }
-               appendStringInfoString(s, v->obj_name);
+               appendStringInfoString(s, obj_name);
        }
-       if (HASSTRING(v->alias) &&
-               (!HASSTRING(v->obj_name) || strcmp(v->obj_name, v->alias) != 0))
+       if (HASSTRING(alias) &&
+               (!HASSTRING(obj_name) || strcmp(obj_name, alias) != 0))
        {
                if (!on_written)
                        appendStringInfoString(s, " on ");
                else
                        appendStringInfoChar(s, ' ');
-               appendStringInfoString(s, v->alias);
+               appendStringInfoString(s, alias);
        }
 }
 
+static void
+print_obj_name(pgspParserContext *ctx)
+{
+       node_vals *v = ctx->nodevals;
+       StringInfo s = ctx->dest;
+
+       print_obj_name0(s, v->obj_name, v->schema_name, v->alias);
+}
 
 static void
 print_prop(StringInfo s, char *prepstr,
@@ -218,6 +267,44 @@ print_prop_if_exists(StringInfo s, char *prepstr,
 }
 
 static void
+print_propstr_if_exists(StringInfo s, char *prepstr,
+                                               StringInfo prop, int level, int exind)
+{
+       if (prop && prop->data[0])
+       {
+               appendStringInfoString(s, "\n");
+               appendStringInfoSpaces(s, TEXT_INDENT_DETAILS(level, exind));
+               appendStringInfoString(s, prepstr);
+               appendStringInfoString(s, prop->data);
+       }
+}
+
+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)
 {
@@ -225,16 +312,23 @@ 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;
        StringInfo s = ctx->dest;
+       ListCell *lc;
        int level = ctx->level - 1;
        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)
@@ -248,10 +342,17 @@ print_current_node(pgspParserContext *ctx)
                exind = 2;
                appendStringInfoSpaces(s, TEXT_INDENT_BASE(level, exind));
        }
-       
-       if (level > 1)
+
+       /* list items doesn't need this header */
+       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:
@@ -300,10 +401,28 @@ 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;
        }
-       
+
+       /* Don't show costs for child talbes */
+       if (ctx->current_list == P_TargetTables)
+               return;
+
        if (!ISZERO(v->startup_cost) &&
                !ISZERO(v->total_cost) &&
                HASSTRING(v->plan_rows) &&
@@ -343,21 +462,39 @@ print_current_node(pgspParserContext *ctx)
                appendStringInfoString(s, ")");
        }
 
-       if (v->output)
+       foreach(lc, v->target_tables)
        {
+               char *str = (char *)lfirst (lc);
+
                appendStringInfoString(s, "\n");
                appendStringInfoSpaces(s, TEXT_INDENT_DETAILS(level, exind));
-               appendStringInfoString(s, "Output: ");
-               appendStringInfoString(s, v->output->data);
+               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_prop_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");
@@ -377,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);
@@ -397,16 +549,32 @@ print_current_node(pgspParserContext *ctx)
 
        if (!ISZERO(v->hash_buckets))
        {
+               bool show_original = false;
+
                appendStringInfoString(s, "\n");
                appendStringInfoSpaces(s, TEXT_INDENT_DETAILS(level, exind));
                appendStringInfoString(s, "Buckets: ");
                appendStringInfoString(s, v->hash_buckets);
+
+               /* See show_hash_info() in explain.c for details */
+               if ((v->org_hash_buckets &&
+                        strcmp(v->hash_buckets, v->org_hash_buckets) != 0) ||
+                       (v->org_hash_batches &&
+                        strcmp(v->hash_batches, v->org_hash_batches) != 0))
+                       show_original = true;
+
+               if (show_original && v->org_hash_buckets)
+               {
+                       appendStringInfoString(s, " (originally ");
+                       appendStringInfoString(s, v->org_hash_buckets);
+                       appendStringInfoChar(s, ')');
+               }
+
                if (!ISZERO(v->hash_batches))
                {
                        appendStringInfoString(s, "  Batches: ");
                        appendStringInfoString(s, v->hash_batches);
-                       if (v->org_hash_batches &&
-                               strcmp(v->hash_batches, v->org_hash_batches) != 0)
+                       if (show_original && v->org_hash_batches)
                        {
                                appendStringInfoString(s, " (originally ");
                                appendStringInfoString(s, v->org_hash_batches);
@@ -422,6 +590,14 @@ print_current_node(pgspParserContext *ctx)
        }
 
        print_prop_if_exists(s, "Heap Fetches: ", v->heap_fetches, level, exind);
+       print_prop_if_exists(s, "Conflict Resolution: ",
+                                                v->conflict_resolution, level, exind);
+       print_propstr_if_exists(s, "Conflict Arbiter Indexes: ",
+                                                       v->conflict_arbiter_indexes, level, exind);
+       print_prop_if_exists(s, "Tuples Inserted: ",
+                                                v->tuples_inserted, level, exind);
+       print_prop_if_exists(s, "Conflicting Tuples: ",
+                                                v->conflicting_tuples, level, exind);
 
        if (!ISZERO(v->shared_hit_blks) ||
                !ISZERO(v->shared_read_blks) ||
@@ -521,6 +697,27 @@ print_current_node(pgspParserContext *ctx)
                        comma =true;
                }
        }
+       if (!ISZERO(v->io_read_time) ||
+               !ISZERO(v->io_write_time))
+       {
+               /* 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: ");
+
+               if (!ISZERO(v->io_read_time))
+               {
+                       appendStringInfoString(s, " read=");
+                       appendStringInfoString(s, v->io_read_time);
+               }
+               if (!ISZERO(v->io_write_time))
+               {
+                       appendStringInfoString(s, " write=");
+                       appendStringInfoString(s, v->io_write_time);
+               }
+       }
 }
 
 static void
@@ -553,31 +750,137 @@ static void
 json_text_objstart(void *state)
 {
        pgspParserContext *ctx = (pgspParserContext *)state;
-       clear_nodeval(ctx->nodevals);
        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
 json_text_objend(void *state)
 {
        pgspParserContext *ctx = (pgspParserContext *)state;
-       switch (ctx->processing)
+
+       /* Print current node if the object is a P_Plan or a child of P_Plans */
+       if (bms_is_member(ctx->level - 1, ctx->plan_levels))
        {
-               case P_Plan:
-                       print_current_node(ctx);
-                       break;
-               case P_Triggers:
-                       print_current_trig_node(ctx);
-                       break;
-               default:
-                       break;
+               print_current_node(ctx);
+               clear_nodeval(ctx->nodevals);
+       }
+       else if (ctx->section == P_Triggers)
+       {
+               print_current_trig_node(ctx);
+               clear_nodeval(ctx->nodevals);
+       }
+       else if (ctx->current_list == P_TargetTables)
+       {
+               /* Move the current working taget tables into nodevals */
+               node_vals *v = ctx->nodevals;
+
+               if (!ctx->work_str)
+                       ctx->work_str = makeStringInfo();
+
+               resetStringInfo(ctx->work_str);
+               appendStringInfoString(ctx->work_str, v->operation);
+               print_obj_name0(ctx->work_str, v->obj_name, v->schema_name, v->alias);
+               v->target_tables = lappend(v->target_tables,
+                                                                  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;
        }
 
-       clear_nodeval(ctx->nodevals);
        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;
@@ -588,22 +891,64 @@ 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 next level of Plan/Plans comes */
-               if (p->tag == P_Plan || p->tag == P_Plans)
+               /*
+                * 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 || p->tag == P_Workers)
                {
                        print_current_node(ctx);
                        clear_nodeval(ctx->nodevals);
                }
+               else if (p->tag == P_TargetTables)
+               {
+                       node_vals *v = ctx->nodevals;
+
+                       ctx->current_list = p->tag;
+                       ctx->list_fname = fname;
+
+                       /* stash some data */
+                       v->tmp_obj_name = v->obj_name;
+                       v->tmp_schema_name = v->schema_name;
+                       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 || 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);
 
-               
                if (p->tag == P_Plan || p->tag == P_Triggers)
-                       ctx->processing = p->tag;
+                       ctx->section = p->tag;
                ctx->setter = p->setter;
        }
 }
@@ -614,6 +959,21 @@ json_text_ofend(void *state, char *fname, bool isnull)
        pgspParserContext *ctx = (pgspParserContext *)state;
        node_vals *v = ctx->nodevals;
 
+       /* We assume that lists with same fname will not be nested */
+       if (ctx->list_fname && strcmp(fname, ctx->list_fname) == 0)
+       {
+               /* Restore stashed data, see json_text_ofstart */
+               if (ctx->current_list == P_TargetTables)
+               {
+                       v->obj_name = v->tmp_obj_name;
+                       v->schema_name = v->tmp_schema_name;
+                       v->alias = v->tmp_alias;
+               }
+
+               ctx->list_fname = NULL;
+               ctx->current_list = P_Invalid;
+       }
+
        /* Planning/Execution time to be appeared at the end of plan */
        if (HASSTRING(v->plan_time) ||
                HASSTRING(v->exec_time))
@@ -658,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;
@@ -675,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