OSDN Git Service

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