OSDN Git Service

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