OSDN Git Service

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