1 /*-------------------------------------------------------------------------
3 * pgsp_json_text.h: Text plan generator for pg_store_plans.
5 * Copyright (c) 2012-2022, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
8 * pg_store_plans/pgsp_json_text.c
10 *-------------------------------------------------------------------------
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"
22 #include "common/jsonapi.h"
24 #include "utils/builtins.h"
26 #include "pgsp_json_text.h"
27 #include "pgsp_json_int.h"
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);
46 /* Parser callbacks for plan textization */
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
57 if(vals->_undef_newelem)
60 vals->_undef = lappend(vals->_undef, s);
64 s = llast (vals->_undef);
67 appendStringInfoString(s, val);
74 vals->node_type = val;
75 vals->nodetag = T_Invalid;
77 p = search_word_table(nodetypes, val, PGSP_JSON_TEXTIZE);
80 vals->node_type = (p->textname ? p->textname : p->longname);
81 vals->nodetag = p->tag;
89 p = search_word_table(strategies, val, PGSP_JSON_TEXTIZE);
94 switch (vals->nodetag)
100 vals->node_type = "HashAggregate"; break;
102 vals->node_type = "GroupAggregate"; break;
104 vals->node_type = "MixedAggregate"; break;
111 if (p->tag == S_Hashed)
112 vals->node_type = "HashSetOp";
119 CONVERSION_SETTER(scan_dir, conv_scandir);
120 SQLQUOTE_SETTER(obj_name);
121 SQLQUOTE_SETTER(alias);
122 SQLQUOTE_SETTER(schema_name);
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);
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))
212 print_obj_name0(StringInfo s,
213 const char *obj_name, const char *schema_name, const char *alias)
215 bool on_written = false;
217 if (HASSTRING(obj_name))
220 appendStringInfoString(s, " on ");
221 if (HASSTRING(schema_name))
223 appendStringInfoString(s, schema_name);
224 appendStringInfoChar(s, '.');
226 appendStringInfoString(s, obj_name);
228 if (HASSTRING(alias) &&
229 (!HASSTRING(obj_name) || strcmp(obj_name, alias) != 0))
232 appendStringInfoString(s, " on ");
234 appendStringInfoChar(s, ' ');
235 appendStringInfoString(s, alias);
240 print_obj_name(pgspParserContext *ctx)
242 node_vals *v = ctx->nodevals;
243 StringInfo s = ctx->dest;
245 print_obj_name0(s, v->obj_name, v->schema_name, v->alias);
249 print_prop(StringInfo s, char *prepstr,
250 const char *prop, int level, int exind)
254 appendStringInfoString(s, "\n");
255 appendStringInfoSpaces(s, TEXT_INDENT_DETAILS(level, exind));
257 appendStringInfoString(s, prepstr);
258 appendStringInfoString(s, prop);
262 print_prop_if_exists(StringInfo s, char *prepstr,
263 const char *prop, int level, int exind)
266 print_prop(s, prepstr, prop, level, exind);
270 print_propstr_if_exists(StringInfo s, char *prepstr,
271 StringInfo prop, int level, int exind)
273 if (prop && prop->data[0])
275 appendStringInfoString(s, "\n");
276 appendStringInfoSpaces(s, TEXT_INDENT_DETAILS(level, exind));
277 appendStringInfoString(s, prepstr);
278 appendStringInfoString(s, prop->data);
283 print_groupingsets_if_exists(StringInfo s, List *gss, int level, int exind)
290 grouping_set *gs = (grouping_set *)lfirst (lc);
294 print_prop_if_exists(s, "Sort Key: ", gs->sort_keys, level, exind);
298 foreach (lcg, gs->group_keys)
300 const char *gk = (const char *)lfirst (lcg);
301 print_prop_if_exists(s, gs->key_type, gk, level, exind);
308 print_prop_if_nz(StringInfo s, char *prepstr,
309 const char *prop, int level, int exind)
312 print_prop(s, prepstr, prop, level, exind);
316 print_current_node(pgspParserContext *ctx)
318 node_vals *v = ctx->nodevals;
319 StringInfo s = ctx->dest;
321 int level = ctx->level - 1;
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
331 if (v->node_type == T_Invalid && !HASSTRING(v->worker_number))
335 appendStringInfoString(s, "\n");
336 appendStringInfoSpaces(s, TEXT_INDENT_BASE(level, exind));
338 if (HASSTRING(v->subplan_name))
340 appendStringInfoString(s, v->subplan_name);
341 appendStringInfoString(s, "\n");
343 appendStringInfoSpaces(s, TEXT_INDENT_BASE(level, exind));
346 /* list items doesn't need this header */
347 if (level > 1 && ctx->current_list == P_Invalid)
348 appendStringInfoString(s, "-> ");
350 if (v->parallel_aware)
351 appendStringInfoString(s, "Parallel ");
353 if (v->async_capable)
354 appendStringInfoString(s, "Async ");
360 case T_BitmapHeapScan:
366 case T_WorkTableScan:
368 if (v->nodetag == T_ModifyTable)
369 appendStringInfoString(s, v->operation);
371 appendStringInfoString(s, v->node_type);
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);
388 appendStringInfoString(s, v->node_type);
389 if (v->join_type && strcmp(v->join_type, "Inner") != 0)
391 appendStringInfoChar(s, ' ');
392 appendStringInfoString(s, v->join_type);
394 if (v->nodetag != T_NestLoop)
395 appendStringInfoString(s, " Join");
399 appendStringInfoString(s, v->node_type);
400 print_prop_if_exists(s, " ", v->setopcommand, 0, 0);
404 /* Existence of worker_number suggests this is a Worker node */
405 if (HASSTRING(v->worker_number))
407 appendStringInfoString(s, "Worker");
408 print_prop_if_exists(s, " ", v->worker_number, 0, 0);
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.
418 appendStringInfoString(s, v->node_type);
422 /* Don't show costs for child talbes */
423 if (ctx->current_list == P_TargetTables)
426 if (!ISZERO(v->startup_cost) &&
427 !ISZERO(v->total_cost) &&
428 HASSTRING(v->plan_rows) &&
429 HASSTRING(v->plan_width))
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, ")");
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))
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, " ");
456 appendStringInfoString(s, "rows=");
457 appendStringInfoString(s, v->actual_rows);
459 appendStringInfoString(s, " loops=");
460 appendStringInfoString(s, v->actual_loops);
462 appendStringInfoString(s, ")");
465 foreach(lc, v->target_tables)
467 char *str = (char *)lfirst (lc);
469 appendStringInfoString(s, "\n");
470 appendStringInfoSpaces(s, TEXT_INDENT_DETAILS(level, exind));
471 appendStringInfoString(s, str);
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);
486 if (HASSTRING(v->sampling_method))
488 appendStringInfoString(s, "\n");
489 appendStringInfoSpaces(s, TEXT_INDENT_DETAILS(level, exind));
490 appendStringInfo(s, "Sampling: %s (%s)",
492 v->sampling_params ? v->sampling_params->data : "");
493 if (v->repeatable_seed)
494 appendStringInfo(s, " REPEATABLE (%s)", v->repeatable_seed);
497 print_propstr_if_exists(s, "Sort Key: ", v->sort_key, level, exind);
498 if (HASSTRING(v->sort_method))
500 appendStringInfoString(s, "\n");
501 appendStringInfoSpaces(s, TEXT_INDENT_DETAILS(level, exind));
502 appendStringInfoString(s, "Sort Method: ");
503 appendStringInfoString(s, v->sort_method);
505 if (HASSTRING(v->sort_space_type) &&
506 HASSTRING(v->sort_space_used))
508 appendStringInfoString(s, " ");
509 appendStringInfoString(s, v->sort_space_type);
510 appendStringInfoString(s, ": ");
511 appendStringInfoString(s, v->sort_space_used);
512 appendStringInfoString(s, "kB");
516 print_prop_if_exists(s, "Function Call: ", v->func_call, level, exind);
519 * Emit unknown properties here. The properties are printed in the same
520 * shape with JSON properties as assumed by explain.c.
522 foreach (lc, v->_undef)
524 StringInfo str = (StringInfo) lfirst(lc);
526 appendStringInfoString(s, "\n");
527 appendStringInfoSpaces(s, TEXT_INDENT_DETAILS(level, exind));
528 appendStringInfoString(s, str->data);
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);
540 if (HASSTRING(v->exact_heap_blks) ||
541 HASSTRING(v->lossy_heap_blks))
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);
550 if (!ISZERO(v->hash_buckets))
552 bool show_original = false;
554 appendStringInfoString(s, "\n");
555 appendStringInfoSpaces(s, TEXT_INDENT_DETAILS(level, exind));
556 appendStringInfoString(s, "Buckets: ");
557 appendStringInfoString(s, v->hash_buckets);
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;
566 if (show_original && v->org_hash_buckets)
568 appendStringInfoString(s, " (originally ");
569 appendStringInfoString(s, v->org_hash_buckets);
570 appendStringInfoChar(s, ')');
573 if (!ISZERO(v->hash_batches))
575 appendStringInfoString(s, " Batches: ");
576 appendStringInfoString(s, v->hash_batches);
577 if (show_original && v->org_hash_batches)
579 appendStringInfoString(s, " (originally ");
580 appendStringInfoString(s, v->org_hash_batches);
581 appendStringInfoChar(s, ')');
584 if (!ISZERO(v->peak_memory_usage))
586 appendStringInfoString(s, " Memory Usage: ");
587 appendStringInfoString(s, v->peak_memory_usage);
588 appendStringInfoString(s, "kB");
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);
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))
607 appendStringInfoString(s, "\n");
608 appendStringInfoSpaces(s, TEXT_INDENT_DETAILS(level, exind));
609 appendStringInfoString(s, "Buffers: shared");
611 if (!ISZERO(v->shared_hit_blks))
613 appendStringInfoString(s, " hit=");
614 appendStringInfoString(s, v->shared_hit_blks);
617 if (!ISZERO(v->shared_read_blks))
619 appendStringInfoString(s, " read=");
620 appendStringInfoString(s, v->shared_read_blks);
623 if (!ISZERO(v->shared_dirtied_blks))
625 appendStringInfoString(s, " dirtied=");
626 appendStringInfoString(s, v->shared_dirtied_blks);
629 if (!ISZERO(v->shared_written_blks))
631 appendStringInfoString(s, " written=");
632 appendStringInfoString(s, v->shared_written_blks);
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))
642 appendStringInfoString(s, ", ");
645 appendStringInfoSpaces(s, TEXT_INDENT_DETAILS(level, exind));
646 appendStringInfoString(s, "Buffers: ");
649 appendStringInfoString(s, "local");
650 if (!ISZERO(v->local_hit_blks))
652 appendStringInfoString(s, " hit=");
653 appendStringInfoString(s, v->local_hit_blks);
656 if (!ISZERO(v->local_read_blks))
658 appendStringInfoString(s, " read=");
659 appendStringInfoString(s, v->local_read_blks);
662 if (!ISZERO(v->local_dirtied_blks))
664 appendStringInfoString(s, " dirtied=");
665 appendStringInfoString(s, v->local_dirtied_blks);
668 if (!ISZERO(v->local_written_blks))
670 appendStringInfoString(s, " written=");
671 appendStringInfoString(s, v->local_written_blks);
675 if (!ISZERO(v->temp_read_blks) ||
676 !ISZERO(v->temp_written_blks))
679 appendStringInfoString(s, ", ");
682 appendStringInfoSpaces(s, TEXT_INDENT_DETAILS(level, exind));
683 appendStringInfoString(s, "Buffers: ");
686 appendStringInfoString(s, "temp");
687 if (!ISZERO(v->temp_read_blks))
689 appendStringInfoString(s, " read=");
690 appendStringInfoString(s, v->temp_read_blks);
693 if (!ISZERO(v->temp_written_blks))
695 appendStringInfoString(s, " written=");
696 appendStringInfoString(s, v->temp_written_blks);
700 if (!ISZERO(v->io_read_time) ||
701 !ISZERO(v->io_write_time))
703 /* Feed a line if any of Buffers: items has been shown */
705 appendStringInfoString(s, "\n");
707 appendStringInfoSpaces(s, TEXT_INDENT_DETAILS(level, exind));
708 appendStringInfoString(s, "I/O Timings: ");
710 if (!ISZERO(v->io_read_time))
712 appendStringInfoString(s, " read=");
713 appendStringInfoString(s, v->io_read_time);
715 if (!ISZERO(v->io_write_time))
717 appendStringInfoString(s, " write=");
718 appendStringInfoString(s, v->io_write_time);
724 print_current_trig_node(pgspParserContext *ctx)
726 node_vals *v = ctx->nodevals;
727 StringInfo s = ctx->dest;
729 if (HASSTRING(v->trig_name) && !ISZERO(v->trig_time))
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);
744 clear_nodeval(node_vals *vals)
746 memset(vals, 0, sizeof(node_vals));
750 json_text_objstart(void *state)
752 pgspParserContext *ctx = (pgspParserContext *)state;
755 /* Create new grouping sets or reset existing ones */
756 if (ctx->current_list == P_GroupSets)
758 node_vals *v = ctx->nodevals;
760 ctx->tmp_gset = (grouping_set*) palloc0(sizeof(grouping_set));
762 v->sort_key = makeStringInfo();
764 v->group_key = makeStringInfo();
766 v->hash_key = makeStringInfo();
767 resetStringInfo(v->sort_key);
768 resetStringInfo(v->group_key);
769 resetStringInfo(v->hash_key);
774 json_text_objend(void *state)
776 pgspParserContext *ctx = (pgspParserContext *)state;
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))
781 print_current_node(ctx);
782 clear_nodeval(ctx->nodevals);
784 else if (ctx->section == P_Triggers)
786 print_current_trig_node(ctx);
787 clear_nodeval(ctx->nodevals);
789 else if (ctx->current_list == P_TargetTables)
791 /* Move the current working taget tables into nodevals */
792 node_vals *v = ctx->nodevals;
795 ctx->work_str = makeStringInfo();
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);
804 else if (ctx->current_list == P_GroupSets && ctx->tmp_gset)
806 /* Move working grouping set into nodevals */
807 node_vals *v = ctx->nodevals;
809 /* Copy sort key if any */
810 if (v->sort_key->data[0])
812 ctx->tmp_gset->sort_keys = strdup(v->sort_key->data);
813 resetStringInfo(v->sort_key);
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;
822 ctx->last_elem_is_object = true;
827 json_text_arrstart(void *state)
829 pgspParserContext *ctx = (pgspParserContext *)state;
831 if (ctx->current_list == P_GroupSets)
838 json_text_arrend(void *state)
840 pgspParserContext *ctx = (pgspParserContext *)state;
842 if (ctx->current_list == P_GroupSets)
845 * wlist_level means that now at the end of innermost list of Group
848 if (ctx->wlist_level == 3)
850 node_vals *v = ctx->nodevals;
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.
858 ctx->tmp_gset->key_type = "Group Key: ";
859 if (v->group_key->data[0])
861 ctx->tmp_gset->group_keys =
862 lappend(ctx->tmp_gset->group_keys,
863 pstrdup(v->group_key->data));
865 else if (v->hash_key->data[0])
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: ";
873 ctx->tmp_gset->group_keys =
874 lappend(ctx->tmp_gset->group_keys, "()");
876 resetStringInfo(ctx->nodevals->group_key);
877 resetStringInfo(ctx->nodevals->hash_key);
884 json_text_ofstart(void *state, char *fname, bool isnull)
887 pgspParserContext *ctx = (pgspParserContext *)state;
890 p = search_word_table(propfields, fname, PGSP_JSON_TEXTIZE);
895 (errmsg("Short JSON parser encoutered unknown field name: \"%s\", skipped.", fname),
896 errdetail_log("INPUT: \"%s\"", ctx->org_string)));
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.
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, ": ");
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.
916 if (p->tag == P_Plan || p->tag == P_Plans || p->tag == P_Workers)
918 print_current_node(ctx);
919 clear_nodeval(ctx->nodevals);
921 else if (p->tag == P_TargetTables)
923 node_vals *v = ctx->nodevals;
925 ctx->current_list = p->tag;
926 ctx->list_fname = fname;
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;
934 if (p->tag == P_GroupSets || p->tag == P_Workers)
936 ctx->current_list = p->tag;
937 ctx->list_fname = fname;
938 ctx->wlist_level = 0;
942 * This paser prints partial result at the end of every P_Plan object,
943 * which includes elements in P_Plans list.
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);
948 ctx->plan_levels = bms_del_member(ctx->plan_levels, ctx->level);
950 if (p->tag == P_Plan || p->tag == P_Triggers)
951 ctx->section = p->tag;
952 ctx->setter = p->setter;
957 json_text_ofend(void *state, char *fname, bool isnull)
959 pgspParserContext *ctx = (pgspParserContext *)state;
960 node_vals *v = ctx->nodevals;
962 /* We assume that lists with same fname will not be nested */
963 if (ctx->list_fname && strcmp(fname, ctx->list_fname) == 0)
965 /* Restore stashed data, see json_text_ofstart */
966 if (ctx->current_list == P_TargetTables)
968 v->obj_name = v->tmp_obj_name;
969 v->schema_name = v->tmp_schema_name;
970 v->alias = v->tmp_alias;
973 ctx->list_fname = NULL;
974 ctx->current_list = P_Invalid;
977 /* Planning/Execution time to be appeared at the end of plan */
978 if (HASSTRING(v->plan_time) ||
979 HASSTRING(v->exec_time))
981 if (HASSTRING(v->plan_time))
983 appendStringInfoString(ctx->dest, "\nPlanning Time: ");
984 appendStringInfoString(ctx->dest, v->plan_time);
985 appendStringInfoString(ctx->dest, " ms");
989 appendStringInfoString(ctx->dest, "\nExecution Time: ");
990 appendStringInfoString(ctx->dest, v->exec_time);
991 appendStringInfoString(ctx->dest, " ms");
998 json_text_scalar(void *state, char *token, JsonTokenType tokentype)
1000 pgspParserContext *ctx = (pgspParserContext *)state;
1003 ctx->setter(ctx->nodevals, token);
1007 pgsp_json_textize(char *json)
1011 pgspParserContext ctx;
1013 init_json_lex_context(&lex, json);
1014 init_parser_context(&ctx, PGSP_JSON_TEXTIZE, json, NULL, 0);
1016 ctx.nodevals = (node_vals*)palloc0(sizeof(node_vals));
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;
1030 if (!run_pg_parse_json(&lex, &sem))
1032 if (ctx.nodevals->node_type)
1033 print_current_node(&ctx);
1035 if (ctx.dest->len > 0 &&
1036 ctx.dest->data[ctx.dest->len - 1] != '\n')
1037 appendStringInfoChar(ctx.dest, '\n');
1039 if (ctx.dest->len == 0)
1040 appendStringInfoString(ctx.dest, "<Input was not JSON>");
1042 appendStringInfoString(ctx.dest, "<truncated>");
1045 pfree(ctx.nodevals);
1047 return ctx.dest->data;