OSDN Git Service

Bump up version to 1.2.
[pgstoreplans/pg_store_plans.git] / pgsp_json_text.c
1 /*-------------------------------------------------------------------------
2  *
3  * pgsp_json_text.h: Text plan generator for pg_store_plan.
4  *
5  * Copyright (c) 2012-2016, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
6  *
7  * IDENTIFICATION
8  *        pg_store_plan/pgsp_json_text.c
9  *
10  *-------------------------------------------------------------------------
11  */
12
13 #include "postgres.h"
14 #include "miscadmin.h"
15 #include "nodes/nodes.h"
16 #include "nodes/bitmapset.h"
17 #include "nodes/pg_list.h"
18 #include "utils/json.h"
19 #include "utils/jsonapi.h"
20 #include "utils/builtins.h"
21
22 #include "pgsp_json_text.h"
23 #include "pgsp_json_int.h"
24
25 static void clear_nodeval(node_vals *vals);
26 static void print_current_node(pgspParserContext *ctx);
27 static void print_current_trig_node(pgspParserContext *ctx);
28 static void print_prop(StringInfo s, char *prepstr,
29                                            const char *prop, int leve, int exind);
30 static void print_prop_if_exists(StringInfo s, char *prepstr,
31                                                                  const char *prop, int leve, int exind);
32 static void print_prop_if_nz(StringInfo s, char *prepstr,
33                                                          const char *prop, int leve, int exind);
34 static void json_text_objstart(void *state);
35 static void json_text_objend(void *state);
36 static void json_text_arrstart(void *state);
37 static void json_text_arrend(void *state);
38 static void json_text_ofstart(void *state, char *fname, bool isnull);
39 static void json_text_ofend(void *state, char *fname, bool isnull);
40 static void json_text_scalar(void *state, char *token, JsonTokenType tokentype);
41
42 /* Parser callbacks for plan textization */
43
44 /*
45  * This setter is used for field names that store_plans doesn't know of.
46  * Unlike the other setters, this holds a list of strings emitted as is in text
47  * explains.
48  */
49 SETTERDECL(_undef)
50 {
51         StringInfo s;
52
53         if(vals->_undef_newelem)
54         {
55                 s = makeStringInfo();
56                 vals->_undef = lappend(vals->_undef, s);
57         }
58         else
59         {
60                 s = llast (vals->_undef);
61         }
62
63         appendStringInfoString(s, val);
64 }
65
66 SETTERDECL(node_type)
67 {
68         word_table *p;
69
70         vals->node_type = val;
71         vals->nodetag = T_Invalid;
72
73         p = search_word_table(nodetypes, val, PGSP_JSON_TEXTIZE);
74         if (p)
75         {
76                 vals->node_type = (p->textname ? p->textname : p->longname);
77                 vals->nodetag = p->tag;
78         }
79 }
80
81 SETTERDECL(strategy)
82 {
83         word_table *p;
84         
85         p = search_word_table(strategies, val, PGSP_JSON_TEXTIZE);
86
87         if (!p)
88                 return;
89
90         switch (vals->nodetag)
91         {
92                 case T_Agg:
93                         switch (p->tag)
94                         {
95                                 case S_Hashed:
96                                         vals->node_type = "HashAggregate"; break;
97                                 case S_Sorted:
98                                         vals->node_type = "GroupAggregate"; break;
99                                 case S_Mixed:
100                                         vals->node_type = "MixedAggregate"; break;
101                                 default:
102                                         break;
103                         }
104                         break;
105
106                 case T_SetOp:
107                         if (p->tag == S_Hashed)
108                                 vals->node_type = "HashSetOp";
109                         break;
110
111                 default:
112                         break;
113         }
114 }
115 CONVERSION_SETTER(scan_dir, conv_scandir);
116 SQLQUOTE_SETTER(obj_name);
117 SQLQUOTE_SETTER(alias);
118 SQLQUOTE_SETTER(schema_name);
119 LIST_SETTER(output);
120 DEFAULT_SETTER(merge_cond);
121 CONVERSION_SETTER(join_type, conv_jointype);
122 CONVERSION_SETTER(setopcommand, conv_setsetopcommand);
123 CONVERSION_SETTER(sort_method, conv_sortmethod);
124 LIST_SETTER(sort_key);
125 LIST_SETTER(group_key);
126 LIST_SETTER(hash_key);
127 BOOL_SETTER(parallel_aware);
128 CONVERSION_SETTER(partial_mode, conv_partialmode);
129 SQLQUOTE_SETTER(index_name);
130 DEFAULT_SETTER(startup_cost);
131 DEFAULT_SETTER(total_cost);
132 DEFAULT_SETTER(plan_rows);
133 DEFAULT_SETTER(plan_width);
134 DEFAULT_SETTER(sort_space_used);
135 CONVERSION_SETTER(sort_space_type, conv_sortspacetype);
136 DEFAULT_SETTER(filter);
137 DEFAULT_SETTER(join_filter);
138 DEFAULT_SETTER(func_call);
139 DEFAULT_SETTER(index_cond);
140 DEFAULT_SETTER(recheck_cond);
141 CONVERSION_SETTER(operation, conv_operation);
142 DEFAULT_SETTER(subplan_name);
143 DEFAULT_SETTER(hash_cond);
144 DEFAULT_SETTER(tid_cond);
145 DEFAULT_SETTER(filter_removed);
146 DEFAULT_SETTER(idxrchk_removed);
147 DEFAULT_SETTER(peak_memory_usage);
148 DEFAULT_SETTER(org_hash_batches);
149 DEFAULT_SETTER(org_hash_buckets);
150 DEFAULT_SETTER(hash_batches);
151 DEFAULT_SETTER(hash_buckets);
152 DEFAULT_SETTER(actual_startup_time);
153 DEFAULT_SETTER(actual_total_time);
154 DEFAULT_SETTER(actual_rows);
155 DEFAULT_SETTER(actual_loops);
156 DEFAULT_SETTER(heap_fetches);
157 DEFAULT_SETTER(shared_hit_blks);
158 DEFAULT_SETTER(shared_read_blks);
159 DEFAULT_SETTER(shared_dirtied_blks);
160 DEFAULT_SETTER(shared_written_blks);
161 DEFAULT_SETTER(local_hit_blks);
162 DEFAULT_SETTER(local_read_blks);
163 DEFAULT_SETTER(local_dirtied_blks);
164 DEFAULT_SETTER(local_written_blks);
165 DEFAULT_SETTER(temp_read_blks);
166 DEFAULT_SETTER(temp_written_blks);
167 DEFAULT_SETTER(io_read_time);
168 DEFAULT_SETTER(io_write_time);
169 SQLQUOTE_SETTER(trig_name);
170 SQLQUOTE_SETTER(trig_relation);
171 DEFAULT_SETTER(trig_time);
172 DEFAULT_SETTER(trig_calls);
173 DEFAULT_SETTER(plan_time);
174 DEFAULT_SETTER(exec_time);
175 DEFAULT_SETTER(exact_heap_blks);
176 DEFAULT_SETTER(lossy_heap_blks);
177 DEFAULT_SETTER(joinfilt_removed);
178 DEFAULT_SETTER(conflict_resolution);
179 LIST_SETTER(conflict_arbiter_indexes);
180 DEFAULT_SETTER(tuples_inserted);
181 DEFAULT_SETTER(conflicting_tuples);
182 DEFAULT_SETTER(sampling_method);
183 LIST_SETTER(sampling_params);
184 DEFAULT_SETTER(repeatable_seed);
185 DEFAULT_SETTER(worker_number);
186 DEFAULT_SETTER(workers_planned);
187 DEFAULT_SETTER(workers_launched);
188 BOOL_SETTER(inner_unique);
189 DEFAULT_SETTER(table_func_name);
190
191 #define ISZERO(s) (!s || strcmp(s, "0") == 0 || strcmp(s, "0.000") == 0 )
192 #define HASSTRING(s) (s && strlen(s) > 0)
193 #define TEXT_LEVEL_STEP 6
194 #define TEXT_INDENT_OFFSET 2
195 #define TEXT_INDENT_BASE(l, e)                                                                                  \
196         (((l < 2) ? 0 : (TEXT_LEVEL_STEP * (l - 2) + TEXT_INDENT_OFFSET)) + e)
197 #define TEXT_INDENT_DETAILS(l, e)                                       \
198         (TEXT_INDENT_BASE(l, e) + ((l < 2) ? 2 : 6))
199
200 static void
201 print_obj_name0(StringInfo s,
202                                 const char *obj_name, const char *schema_name, const char *alias)
203 {
204         bool on_written = false;
205
206         if (HASSTRING(obj_name))
207         {
208                 on_written = true;
209                 appendStringInfoString(s, " on ");
210                 if (HASSTRING(schema_name))
211                 {
212                         appendStringInfoString(s, schema_name);
213                         appendStringInfoChar(s, '.');
214                 }
215                 appendStringInfoString(s, obj_name);
216         }
217         if (HASSTRING(alias) &&
218                 (!HASSTRING(obj_name) || strcmp(obj_name, alias) != 0))
219         {
220                 if (!on_written)
221                         appendStringInfoString(s, " on ");
222                 else
223                         appendStringInfoChar(s, ' ');
224                 appendStringInfoString(s, alias);
225         }
226 }
227
228 static void
229 print_obj_name(pgspParserContext *ctx)
230 {
231         node_vals *v = ctx->nodevals;
232         StringInfo s = ctx->dest;
233
234         print_obj_name0(s, v->obj_name, v->schema_name, v->alias);
235 }
236
237 static void
238 print_prop(StringInfo s, char *prepstr,
239                    const char *prop, int level, int exind)
240 {
241         if (level > 0)
242         {
243                 appendStringInfoString(s, "\n");
244                 appendStringInfoSpaces(s, TEXT_INDENT_DETAILS(level, exind));
245         }
246         appendStringInfoString(s, prepstr);
247         appendStringInfoString(s, prop);
248 }
249
250 static void
251 print_prop_if_exists(StringInfo s, char *prepstr,
252                                          const char *prop, int level, int exind)
253 {
254         if (HASSTRING(prop))
255                 print_prop(s, prepstr, prop, level, exind);
256 }
257
258 static void
259 print_propstr_if_exists(StringInfo s, char *prepstr,
260                                                 StringInfo prop, int level, int exind)
261 {
262         if (prop && prop->data[0])
263         {
264                 appendStringInfoString(s, "\n");
265                 appendStringInfoSpaces(s, TEXT_INDENT_DETAILS(level, exind));
266                 appendStringInfoString(s, prepstr);
267                 appendStringInfoString(s, prop->data);
268         }
269 }
270
271 static void
272 print_groupingsets_if_exists(StringInfo s, List *gss, int level, int exind)
273 {
274         ListCell *lc;
275
276         foreach (lc, gss)
277         {
278                 ListCell *lcg;
279                 grouping_set *gs = (grouping_set *)lfirst (lc);
280
281                 if (gs->sort_keys)
282                 {
283                         print_prop_if_exists(s, "Sort Key: ", gs->sort_keys, level, exind);
284                         exind += 2;
285                 }
286
287                 foreach (lcg, gs->group_keys)
288                 {
289                         const char *gk = (const char *)lfirst (lcg);
290                         print_prop_if_exists(s, gs->key_type, gk, level, exind);
291                 }
292
293         }
294 }
295
296 static void
297 print_prop_if_nz(StringInfo s, char *prepstr,
298                                  const char *prop, int level, int exind)
299 {
300         if (!ISZERO(prop))
301                 print_prop(s, prepstr, prop, level, exind);
302 }
303
304 static void 
305 print_current_node(pgspParserContext *ctx)
306 {
307         node_vals *v = ctx->nodevals;
308         StringInfo s = ctx->dest;
309         ListCell *lc;
310         int level = ctx->level - 1;
311         bool comma = false;
312         int exind = 0;
313
314         /*
315          * The element objects in "Workers" list doesn't have node type, which
316          * would be named T_Worker if there were in node.h. So it needs a special
317          * treat.
318          */
319         
320         if (v->node_type == T_Invalid && !HASSTRING(v->worker_number))
321                 return;
322
323         if (s->len > 0)
324                 appendStringInfoString(s, "\n");
325         appendStringInfoSpaces(s, TEXT_INDENT_BASE(level, exind));
326
327         if (HASSTRING(v->subplan_name))
328         {
329                 appendStringInfoString(s, v->subplan_name);
330                 appendStringInfoString(s, "\n");
331                 exind = 2;
332                 appendStringInfoSpaces(s, TEXT_INDENT_BASE(level, exind));
333         }
334
335         /* list items doesn't need this header */
336         if (level > 1 && ctx->current_list == P_Invalid)
337                 appendStringInfoString(s, "->  ");
338
339         if (v->parallel_aware)
340                 appendStringInfoString(s, "Parallel ");
341
342         switch (v->nodetag)
343         {
344                 case T_ModifyTable:
345                 case T_SeqScan:
346                 case T_BitmapHeapScan:
347                 case T_TidScan:
348                 case T_SubqueryScan:
349                 case T_FunctionScan:
350                 case T_ValuesScan:
351                 case T_CteScan:
352                 case T_WorkTableScan:
353                 case T_ForeignScan:
354                         if (v->nodetag == T_ModifyTable)
355                                 appendStringInfoString(s, v->operation);
356                         else
357                                 appendStringInfoString(s, v->node_type);
358
359                         print_obj_name(ctx);
360                         break;
361
362                 case T_IndexScan:
363                 case T_IndexOnlyScan:
364                 case T_BitmapIndexScan:
365                         appendStringInfoString(s, v->node_type);
366                         print_prop_if_exists(s, " ", v->scan_dir, 0, 0);
367                         print_prop_if_exists(s, " using ", v->index_name, 0, 0);
368                         print_obj_name(ctx);
369                         break;
370
371                 case T_NestLoop:
372                 case T_MergeJoin:
373                 case T_HashJoin:
374                         appendStringInfoString(s, v->node_type);
375                         if (v->join_type && strcmp(v->join_type, "Inner") != 0)
376                         {
377                                 appendStringInfoChar(s, ' ');
378                                 appendStringInfoString(s, v->join_type);
379                         }
380                         if (v->nodetag != T_NestLoop)
381                                 appendStringInfoString(s, " Join");
382                         break;
383
384                 case T_SetOp:
385                         appendStringInfoString(s, v->node_type);
386                         print_prop_if_exists(s, " ", v->setopcommand, 0, 0);
387                         break;
388
389                 default:
390                         /* Existence of worker_number suggests this is a Worker node */
391                         if (HASSTRING(v->worker_number))
392                         {
393                                 appendStringInfoString(s, "Worker");
394                                 print_prop_if_exists(s, " ", v->worker_number, 0, 0);
395
396                                 /* 
397                                  * "Worker"s are individual JSON objects in a JSON list but
398                                  * should be printed as just a property in text
399                                  * representaion. Correct indent using exind here.
400                                  */
401                                 exind = -4;
402                         }
403                         else
404                                 appendStringInfoString(s, v->node_type);
405                         break;
406         }
407
408         /* Don't show costs for child talbes */
409         if (ctx->current_list == P_TargetTables)
410                 return;
411
412         if (!ISZERO(v->startup_cost) &&
413                 !ISZERO(v->total_cost) &&
414                 HASSTRING(v->plan_rows) &&
415                 HASSTRING(v->plan_width))
416         {
417                 appendStringInfoString(s, "  (cost=");
418                 appendStringInfoString(s, v->startup_cost);
419                 appendStringInfoString(s, "..");
420                 appendStringInfoString(s, v->total_cost);
421                 appendStringInfoString(s, " rows=");
422                 appendStringInfoString(s, v->plan_rows);
423                 appendStringInfoString(s, " width=");
424                 appendStringInfoString(s, v->plan_width);
425                 appendStringInfoString(s, ")");
426         }
427
428         if (HASSTRING(v->actual_loops) && ISZERO(v->actual_loops))
429                 appendStringInfoString(s, " (never executed)");
430         else if (HASSTRING(v->actual_rows) &&
431                          HASSTRING(v->actual_loops) &&
432                          HASSTRING(v->actual_startup_time) &&
433                          HASSTRING(v->actual_total_time))
434         {
435                 appendStringInfoString(s, " (actual ");
436                 appendStringInfoString(s, "time=");
437                 appendStringInfoString(s, v->actual_startup_time);
438                 appendStringInfoString(s, "..");
439                 appendStringInfoString(s, v->actual_total_time);
440                 appendStringInfoString(s, " ");
441
442                 appendStringInfoString(s, "rows=");
443                 appendStringInfoString(s, v->actual_rows);
444
445                 appendStringInfoString(s, " loops=");
446                 appendStringInfoString(s, v->actual_loops);
447
448                 appendStringInfoString(s, ")");
449         }
450
451         foreach(lc, v->target_tables)
452         {
453                 char *str = (char *)lfirst (lc);
454
455                 appendStringInfoString(s, "\n");
456                 appendStringInfoSpaces(s, TEXT_INDENT_DETAILS(level, exind));
457                 appendStringInfoString(s, str);
458         }
459                 
460         print_propstr_if_exists(s, "Output: ", v->output, level, exind);
461         print_propstr_if_exists(s, "Group Key: ", v->group_key, level, exind);
462         print_groupingsets_if_exists(s, v->grouping_sets, level, exind);
463         print_prop_if_exists(s, "Merge Cond: ", v->merge_cond, level, exind);
464         print_prop_if_exists(s, "Hash Cond: " , v->hash_cond, level, exind);
465         print_prop_if_exists(s, "Tid Cond: " , v->tid_cond, level, exind);
466         print_prop_if_exists(s, "Join Filter: " , v->join_filter, level, exind);
467         print_prop_if_exists(s, "Index Cond: " , v->index_cond, level, exind);
468         print_prop_if_exists(s, "Recheck Cond: ", v->recheck_cond, level, exind);
469         print_prop_if_exists(s, "Workers Planned: ", v->workers_planned, level, exind);
470         print_prop_if_exists(s, "Workers Launched: ", v->workers_launched, level, exind);
471
472         if (HASSTRING(v->sampling_method))
473         {
474                 appendStringInfoString(s, "\n");
475                 appendStringInfoSpaces(s, TEXT_INDENT_DETAILS(level, exind));
476                 appendStringInfo(s, "Sampling: %s (%s)",
477                                                  v->sampling_method,
478                                                  v->sampling_params ? v->sampling_params->data : "");
479                 if (v->repeatable_seed)
480                         appendStringInfo(s, " REPEATABLE (%s)", v->repeatable_seed);
481         }
482         
483         print_propstr_if_exists(s, "Sort Key: ", v->sort_key, level, exind);
484         if (HASSTRING(v->sort_method))
485         {
486                 appendStringInfoString(s, "\n");
487                 appendStringInfoSpaces(s, TEXT_INDENT_DETAILS(level, exind));
488                 appendStringInfoString(s, "Sort Method: ");
489                 appendStringInfoString(s, v->sort_method);
490
491                 if (HASSTRING(v->sort_space_type) &&
492                         HASSTRING(v->sort_space_used))
493                 {
494                         appendStringInfoString(s, "  ");
495                         appendStringInfoString(s, v->sort_space_type);
496                         appendStringInfoString(s, ": ");
497                         appendStringInfoString(s, v->sort_space_used);
498                         appendStringInfoString(s, "kB");
499                 }
500         }
501
502         print_prop_if_exists(s, "Function Call: ", v->func_call, level, exind);
503
504         /*
505          * Emit unknown properties here. The properties are printed in the same
506          * shape with JSON properties as assumed by explain.c.
507          */
508         foreach (lc, v->_undef)
509         {
510                 StringInfo str = (StringInfo) lfirst(lc);
511
512                 appendStringInfoString(s, "\n");
513                 appendStringInfoSpaces(s, TEXT_INDENT_DETAILS(level, exind));
514                 appendStringInfoString(s, str->data);
515         }
516         v->_undef = NULL;
517
518         print_prop_if_exists(s, "Filter: ", v->filter, level, exind);
519         print_prop_if_nz(s, "Rows Removed by Filter: ",
520                                                  v->filter_removed, level, exind);
521         print_prop_if_nz(s, "Rows Removed by Index Recheck: ",
522                                                  v->idxrchk_removed, level, exind);
523         print_prop_if_nz(s, "Rows Removed by Join Filter: ",
524                                                  v->joinfilt_removed, level, exind);
525
526         if (HASSTRING(v->exact_heap_blks) ||
527                 HASSTRING(v->lossy_heap_blks))
528         {
529                 appendStringInfoString(s, "\n");
530                 appendStringInfoSpaces(s, TEXT_INDENT_DETAILS(level, exind));
531                 appendStringInfoString(s, "Heap Blocks:");
532                 print_prop_if_nz(s, " exact=", v->exact_heap_blks, 0, exind);
533                 print_prop_if_nz(s, " lossy=", v->lossy_heap_blks, 0, exind);
534         }
535
536         if (!ISZERO(v->hash_buckets))
537         {
538                 bool show_original = false;
539
540                 appendStringInfoString(s, "\n");
541                 appendStringInfoSpaces(s, TEXT_INDENT_DETAILS(level, exind));
542                 appendStringInfoString(s, "Buckets: ");
543                 appendStringInfoString(s, v->hash_buckets);
544
545                 /* See show_hash_info() in explain.c for details */
546                 if ((v->org_hash_buckets &&
547                          strcmp(v->hash_buckets, v->org_hash_buckets) != 0) ||
548                         (v->org_hash_batches &&
549                          strcmp(v->hash_batches, v->org_hash_batches) != 0))
550                         show_original = true;
551
552                 if (show_original && v->org_hash_buckets)
553                 {
554                         appendStringInfoString(s, " (originally ");
555                         appendStringInfoString(s, v->org_hash_buckets);
556                         appendStringInfoChar(s, ')');
557                 }
558
559                 if (!ISZERO(v->hash_batches))
560                 {
561                         appendStringInfoString(s, "  Batches: ");
562                         appendStringInfoString(s, v->hash_batches);
563                         if (show_original && v->org_hash_batches)
564                         {
565                                 appendStringInfoString(s, " (originally ");
566                                 appendStringInfoString(s, v->org_hash_batches);
567                                 appendStringInfoChar(s, ')');
568                         }
569                 }
570                 if (!ISZERO(v->peak_memory_usage))
571                 {
572                         appendStringInfoString(s, "  Memory Usage: ");
573                         appendStringInfoString(s, v->peak_memory_usage);
574                         appendStringInfoString(s, "kB");
575                 }
576         }
577
578         print_prop_if_exists(s, "Heap Fetches: ", v->heap_fetches, level, exind);
579         print_prop_if_exists(s, "Conflict Resolution: ",
580                                                  v->conflict_resolution, level, exind);
581         print_propstr_if_exists(s, "Conflict Arbiter Indexes: ",
582                                                         v->conflict_arbiter_indexes, level, exind);
583         print_prop_if_exists(s, "Tuples Inserted: ",
584                                                  v->tuples_inserted, level, exind);
585         print_prop_if_exists(s, "Conflicting Tuples: ",
586                                                  v->conflicting_tuples, level, exind);
587
588         if (!ISZERO(v->shared_hit_blks) ||
589                 !ISZERO(v->shared_read_blks) ||
590                 !ISZERO(v->shared_dirtied_blks) ||
591                 !ISZERO(v->shared_written_blks))
592         {
593                 appendStringInfoString(s, "\n");
594                 appendStringInfoSpaces(s, TEXT_INDENT_DETAILS(level, exind));
595                 appendStringInfoString(s, "Buffers: shared");
596
597                 if (!ISZERO(v->shared_hit_blks))
598                 {
599                         appendStringInfoString(s, " hit=");
600                         appendStringInfoString(s, v->shared_hit_blks);
601                         comma =true;
602                 }
603                 if (!ISZERO(v->shared_read_blks))
604                 {
605                         appendStringInfoString(s, " read=");
606                         appendStringInfoString(s, v->shared_read_blks);
607                         comma =true;
608                 }
609                 if (!ISZERO(v->shared_dirtied_blks))
610                 {
611                         appendStringInfoString(s, " dirtied=");
612                         appendStringInfoString(s, v->shared_dirtied_blks);
613                         comma =true;
614                 }
615                 if (!ISZERO(v->shared_written_blks))
616                 {
617                         appendStringInfoString(s, " written=");
618                         appendStringInfoString(s, v->shared_written_blks);
619                         comma =true;
620                 }
621         }
622         if (!ISZERO(v->local_hit_blks) ||
623                 !ISZERO(v->local_read_blks) ||
624                 !ISZERO(v->local_dirtied_blks) ||
625                 !ISZERO(v->local_written_blks))
626         {
627                 if (comma)
628                         appendStringInfoString(s, ", ");
629                 else
630                 {
631                         appendStringInfoSpaces(s, TEXT_INDENT_DETAILS(level, exind));
632                         appendStringInfoString(s, "Buffers: ");
633                 }
634
635                 appendStringInfoString(s, "local");
636                 if (!ISZERO(v->local_hit_blks))
637                 {
638                         appendStringInfoString(s, " hit=");
639                         appendStringInfoString(s, v->local_hit_blks);
640                         comma =true;
641                 }
642                 if (!ISZERO(v->local_read_blks))
643                 {
644                         appendStringInfoString(s, " read=");
645                         appendStringInfoString(s, v->local_read_blks);
646                         comma =true;
647                 }
648                 if (!ISZERO(v->local_dirtied_blks))
649                 {
650                         appendStringInfoString(s, " dirtied=");
651                         appendStringInfoString(s, v->local_dirtied_blks);
652                         comma =true;
653                 }
654                 if (!ISZERO(v->local_written_blks))
655                 {
656                         appendStringInfoString(s, " written=");
657                         appendStringInfoString(s, v->local_written_blks);
658                         comma =true;
659                 }
660         }
661         if (!ISZERO(v->temp_read_blks) ||
662                 !ISZERO(v->temp_written_blks))
663         {
664                 if (comma)
665                         appendStringInfoString(s, ", ");
666                 else
667                 {
668                         appendStringInfoSpaces(s, TEXT_INDENT_DETAILS(level, exind));
669                         appendStringInfoString(s, "Buffers: ");
670                 }
671
672                 appendStringInfoString(s, "temp");
673                 if (!ISZERO(v->temp_read_blks))
674                 {
675                         appendStringInfoString(s, " read=");
676                         appendStringInfoString(s, v->temp_read_blks);
677                         comma =true;
678                 }
679                 if (!ISZERO(v->temp_written_blks))
680                 {
681                         appendStringInfoString(s, " written=");
682                         appendStringInfoString(s, v->temp_written_blks);
683                         comma =true;
684                 }
685         }
686         if (!ISZERO(v->io_read_time) ||
687                 !ISZERO(v->io_write_time))
688         {
689                 /* Feed a line if any of Buffers: items has been shown */
690                 if (comma)
691                         appendStringInfoString(s, "\n");
692                         
693                 appendStringInfoSpaces(s, TEXT_INDENT_DETAILS(level, exind));
694                 appendStringInfoString(s, "I/O Timings: ");
695
696                 if (!ISZERO(v->io_read_time))
697                 {
698                         appendStringInfoString(s, " read=");
699                         appendStringInfoString(s, v->io_read_time);
700                 }
701                 if (!ISZERO(v->io_write_time))
702                 {
703                         appendStringInfoString(s, " write=");
704                         appendStringInfoString(s, v->io_write_time);
705                 }
706         }
707 }
708
709 static void
710 print_current_trig_node(pgspParserContext *ctx)
711 {
712         node_vals *v = ctx->nodevals;
713         StringInfo s = ctx->dest;
714
715         if (HASSTRING(v->trig_name) && !ISZERO(v->trig_time))
716         {
717                 if (s->len > 0)
718                         appendStringInfoString(s, "\n");
719                 appendStringInfoString(s, "Trigger ");
720                 appendStringInfoString(s, v->trig_name);
721                 appendStringInfoString(s, ": time=");
722                 appendStringInfoString(s, v->trig_time);
723                 appendStringInfoString(s, " calls=");
724                 appendStringInfoString(s, v->trig_calls);
725         }
726 }
727
728
729 static void
730 clear_nodeval(node_vals *vals)
731 {
732         memset(vals, 0, sizeof(node_vals));
733 }
734
735 static void
736 json_text_objstart(void *state)
737 {
738         pgspParserContext *ctx = (pgspParserContext *)state;
739         ctx->level++;
740
741         /* Create new grouping sets or reset existing ones */
742         if (ctx->current_list == P_GroupSets)
743         {
744                 node_vals *v = ctx->nodevals;
745
746                 ctx->tmp_gset = (grouping_set*) palloc0(sizeof(grouping_set));
747                 if (!v->sort_key)
748                         v->sort_key = makeStringInfo();
749                 if (!v->group_key)
750                         v->group_key = makeStringInfo();
751                 if (!v->hash_key)
752                         v->hash_key = makeStringInfo();
753                 resetStringInfo(v->sort_key);
754                 resetStringInfo(v->group_key);
755                 resetStringInfo(v->hash_key);
756         }
757 }
758
759 static void
760 json_text_objend(void *state)
761 {
762         pgspParserContext *ctx = (pgspParserContext *)state;
763
764         /* Print current node if the object is a P_Plan or a child of P_Plans */
765         if (bms_is_member(ctx->level - 1, ctx->plan_levels))
766         {
767                 print_current_node(ctx);
768                 clear_nodeval(ctx->nodevals);
769         }
770         else if (ctx->section == P_Triggers)
771         {
772                 print_current_trig_node(ctx);
773                 clear_nodeval(ctx->nodevals);
774         }
775         else if (ctx->current_list == P_TargetTables)
776         {
777                 /* Move the current working taget tables into nodevals */
778                 node_vals *v = ctx->nodevals;
779
780                 if (!ctx->work_str)
781                         ctx->work_str = makeStringInfo();
782
783                 resetStringInfo(ctx->work_str);
784                 appendStringInfoString(ctx->work_str, v->operation);
785                 print_obj_name0(ctx->work_str, v->obj_name, v->schema_name, v->alias);
786                 v->target_tables = lappend(v->target_tables,
787                                                                    pstrdup(ctx->work_str->data));
788                 resetStringInfo(ctx->work_str);
789         }
790         else if (ctx->current_list == P_GroupSets && ctx->tmp_gset)
791         {
792                 /* Move working grouping set into nodevals */
793                 node_vals *v = ctx->nodevals;
794
795                 /* Copy sort key if any */
796                 if (v->sort_key->data[0])
797                 {
798                         ctx->tmp_gset->sort_keys = strdup(v->sort_key->data);
799                         resetStringInfo(v->sort_key);
800                 }
801
802                 /* Move working grouping set into nodevals */
803                 ctx->nodevals->grouping_sets = 
804                         lappend(v->grouping_sets, ctx->tmp_gset);
805                 ctx->tmp_gset = NULL;
806         }
807
808         ctx->last_elem_is_object = true;
809         ctx->level--;
810 }
811
812 static void 
813 json_text_arrstart(void *state)
814 {
815         pgspParserContext *ctx = (pgspParserContext *)state;
816
817         if (ctx->current_list == P_GroupSets)
818         {
819                 ctx->wlist_level++;
820         }
821 }
822
823 static void
824 json_text_arrend(void *state)
825 {
826         pgspParserContext *ctx = (pgspParserContext *)state;
827
828         if (ctx->current_list == P_GroupSets)
829         {
830                 /*
831                  * wlist_level means that now at the end of innermost list of Group
832                  * Keys
833                  */
834                 if (ctx->wlist_level  == 3)
835                 {
836                         node_vals *v = ctx->nodevals;
837
838                         /*
839                          * At this point, v->group_key holds the keys in "Group Keys". The
840                          * item holds a double-nested list and the innermost lists are to
841                          * go into individual "Group Key" lines. Empty innermost list is
842                          * represented as "()" there. See explain.c of PostgreSQL.
843                          */
844                         ctx->tmp_gset->key_type = "Group Key: ";
845                         if (v->group_key->data[0])
846                         {
847                                 ctx->tmp_gset->group_keys =
848                                         lappend(ctx->tmp_gset->group_keys,
849                                                         pstrdup(v->group_key->data));
850                         }
851                         else if (v->hash_key->data[0])
852                         {
853                                 ctx->tmp_gset->group_keys =
854                                         lappend(ctx->tmp_gset->group_keys,
855                                                         pstrdup(v->hash_key->data));
856                                 ctx->tmp_gset->key_type = "Hash Key: ";
857                         }
858                         else
859                                 ctx->tmp_gset->group_keys =
860                                         lappend(ctx->tmp_gset->group_keys, "()");
861
862                         resetStringInfo(ctx->nodevals->group_key);
863                         resetStringInfo(ctx->nodevals->hash_key);
864                 }
865                 ctx->wlist_level--;
866         }
867 }
868
869 static void
870 json_text_ofstart(void *state, char *fname, bool isnull)
871 {
872         word_table *p;
873         pgspParserContext *ctx = (pgspParserContext *)state;
874
875         ctx->setter = NULL;
876         p = search_word_table(propfields, fname, PGSP_JSON_TEXTIZE);
877
878         if (!p)
879         {
880                 ereport(DEBUG2,
881                                 (errmsg("Short JSON parser encoutered unknown field name: \"%s\", skipped.", fname),
882                                  errdetail_log("INPUT: \"%s\"", ctx->org_string)));
883
884                 /*
885                  * Unknown properties may be put by foreign data wrappers and assumed
886                  * to be printed in the same format to JSON properties. We store in
887                  * nodevals a string emittable as-is in text explains.
888                  */
889                 ctx->setter = SETTER(_undef);
890                 ctx->nodevals->_undef_newelem = true;
891                 ctx->setter(ctx->nodevals, fname);
892                 ctx->nodevals->_undef_newelem = false;
893                 ctx->setter(ctx->nodevals, ": ");
894         }
895         else
896         {
897                 /*
898                  * Print the current node immediately if the next level of
899                  * Plan/Plans/Worers comes. This assumes that the plan output is
900                  * strcutured tail-recursively.
901                  */
902                 if (p->tag == P_Plan || p->tag == P_Plans || p->tag == P_Workers)
903                 {
904                         print_current_node(ctx);
905                         clear_nodeval(ctx->nodevals);
906                 }
907                 else if (p->tag == P_TargetTables)
908                 {
909                         node_vals *v = ctx->nodevals;
910
911                         ctx->current_list = p->tag;
912                         ctx->list_fname = fname;
913
914                         /* stash some data */
915                         v->tmp_obj_name = v->obj_name;
916                         v->tmp_schema_name = v->schema_name;
917                         v->tmp_alias = v->alias;
918                 }
919
920                 if (p->tag == P_GroupSets || p->tag == P_Workers)
921                 {
922                         ctx->current_list = p->tag;
923                         ctx->list_fname = fname;
924                         ctx->wlist_level = 0;
925                 }
926
927                 /*
928                  * This paser prints partial result at the end of every P_Plan object,
929                  * which includes elements in P_Plans list.
930                  */
931                 if (p->tag == P_Plan || p->tag == P_Plans || p->tag == P_Workers)
932                         ctx->plan_levels = bms_add_member(ctx->plan_levels, ctx->level);
933                 else
934                         ctx->plan_levels = bms_del_member(ctx->plan_levels, ctx->level);
935
936                 if (p->tag == P_Plan || p->tag == P_Triggers)
937                         ctx->section = p->tag;
938                 ctx->setter = p->setter;
939         }
940 }
941
942 static void
943 json_text_ofend(void *state, char *fname, bool isnull)
944 {
945         pgspParserContext *ctx = (pgspParserContext *)state;
946         node_vals *v = ctx->nodevals;
947
948         /* We assume that lists with same fname will not be nested */
949         if (ctx->list_fname && strcmp(fname, ctx->list_fname) == 0)
950         {
951                 /* Restore stashed data, see json_text_ofstart */
952                 if (ctx->current_list == P_TargetTables)
953                 {
954                         v->obj_name = v->tmp_obj_name;
955                         v->schema_name = v->tmp_schema_name;
956                         v->alias = v->tmp_alias;
957                 }
958
959                 ctx->list_fname = NULL;
960                 ctx->current_list = P_Invalid;
961         }
962
963         /* Planning/Execution time to be appeared at the end of plan */
964         if (HASSTRING(v->plan_time) ||
965                 HASSTRING(v->exec_time))
966         {
967                 if (HASSTRING(v->plan_time))
968                 {
969                         appendStringInfoString(ctx->dest, "\nPlanning Time: ");
970                         appendStringInfoString(ctx->dest, v->plan_time);
971                         appendStringInfoString(ctx->dest, " ms");
972                 }
973                 else
974                 {
975                         appendStringInfoString(ctx->dest, "\nExecution Time: ");
976                         appendStringInfoString(ctx->dest, v->exec_time);
977                         appendStringInfoString(ctx->dest, " ms");
978                 }
979                 clear_nodeval(v);
980         }
981 }
982
983 static void
984 json_text_scalar(void *state, char *token, JsonTokenType tokentype)
985 {
986         pgspParserContext *ctx = (pgspParserContext *)state;
987
988         if (ctx->setter)
989                 ctx->setter(ctx->nodevals, token);
990 }
991
992 char *
993 pgsp_json_textize(char *json)
994 {
995         JsonLexContext lex;
996         JsonSemAction sem;
997         pgspParserContext       ctx;
998
999         init_json_lex_context(&lex, json);
1000         init_parser_context(&ctx, PGSP_JSON_TEXTIZE, json, NULL, 0);
1001
1002         ctx.nodevals = (node_vals*)palloc0(sizeof(node_vals));
1003
1004         sem.semstate = (void*)&ctx;
1005         sem.object_start       = json_text_objstart;
1006         sem.object_end         = json_text_objend;
1007         sem.array_start        = json_text_arrstart;
1008         sem.array_end          = json_text_arrend;
1009         sem.object_field_start = json_text_ofstart;
1010         sem.object_field_end   = json_text_ofend;
1011         sem.array_element_start= NULL;
1012         sem.array_element_end  = NULL;
1013         sem.scalar             = json_text_scalar;
1014
1015
1016         if (!run_pg_parse_json(&lex, &sem))
1017         {
1018                 if (ctx.nodevals->node_type)
1019                         print_current_node(&ctx);
1020
1021                 if (ctx.dest->len > 0 &&
1022                         ctx.dest->data[ctx.dest->len - 1] != '\n')
1023                         appendStringInfoChar(ctx.dest, '\n');
1024                 
1025                 if (ctx.dest->len == 0)
1026                         appendStringInfoString(ctx.dest, "<Input was not JSON>");
1027                 else
1028                         appendStringInfoString(ctx.dest, "<truncated>");
1029         }
1030
1031         pfree(ctx.nodevals);
1032
1033         return ctx.dest->data;
1034 }