OSDN Git Service

Add new columns to pg_store_plans view
[pgstoreplans/pg_store_plans.git] / pgsp_json.c
1 /*-------------------------------------------------------------------------
2  *
3  * pgsp_json.c: Plan handler for JSON/XML/YAML style plans
4  *
5  * Copyright (c) 2012-2022, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
6  *
7  * IDENTIFICATION
8  *        pg_store_plans/pgsp_json.c
9  *
10  *-------------------------------------------------------------------------
11  */
12
13 #include "postgres.h"
14 #include "miscadmin.h"
15 #include "nodes/nodes.h"
16 #include "nodes/parsenodes.h"
17 #include "nodes/bitmapset.h"
18 #include "parser/scanner.h"
19 #include "parser/gram.h"
20 #include "utils/xml.h"
21 #include "utils/json.h"
22 #if PG_VERSION_NUM < 130000
23 #include "utils/jsonapi.h"
24 #else
25 #include "common/jsonapi.h"
26 #endif
27 #include "pgsp_json.h"
28 #include "pgsp_json_int.h"
29
30 #define INDENT_STEP 2
31
32
33 void normalize_expr(char *expr, bool preserve_space);
34 static const char *converter_core(word_table *tbl,
35                                                  const char *src, pgsp_parser_mode mode);
36
37 static void json_objstart(void *state);
38 static void json_objend(void *state);
39 static void json_arrstart(void *state);
40 static void json_arrend(void *state);
41 static void json_ofstart(void *state, char *fname, bool isnull);
42 static void json_aestart(void *state, bool isnull);
43 static void json_scalar(void *state, char *token, JsonTokenType tokentype);
44
45 static void yaml_objstart(void *state);
46 static void yaml_objend(void *state);
47 static void yaml_arrstart(void *state);
48 static void yaml_arrend(void *state);
49 static void yaml_ofstart(void *state, char *fname, bool isnull);
50 static void yaml_aestart(void *state, bool isnull);
51 static void yaml_scalar(void *state, char *token, JsonTokenType tokentype);
52
53 static void adjust_wbuf(pgspParserContext *ctx, int len);
54 static char *hyphenate_words(pgspParserContext *ctx, char *src);
55 static void xml_objstart(void *state);
56 static void xml_objend(void *state);
57 static void xml_arrend(void *state);
58 static void xml_ofstart(void *state, char *fname, bool isnull);
59 static void xml_ofend(void *state, char *fname, bool isnull);
60 static void xml_aestart(void *state, bool isnull);
61 static void xml_aeend(void *state, bool isnull);
62 static void xml_scalar(void *state, char *token, JsonTokenType tokentype) ;
63
64 static void init_json_semaction(JsonSemAction *sem,
65                                                                                   pgspParserContext *ctx);
66
67 word_table propfields[] =
68 {
69         {P_NodeType,            "t" ,"Node Type",                       NULL, true,  conv_nodetype,             SETTER(node_type)},
70         {P_RelationShip,        "h" ,"Parent Relationship",     NULL, true,  conv_relasionship, NULL},
71         {P_RelationName,        "n" ,"Relation Name",           NULL, true,  NULL,                              SETTER(obj_name)},
72         {P_FunctioName,         "f" ,"Function Name",           NULL, true,  NULL,                              SETTER(obj_name)},
73         {P_IndexName,           "i" ,"Index Name",                      NULL, true,  NULL,                              SETTER(index_name)},
74         {P_CTEName,                     "c" ,"CTE Name",                        NULL, true,  NULL,                              SETTER(obj_name)},
75         {P_TrgRelation,         "w" ,"Relation",                        NULL, true,  NULL,                              SETTER(trig_relation)},
76         {P_Schema,                      "s" ,"Schema",                          NULL, true,  NULL,                              SETTER(schema_name)},
77         {P_Alias,                       "a" ,"Alias",                           NULL, true,  NULL,                              SETTER(alias)},
78         {P_Output,                      "o" ,"Output",                          NULL, true,  conv_expression,   SETTER(output)},
79         {P_ScanDir,                     "d" ,"Scan Direction",          NULL, true,  conv_scandir,              SETTER(scan_dir)},
80         {P_MergeCond,           "m" ,"Merge Cond",                      NULL, true,  conv_expression,   SETTER(merge_cond)},
81         {P_Strategy,            "g" ,"Strategy",                        NULL, true,  conv_strategy,             SETTER(strategy)},
82         {P_JoinType,            "j" ,"Join Type",                       NULL, true,  conv_jointype,             SETTER(join_type)},
83         {P_SortMethod,          "e" ,"Sort Method",                     NULL, true,  conv_sortmethod,   SETTER(sort_method)},
84         {P_SortKey,                     "k" ,"Sort Key",                        NULL, true,  conv_expression,   SETTER(sort_key)},
85         {P_Filter,                      "5" ,"Filter",                          NULL, true,  conv_expression,   SETTER(filter)},
86         {P_JoinFilter,          "6" ,"Join Filter",                     NULL, true,  conv_expression,   SETTER(join_filter)},
87         {P_HashCond,            "7" ,"Hash Cond",                       NULL, true,  conv_expression,   SETTER(hash_cond)},
88         {P_IndexCond,           "8" ,"Index Cond",                      NULL, true,  conv_expression,   SETTER(index_cond)},
89         {P_TidCond,                     "9" ,"TID Cond",                        NULL, true,  conv_expression,   SETTER(tid_cond)},
90         {P_RecheckCond,         "0" ,"Recheck Cond",            NULL, true,  conv_expression,   SETTER(recheck_cond)},
91         {P_Operation,           "!" ,"Operation",                       NULL, true,  conv_operation,    SETTER(operation)},
92         {P_SubplanName,         "q" ,"Subplan Name",            NULL, true,  NULL,                              SETTER(subplan_name)},
93         {P_Command,                     "b" ,"Command",                         NULL, true,  conv_setsetopcommand,SETTER(setopcommand)},
94         {P_Triggers,            "r" ,"Triggers",                        NULL, true,  NULL,                              NULL},
95         {P_Trigger,                     "u" ,"Trigger",                         NULL, true,  NULL,                              SETTER(node_type)},
96         {P_TriggerName,         "v" ,"Trigger Name",            NULL, true,  NULL,                              SETTER(trig_name)},
97         {P_ConstraintName,      "x" ,"Constraint Name",         NULL, true,  NULL,                              NULL},
98         {P_Plans,                       "l" ,"Plans",                           NULL, true,  NULL,                              NULL},
99         {P_Plan,                        "p" ,"Plan",                            NULL, true,  NULL,                              NULL},
100         {P_GroupKey,            "-" ,"Group Key",                       NULL, true,  NULL,                              SETTER(group_key)},
101         {P_GroupSets,           "=" ,"Grouping Sets",           NULL, true,  NULL,                              NULL},
102         {P_GroupKeys,           "\\" ,"Group Keys",                     NULL, true,  NULL,                              SETTER(group_key)},
103
104         {P_HashKeys,            "~" ,"Hash Keys",                       NULL, true,  NULL,                              SETTER(hash_key)},
105         {P_HashKey,             "|" ,"Hash Key",                        NULL, true,  NULL,                              SETTER(hash_key)},
106
107         {P_Parallel,            "`" ,"Parallel Aware",          NULL, true,  NULL,                              SETTER(parallel_aware)},
108         {P_PartialMode,         ">" ,"Partial Mode",            NULL, true,  conv_partialmode,SETTER(partial_mode)},
109         {P_WorkersPlanned,      "{" ,"Workers Planned",         NULL, true,  NULL,                              SETTER(workers_planned)},
110         {P_WorkersLaunched, "}" ,"Workers Launched",    NULL, true,  NULL,                              SETTER(workers_launched)},
111         {P_InnerUnique,         "?" ,"Inner Unique",            NULL, true,  NULL,                              SETTER(inner_unique)},
112         {P_AsyncCapable,        "ac", "Async Capable",          NULL, true,  NULL,                              SETTER(async_capable)},
113
114         /* Values of these properties are ignored on normalization */
115         {P_FunctionCall,        "y" ,"Function Call",           NULL, false, NULL,                              SETTER(func_call)},
116         {P_StartupCost,         "1" ,"Startup Cost",            NULL, false, NULL,                              SETTER(startup_cost)},
117         {P_TotalCost,           "2" ,"Total Cost",                      NULL, false, NULL,                              SETTER(total_cost)},
118         {P_PlanRows,            "3" ,"Plan Rows",                       NULL, false, NULL,                              SETTER(plan_rows)},
119         {P_PlanWidth,           "4" ,"Plan Width",                      NULL, false, NULL,                              SETTER(plan_width)},
120         {P_ActualStartupTime,"A","Actual Startup Time", NULL, false, NULL,                              SETTER(actual_startup_time)},
121         {P_ActualTotalTime, "B" ,"Actual Total Time",   NULL, false, NULL,                              SETTER(actual_total_time)},
122         {P_ActualRows,          "C" ,"Actual Rows",                     NULL, false, NULL,                              SETTER(actual_rows)},
123         {P_ActualLoops,         "D" ,"Actual Loops",            NULL, false, NULL,                              SETTER(actual_loops)},
124         {P_HeapFetches,         "E" ,"Heap Fetches",            NULL, false, NULL,                              SETTER(heap_fetches)},
125         {P_SharedHitBlks,       "F" ,"Shared Hit Blocks",       NULL, false, NULL,                              SETTER(shared_hit_blks)},
126         {P_SharedReadBlks,      "G" ,"Shared Read Blocks",      NULL, false, NULL,                              SETTER(shared_read_blks)},
127         {P_SharedDirtiedBlks,"H","Shared Dirtied Blocks",NULL,false, NULL,                              SETTER(shared_dirtied_blks)},
128         {P_SharedWrittenBlks,"I","Shared Written Blocks",NULL,false, NULL,                              SETTER(shared_written_blks)},
129         {P_LocalHitBlks,        "J" ,"Local Hit Blocks",        NULL, false, NULL,                              SETTER(local_hit_blks)},
130         {P_LocalReadBlks,       "K" ,"Local Read Blocks",       NULL, false, NULL,                              SETTER(local_read_blks)},
131         {P_LocalDirtiedBlks,"L" ,"Local Dirtied Blocks",NULL, false, NULL,                              SETTER(local_dirtied_blks)},
132         {P_LocalWrittenBlks,"M" ,"Local Written Blocks",NULL, false, NULL,                              SETTER(local_written_blks)},
133         {P_TempReadBlks,        "N" ,"Temp Read Blocks",        NULL, false, NULL,                              SETTER(temp_read_blks)},
134         {P_TempWrittenBlks, "O" ,"Temp Written Blocks", NULL, false, NULL,                              SETTER(temp_written_blks)},
135         {P_IOReadTime,          "P" ,"I/O Read Time",           NULL, false, NULL,                              SETTER(io_read_time)},
136         {P_IOWwriteTime,        "Q" ,"I/O Write Time",          NULL, false, NULL,                              SETTER(io_write_time)},
137         {P_SortSpaceUsed,       "R" ,"Sort Space Used",         NULL, false, NULL,                              SETTER(sort_space_used)},
138         {P_SortSpaceType,       "S" ,"Sort Space Type",         NULL, false, conv_sortspacetype,SETTER(sort_space_type)},
139         {P_PeakMemoryUsage,     "T" ,"Peak Memory Usage",       NULL, false, NULL,                              SETTER(peak_memory_usage)},
140         {P_OrgHashBatches,      "U","Original Hash Batches",NULL, false, NULL,                          SETTER(org_hash_batches)},
141         {P_OrgHashBuckets,      "*","Original Hash Buckets",NULL, false, NULL,                          SETTER(org_hash_buckets)},
142         {P_HashBatches,         "V" ,"Hash Batches",            NULL, false, NULL,                              SETTER(hash_batches)},
143         {P_HashBuckets,         "W" ,"Hash Buckets",            NULL, false, NULL,                              SETTER(hash_buckets)},
144         {P_RowsFilterRmvd,      "X" ,"Rows Removed by Filter",NULL,false,NULL,                          SETTER(filter_removed)},
145         {P_RowsIdxRchkRmvd, "Y" ,"Rows Removed by Index Recheck",NULL,false, NULL,              SETTER(idxrchk_removed)},
146         {P_TrgTime,                     "Z" ,"Time",                            NULL, false,  NULL,                             SETTER(trig_time)},
147         {P_TrgCalls,            "z" ,"Calls",                           NULL, false,  NULL,                             SETTER(trig_calls)},
148         {P_PlanTime,            "#" ,"Planning Time",           NULL, false,  NULL,                             SETTER(plan_time)},
149         {P_ExecTime,            "$" ,"Execution Time",          NULL, false,  NULL,                             SETTER(exec_time)},
150         {P_ExactHeapBlks,       "&" ,"Exact Heap Blocks",       NULL, false,  NULL,                             SETTER(exact_heap_blks)},
151         {P_LossyHeapBlks,       "(" ,"Lossy Heap Blocks",       NULL, false,  NULL,                             SETTER(lossy_heap_blks)},
152         {P_RowsJoinFltRemvd,")" ,"Rows Removed by Join Filter", NULL, false,  NULL,             SETTER(joinfilt_removed)},
153         {P_TargetTables,    "_" ,"Target Tables",               NULL, false,  NULL,                             NULL},
154         {P_ConfRes,                     "%" ,"Conflict Resolution",     NULL, false,  NULL,                     SETTER(conflict_resolution)},
155         {P_ConfArbitIdx,    "@" ,"Conflict Arbiter Indexes",NULL, false,  NULL,                 SETTER(conflict_arbiter_indexes)},
156         {P_TuplesInserted,  "^" ,"Tuples Inserted",             NULL, false,  NULL,                             SETTER(tuples_inserted)},
157         {P_ConfTuples,          "+" ,"Conflicting Tuples",      NULL, false,  NULL,                             SETTER(conflicting_tuples)},
158         {P_SamplingMethod,  ":"  ,"Sampling Method" ,   NULL, false,  NULL,                             SETTER(sampling_method)},
159         {P_SamplingParams,  ";"  ,"Sampling Parameters" , NULL, false,  NULL,                   SETTER(sampling_params)},
160         {P_RepeatableSeed,  "<"  ,"Repeatable Seed" ,   NULL, false,  NULL,                             SETTER(repeatable_seed)},
161         {P_Workers,             "[" ,"Workers",                         NULL, false,  NULL,                             NULL},
162         {P_WorkerNumber,    "]" ,"Worker Number",               NULL, false,  NULL,                             SETTER(worker_number)},
163         {P_TableFuncName,   "aa" ,"Table Function Name",NULL, false,  NULL,                     SETTER(table_func_name)},
164
165         {P_PresortedKey,    "pk" ,"Presorted Key"          ,NULL, false,  NULL,                 SETTER(presorted_key)},
166         {P_FullsortGroups,  "fg" ,"Full-sort Groups"   ,NULL, false,  NULL,                     NULL},
167         {P_SortMethodsUsed, "su" ,"Sort Methods Used"  ,NULL, false,  NULL,                     SETTER(sortmethod_used)},
168         {P_SortSpaceMemory, "sm" ,"Sort Space Memory"  ,NULL, false,  NULL,                     SETTER(sortspace_mem)},
169         {P_GroupCount,          "gc" ,"Group Count"        ,NULL, false,  NULL,                 SETTER(group_count)},
170         {P_AvgSortSpcUsed,  "as" ,"Average Sort Space Used",NULL, false,  NULL,         SETTER(avg_sortspc_used)},
171         {P_PeakSortSpcUsed, "ps" ,"Peak Sort Space Used",NULL, false,  NULL,            SETTER(peak_sortspc_used)},
172         {P_PreSortedGroups, "pg" ,"Pre-sorted Groups"  ,NULL, false,  NULL,                     NULL},
173
174         {P_Invalid, NULL, NULL, NULL, false, NULL, NULL}
175 };
176
177 word_table nodetypes[] =
178 {
179         {T_Result,              "a" ,"Result",                  NULL, false, NULL, NULL},
180         {T_ModifyTable, "b" ,"ModifyTable",     NULL, false, NULL, NULL},
181         {T_Append,              "c" ,"Append",                  NULL, false, NULL, NULL},
182         {T_MergeAppend, "d" ,"Merge Append",    NULL, false, NULL, NULL},
183         {T_RecursiveUnion,"e" ,"Recursive Union",NULL, false, NULL, NULL},
184         {T_BitmapAnd,   "f" ,"BitmapAnd",               NULL, false, NULL, NULL},
185         {T_BitmapOr,    "g" ,"BitmapOr",                NULL, false, NULL, NULL},
186         {T_Scan,                ""  , "", "", false, NULL, NULL},
187         {T_SeqScan,             "h" ,"Seq Scan",                NULL, false, NULL, NULL},
188         {T_IndexScan,   "i" ,"Index Scan",              NULL, false, NULL, NULL},
189         {T_IndexOnlyScan,"j","Index Only Scan",NULL, false, NULL, NULL},
190         {T_BitmapIndexScan,"k" ,"Bitmap Index Scan", NULL, false, NULL, NULL},
191         {T_BitmapHeapScan,"l" ,"Bitmap Heap Scan", NULL ,false, NULL, NULL},
192         {T_TidScan,             "m" ,"Tid Scan",                NULL, false, NULL, NULL},
193         {T_SubqueryScan,"n" ,"Subquery Scan",   NULL, false, NULL, NULL},
194         {T_FunctionScan,"o" ,"Function Scan",   NULL, false, NULL, NULL},
195         {T_ValuesScan,  "p" ,"Values Scan",     NULL, false, NULL, NULL},
196         {T_CteScan,             "q" ,"CTE Scan",                NULL, false, NULL, NULL},
197         {T_WorkTableScan,"r","WorkTable Scan",  NULL, false, NULL, NULL},
198         {T_ForeignScan, "s" , "Foreign Scan",   NULL, false, NULL, NULL},
199         {T_Join,                ""  ,   "",                             NULL, false, NULL, NULL},
200         {T_NestLoop,    "t" ,"Nested Loop",     NULL, false, NULL, NULL},
201         {T_MergeJoin,   "u" ,"Merge Join",              "Merge", false, NULL, NULL},
202         {T_HashJoin,    "v" ,"Hash Join",               "Hash", false, NULL, NULL},
203         {T_Material,    "w" ,"Materialize",             NULL, false, NULL, NULL},
204         {T_Sort,                "x" ,"Sort",                    NULL, false, NULL, NULL},
205         {T_Group,               "y" ,"Group",                   NULL, false, NULL, NULL},
206         {T_Agg,                 "z" ,"Aggregate",               NULL, false, NULL, NULL},
207         {T_WindowAgg,   "0" ,"WindowAgg",               NULL, false, NULL, NULL},
208         {T_Unique,              "1" ,"Unique",                  NULL, false, NULL, NULL},
209         {T_Hash,                "2" ,"Hash",                    NULL, false, NULL, NULL},
210         {T_SetOp,               "3" ,"SetOp",                   NULL, false, NULL, NULL},
211         {T_LockRows,    "4" ,"LockRows",                NULL, false, NULL, NULL},
212         {T_Limit,               "5" ,"Limit",                   NULL, false, NULL, NULL},
213 #if PG_VERSION_NUM >= 90500
214         {T_SampleScan,  "B" ,"Sample Scan",             NULL, false, NULL, NULL},
215 #endif
216 #if PG_VERSION_NUM >= 90600
217         {T_Gather,              "6" ,"Gather",                  NULL, false, NULL, NULL},
218 #endif
219 #if PG_VERSION_NUM >= 100000
220         {T_ProjectSet,  "7" ,"ProjectSet",              NULL, false, NULL, NULL},
221         {T_TableFuncScan,"8","Table Function Scan",     NULL, false, NULL, NULL},
222         {T_NamedTuplestoreScan,"9","Named Tuplestore Scan",     NULL, false, NULL, NULL},
223         {T_GatherMerge, "A" ,"Gather Merge",    NULL, false, NULL, NULL},
224 #endif
225 #if PG_VERSION_NUM >= 130000
226         {T_IncrementalSort,     "C" ,"Incremental Sort", NULL, false, NULL, NULL},
227 #endif
228 #if PG_VERSION_NUM >= 140000
229         {T_TidRangeScan,"D", "Tid Range Scan",  NULL, false, NULL, NULL},
230         {T_Memoize,             "E", "Memoize",                 NULL, false, NULL, NULL},
231 #endif
232
233         {T_Invalid,             NULL, NULL, NULL, false, NULL, NULL}
234 };
235
236 word_table directions[] =
237 {
238         {T_Invalid,  "b" ,"Backward",   "Backward", false, NULL, NULL},
239         {T_Invalid,  "n" ,"NoMovement","",                      false, NULL, NULL},
240         {T_Invalid,  "f" ,"Forward",    "",                     false, NULL, NULL},
241         {T_Invalid, NULL , NULL,                NULL,           false, NULL, NULL}
242 };
243
244 word_table relationships[] =
245 {
246         {T_Invalid,  "o" ,"Outer", NULL, false, NULL, NULL},
247         {T_Invalid,  "i" ,"Inner", NULL, false, NULL, NULL},
248         {T_Invalid,  "s" ,"Subquery", NULL, false, NULL, NULL},
249         {T_Invalid,  "m" ,"Member", NULL, false, NULL, NULL},
250         {T_Invalid,  "I" ,"InitPlan", NULL, false, NULL, NULL},
251         {T_Invalid,  "S" ,"SubPlan", NULL, false, NULL, NULL},
252         {T_Invalid, NULL, NULL, NULL, false, NULL, NULL}
253 };
254
255 word_table strategies[] =
256 {
257         {S_Plain,       "p" ,"Plain", NULL, false, NULL, NULL},
258         {S_Sorted,      "s" ,"Sorted", NULL, false, NULL, NULL},
259         {S_Hashed,      "h" ,"Hashed", NULL, false, NULL, NULL},
260         {S_Mixed,       "m" ,"Mixed", NULL, false, NULL, NULL},
261         {S_Invalid,     NULL, NULL, NULL, false, NULL, NULL}
262 };
263
264 word_table operations[] =
265 {
266         {T_Invalid,  "i" ,"Insert", NULL, false, NULL, NULL},
267         {T_Invalid,  "d" ,"Delete", NULL, false, NULL, NULL},
268         {T_Invalid,  "u" ,"Update", NULL, false, NULL, NULL},
269         {T_Invalid, NULL, NULL, NULL, false, NULL, NULL}
270 };
271
272 word_table jointypes[] =
273 {
274         {T_Invalid,  "i" ,"Inner", NULL, false, NULL, NULL},
275         {T_Invalid,  "l" ,"Left", NULL, false, NULL, NULL},
276         {T_Invalid,  "f" ,"Full", NULL, false, NULL, NULL},
277         {T_Invalid,  "r" ,"Right", NULL, false, NULL, NULL},
278         {T_Invalid,  "s" ,"Semi", NULL, false, NULL, NULL},
279         {T_Invalid,  "a" ,"Anti", NULL, false, NULL, NULL},
280         {T_Invalid, NULL, NULL, NULL, false, NULL, NULL}
281 };
282
283 word_table setsetopcommands[] =
284 {
285         {T_Invalid,  "i" ,"Intersect", NULL, false, NULL, NULL},
286         {T_Invalid,  "I" ,"Intersect All", NULL, false, NULL, NULL},
287         {T_Invalid,  "e" ,"Except", NULL, false, NULL, NULL},
288         {T_Invalid,  "E" ,"Except All", NULL, false, NULL, NULL},
289         {T_Invalid, NULL, NULL, NULL, false, NULL, NULL}
290 };
291
292 word_table sortmethods[] =
293 {
294         {T_Invalid,  "h" ,"top-N heapsort", NULL, false, NULL, NULL},
295         {T_Invalid,  "q" ,"quicksort", NULL, false, NULL, NULL},
296         {T_Invalid,  "e" ,"external sort", NULL, false, NULL, NULL},
297         {T_Invalid,  "E" ,"external merge", NULL, false, NULL, NULL},
298         {T_Invalid,  "s" ,"still in progress", NULL, false, NULL, NULL},
299         {T_Invalid, NULL, NULL, NULL, false, NULL, NULL}
300 };
301
302 word_table sortspacetype[] =
303 {
304         {T_Invalid,  "d" ,"Disk",       NULL, false, NULL, NULL},
305         {T_Invalid,  "m" ,"Memory",NULL, false, NULL, NULL},
306         {T_Invalid, NULL, NULL, NULL, false, NULL, NULL}
307 };
308
309 word_table partialmode[] =
310 {
311         {T_Invalid,  "p" ,"Partial",    NULL, false, NULL, NULL},
312         {T_Invalid,  "f" ,"Finalize",NULL, false, NULL, NULL},
313         {T_Invalid,  "s" ,"Simple",NULL, false, NULL, NULL},
314         {T_Invalid, NULL, NULL, NULL, false, NULL, NULL}
315 };
316
317
318 word_table *
319 search_word_table(word_table *tbl, const char *word, int mode)
320 {
321         word_table *p;
322
323         bool longname =
324                 (mode == PGSP_JSON_SHORTEN || mode == PGSP_JSON_NORMALIZE);
325
326
327         /*
328          * Use simple linear search. We can gain too small portion of the whole
329          * processing time using more 'clever' algorithms like b-tree or tries,
330          * which won't be worth the additional memory, complexity and
331          * initialization cost.
332          */
333         for (p = tbl ; p->longname ; p++)
334         {
335                 if (strcmp(longname ? p->longname: p->shortname, word) == 0)
336                         break;
337         }
338
339         if (p->longname == NULL && mode == PGSP_JSON_TEXTIZE)
340         {
341                 /* Fallback to long json prop name */
342                 for (p = tbl ; p->longname ; p++)
343                         if (strcmp(p->longname, word) == 0)
344                                 break;
345         }
346
347         return (p->longname ? p : NULL);
348 }
349
350
351 const char *
352 converter_core(word_table *tbl,
353                            const char *src, pgsp_parser_mode mode)
354 {
355         word_table *p;
356         char *ret;
357
358         p = search_word_table(tbl, src, mode);
359
360         if (!p) return src;
361
362         ret = p->shortname;
363         switch(mode)
364         {
365                 case PGSP_JSON_SHORTEN:
366                 case PGSP_JSON_NORMALIZE:
367                         ret = p->shortname;
368                         break;
369                 case PGSP_JSON_INFLATE:
370                 case PGSP_JSON_YAMLIZE:
371                 case PGSP_JSON_XMLIZE:
372                         ret = p->longname;
373                         break;
374                 case PGSP_JSON_TEXTIZE:
375                         if(p->textname)
376                                 ret = p->textname;
377                         else
378                                 ret = p->longname;
379                         break;
380                 default:
381                         elog(ERROR, "Internal error");
382         }
383         return ret;
384 }
385
386 const char *
387 conv_nodetype(const char *src, pgsp_parser_mode mode)
388 {
389         return converter_core(nodetypes, src, mode);
390 }
391
392 const char *
393 conv_scandir(const char *src, pgsp_parser_mode mode)
394 {
395         return converter_core(directions, src, mode);
396 }
397
398 const char *
399 conv_relasionship(const char *src, pgsp_parser_mode mode)
400 {
401         return converter_core(relationships, src, mode);
402 }
403
404 const char *
405 conv_strategy(const char *src, pgsp_parser_mode mode)
406 {
407         return converter_core(strategies, src, mode);
408 }
409
410 /*
411  * Look for these operator characters in order to decide whether to strip
412  * whitespaces which are needless from the view of sql syntax in
413  * normalize_expr(). This must be synced with op_chars in scan.l.
414  */
415 #define OPCHARS "~!@#^&|`?+-*/%<>="
416 #define IS_WSCHAR(c) ((c) == ' ' || (c) == '\n' || (c) == '\t')
417 #define IS_CONST(tok) (tok == FCONST || tok == SCONST || tok == BCONST || \
418                         tok == XCONST || tok == ICONST || tok == NULL_P || \
419                     tok == TRUE_P || tok == FALSE_P || \
420                         tok == CURRENT_DATE || tok == CURRENT_TIME || \
421                     tok == LOCALTIME || tok == LOCALTIMESTAMP)
422 #define IS_INDENTED_ARRAY(v) ((v) == P_GroupKeys || (v) == P_HashKeys)
423
424 /*
425  * norm_yylex: core_yylex with replacing some tokens.
426  */
427 static int
428 norm_yylex(char *str, core_YYSTYPE *yylval, YYLTYPE *yylloc, core_yyscan_t yyscanner)
429 {
430         int tok;
431
432         PG_TRY();
433         {
434                 tok = core_yylex(yylval, yylloc, yyscanner);
435         }
436         PG_CATCH();
437         {
438                 /*
439                  * Error might occur during parsing quoted tokens that chopped
440                  * halfway. Just ignore the rest of this query even if there might
441                  * be other reasons for parsing to fail.
442                  */
443                 FlushErrorState();
444                 return -1;
445         }
446         PG_END_TRY();
447
448         /*
449          * '?' alone is assumed to be an IDENT.  If there's a real
450          * operator '?', this should be confused but there's hardly be.
451          */
452         if (tok == Op && str[*yylloc] == '?' &&
453                 strchr(OPCHARS, str[*yylloc + 1]) == NULL)
454                 tok = SCONST;
455
456         /*
457          * Replace tokens with '=' if the operator is consists of two or
458          * more opchars only. Assuming that opchars do not compose a token
459          * with non-opchars, check the first char only is sufficient.
460          */
461         if (tok == Op && strchr(OPCHARS, str[*yylloc]) != NULL)
462                 tok = '=';
463
464         return tok;
465 }
466
467 /*
468  * normalize_expr - Normalize statements or expressions.
469  *
470  * Mask constants, strip unnecessary whitespaces and upcase keywords. expr is
471  * modified in-place (destructively). If readability is more important than
472  * uniqueness, preserve_space puts one space for one existent whitespace for
473  * more readability.
474  */
475 /* scanner interface is changed in PG12 */
476 #if PG_VERSION_NUM < 120000
477 #define ScanKeywords (*ScanKeywords)
478 #define ScanKeywordTokens NumScanKeywords
479 #endif
480 void
481 normalize_expr(char *expr, bool preserve_space)
482 {
483         core_yyscan_t yyscanner;
484         core_yy_extra_type yyextra;
485         core_YYSTYPE yylval;
486         YYLTYPE         yylloc;
487         YYLTYPE         lastloc;
488         YYLTYPE start;
489         char *wp;
490         int                     tok, lasttok;
491
492         wp = expr;
493         yyscanner = scanner_init(expr,
494                                                          &yyextra,
495                                                          &ScanKeywords,
496                                                          ScanKeywordTokens);
497
498         /*
499          * The warnings about nonstandard escape strings is already emitted in the
500          * core. Just silence them here.
501          */
502 #if PG_VERSION_NUM >= 90500
503         yyextra.escape_string_warning = false;
504 #endif
505         lasttok = 0;
506         lastloc = -1;
507
508         for (;;)
509         {
510                 tok = norm_yylex(expr, &yylval, &yylloc, yyscanner);
511
512                 start = yylloc;
513
514                 if (lastloc >= 0)
515                 {
516                         int i, i2;
517
518                         /* Skipping preceding whitespaces */
519                         for(i = lastloc ; i < start && IS_WSCHAR(expr[i]) ; i++);
520
521                         /* Searching for trailing whitespace */
522                         for(i2 = i; i2 < start && !IS_WSCHAR(expr[i2]) ; i2++);
523
524                         if (lasttok == IDENT)
525                         {
526                                 /* Identifiers are copied in case-sensitive manner. */
527                                 memcpy(wp, expr + i, i2 - i);
528                                 wp += i2 - i;
529                         }
530 #if PG_VERSION_NUM >= 100000
531                         /*
532                          * Since PG10 pg_stat_statements doesn't store trailing semicolon
533                          * in the column "query". Normalization is basically useless in the
534                          * version but still usefull to match utility commands so follow
535                          * the behavior change.
536                          */
537                         else if (lasttok == ';')
538                         {
539                                 /* Just do nothing */
540                         }
541 #endif
542                         else
543                         {
544                                 /* Upcase keywords */
545                                 char *sp;
546                                 for (sp = expr + i ; sp < expr + i2 ; sp++, wp++)
547                                         *wp = (*sp >= 'a' && *sp <= 'z' ?
548                                                    *sp - ('a' - 'A') : *sp);
549                         }
550
551                         /*
552                          * Because of destructive writing, wp must not go advance the
553                          * reading point.
554                          * Although this function's output does not need any validity as a
555                          * statement or an expression, spaces are added where it should be
556                          * to keep some extent of sanity.  If readability is more important
557                          * than uniqueness, preserve_space adds one space for each
558                          * existent whitespace.
559                          */
560                         if (tok > 0 &&
561                                 i2 < start &&
562                                 (preserve_space ||
563                                  (tok >= IDENT && lasttok >= IDENT &&
564                                   !IS_CONST(tok) && !IS_CONST(lasttok))))
565                                 *wp++ = ' ';
566
567                         start = i2;
568                 }
569
570                 /* Exit on parse error. */
571                 if (tok < 0)
572                 {
573                         *wp = 0;
574                         return;
575                 }
576
577                 /*
578                  * Negative signs before numbers are tokenized separately. And
579                  * explicit positive signs won't appear in deparsed expressions.
580                  */
581                 if (tok == '-')
582                         tok = norm_yylex(expr, &yylval, &yylloc, yyscanner);
583
584                 /* Exit on parse error. */
585                 if (tok < 0)
586                 {
587                         *wp = 0;
588                         return;
589                 }
590
591                 if (IS_CONST(tok))
592                 {
593                         YYLTYPE end;
594
595                         tok = norm_yylex(expr, &yylval, &end, yyscanner);
596
597                         /* Exit on parse error. */
598                         if (tok < 0)
599                         {
600                                 *wp = 0;
601                                 return;
602                         }
603
604                         /*
605                          * Negative values may be surrounded with parens by the
606                          * deparser. Mask involving them.
607                          */
608                         if (lasttok == '(' && tok == ')')
609                         {
610                                 wp -= (start - lastloc);
611                                 start = lastloc;
612                                 end++;
613                         }
614
615                         while (expr[end - 1] == ' ')
616                                 end--;
617
618                         *wp++ = '?';
619                         yylloc = end;
620                 }
621
622                 if (tok == 0)
623                         break;
624
625                 lasttok = tok;
626                 lastloc = yylloc;
627         }
628         *wp = 0;
629 }
630
631 const char *
632 conv_expression(const char *src, pgsp_parser_mode mode)
633 {
634         const char *ret = src;
635
636         if (mode == PGSP_JSON_NORMALIZE)
637         {
638                 char *t = pstrdup(src);
639                 normalize_expr(t, true);
640                 ret = (const char *)t;
641         }
642         return ret;
643 }
644
645 const char *
646 conv_operation(const char *src, pgsp_parser_mode mode)
647 {
648         return converter_core(operations, src, mode);
649
650 }
651
652 const char *
653 conv_jointype(const char *src, pgsp_parser_mode mode)
654 {
655         return converter_core(jointypes, src, mode);
656 }
657
658 const char *
659 conv_setsetopcommand(const char *src, pgsp_parser_mode mode)
660 {
661         return converter_core(setsetopcommands, src, mode);
662 }
663
664 const char *
665 conv_sortmethod(const char *src, pgsp_parser_mode mode)
666 {
667         return converter_core(sortmethods, src, mode);
668 }
669
670 const char *
671 conv_sortspacetype(const char *src, pgsp_parser_mode mode)
672 {
673         return converter_core(sortspacetype, src, mode);
674 }
675
676 const char *
677 conv_partialmode(const char *src, pgsp_parser_mode mode)
678 {
679         return converter_core(partialmode, src, mode);
680 }
681
682 /**** Parser callbacks ****/
683
684 /* JSON */
685 static void
686 json_objstart(void *state)
687 {
688         pgspParserContext *ctx = (pgspParserContext *)state;
689
690         if (ctx->mode == PGSP_JSON_INFLATE)
691         {
692                 if (!ctx->fname && ctx->dest->len > 0)
693                 {
694                         appendStringInfoChar(ctx->dest, '\n');
695                         appendStringInfoSpaces(ctx->dest, (ctx->level) * INDENT_STEP);
696                 }
697                 ctx->fname = NULL;
698         }
699         appendStringInfoChar(ctx->dest, '{');
700
701         ctx->level++;
702         ctx->first = bms_add_member(ctx->first, ctx->level);
703
704         if (ctx->mode == PGSP_JSON_INFLATE)
705                 appendStringInfoChar(ctx->dest, '\n');
706 }
707
708 static void
709 json_objend(void *state)
710 {
711         pgspParserContext *ctx = (pgspParserContext *)state;
712         if (ctx->mode == PGSP_JSON_INFLATE)
713         {
714                 if (!bms_is_member(ctx->level, ctx->first))
715                         appendStringInfoChar(ctx->dest, '\n');
716                 appendStringInfoSpaces(ctx->dest, (ctx->level - 1) * INDENT_STEP);
717         }
718
719         appendStringInfoChar(ctx->dest, '}');
720
721         ctx->level--;
722         ctx->last_elem_is_object = true;
723         ctx->first = bms_del_member(ctx->first, ctx->level);
724         ctx->fname = NULL;
725 }
726
727 static void
728 json_arrstart(void *state)
729 {
730         pgspParserContext *ctx = (pgspParserContext *)state;
731
732         if (IS_INDENTED_ARRAY(ctx->current_list))
733                 ctx->wlist_level++;
734
735         appendStringInfoChar(ctx->dest, '[');
736         ctx->fname = NULL;
737         ctx->level++;
738         ctx->last_elem_is_object = true;
739         ctx->first = bms_add_member(ctx->first, ctx->level);
740 }
741
742 static void
743 json_arrend(void *state)
744 {
745         pgspParserContext *ctx = (pgspParserContext *)state;
746
747         if (IS_INDENTED_ARRAY(ctx->current_list))
748                 ctx->wlist_level--;
749
750         if (ctx->mode == PGSP_JSON_INFLATE &&
751                 (IS_INDENTED_ARRAY(ctx->current_list) ?
752                  ctx->wlist_level == 0 : ctx->last_elem_is_object))
753         {
754                 appendStringInfoChar(ctx->dest, '\n');
755                 appendStringInfoSpaces(ctx->dest, (ctx->level - 1) * INDENT_STEP);
756         }
757
758         appendStringInfoChar(ctx->dest, ']');
759         ctx->level--;
760 }
761
762 static void
763 json_ofstart(void *state, char *fname, bool isnull)
764 {
765         word_table *p;
766         pgspParserContext *ctx = (pgspParserContext *)state;
767         char *fn;
768
769         ctx->remove = false;
770         p = search_word_table(propfields, fname, ctx->mode);
771         if (!p)
772         {
773                 ereport(DEBUG1,
774                                 (errmsg("JSON parser encoutered unknown field name: \"%s\".", fname),
775                                  errdetail_log("INPUT: \"%s\"", ctx->org_string)));
776         }
777
778         ctx->remove = (ctx->mode == PGSP_JSON_NORMALIZE &&
779                                    (!p || !p->normalize_use));
780
781         if (ctx->remove)
782                 return;
783
784         if (!bms_is_member(ctx->level, ctx->first))
785         {
786                 appendStringInfoChar(ctx->dest, ',');
787                 if (ctx->mode == PGSP_JSON_INFLATE)
788                         appendStringInfoChar(ctx->dest, '\n');
789         }
790         else
791                 ctx->first = bms_del_member(ctx->first, ctx->level);
792
793         if (ctx->mode == PGSP_JSON_INFLATE)
794                 appendStringInfoSpaces(ctx->dest, ctx->level * INDENT_STEP);
795
796         /*
797          * We intentionally let some property names not have a short name. Use long
798          * name for the cases.
799          */
800         if (!p || !p->longname)
801                 fn = fname;
802         else if (ctx->mode == PGSP_JSON_INFLATE ||
803                          !(p->shortname && p->shortname[0]))
804                 fn = p->longname;
805         else
806                 fn = p->shortname;
807
808         escape_json(ctx->dest, fn);
809         ctx->fname = fn;
810         ctx->valconverter = (p ? p->converter : NULL);
811
812         appendStringInfoChar(ctx->dest, ':');
813
814         if (ctx->mode == PGSP_JSON_INFLATE)
815                 appendStringInfoChar(ctx->dest, ' ');
816
817         if (p && IS_INDENTED_ARRAY(p->tag))
818         {
819                 ctx->current_list = p->tag;
820                 ctx->list_fname = fname;
821                 ctx->wlist_level = 0;
822         }
823 }
824
825 static void
826 json_ofend(void *state, char *fname, bool isnull)
827 {
828         pgspParserContext *ctx = (pgspParserContext *)state;
829
830         if (ctx->list_fname && strcmp(fname, ctx->list_fname) == 0)
831         {
832                 ctx->list_fname = NULL;
833                 ctx->current_list = P_Invalid;
834         }
835 }
836
837 static void
838 json_aestart(void *state, bool isnull)
839 {
840         pgspParserContext *ctx = (pgspParserContext *)state;
841         if (ctx->remove)
842                 return;
843
844         if (IS_INDENTED_ARRAY(ctx->current_list) &&
845                 ctx->wlist_level == 1)
846         {
847                 if (!bms_is_member(ctx->level, ctx->first))
848                         appendStringInfoChar(ctx->dest, ',');
849
850                 if (ctx->mode == PGSP_JSON_INFLATE)
851                 {
852                         appendStringInfoChar(ctx->dest, '\n');
853                         appendStringInfoSpaces(ctx->dest, (ctx->level) * INDENT_STEP);
854                 }
855         }
856         else
857         {
858                 if (!bms_is_member(ctx->level, ctx->first))
859                 {
860                         appendStringInfoChar(ctx->dest, ',');
861
862                         if (ctx->mode == PGSP_JSON_INFLATE &&
863                                 !ctx->last_elem_is_object)
864                                 appendStringInfoChar(ctx->dest, ' ');
865                 }
866         }
867
868         ctx->first = bms_del_member(ctx->first, ctx->level);
869 }
870
871 static void
872 json_scalar(void *state, char *token, JsonTokenType tokentype)
873 {
874         pgspParserContext *ctx = (pgspParserContext *)state;
875         const char *val = token;
876
877         if (ctx->remove)
878                 return;
879
880         if (ctx->valconverter)
881                 val = ctx->valconverter(token, ctx->mode);
882
883         if (tokentype == JSON_TOKEN_STRING)
884                 escape_json(ctx->dest, val);
885         else
886                 appendStringInfoString(ctx->dest, val);
887         ctx->last_elem_is_object = false;
888 }
889
890
891 /* YAML */
892 static void
893 yaml_objstart(void *state)
894 {
895         pgspParserContext *ctx = (pgspParserContext *)state;
896
897         if (ctx->fname)
898         {
899                 if (ctx->dest->len > 0)
900                         appendStringInfoChar(ctx->dest, '\n');
901                 appendStringInfoSpaces(ctx->dest, (ctx->level - 1) * INDENT_STEP);
902                 appendStringInfoString(ctx->dest, "- ");
903                 appendStringInfoString(ctx->dest, ctx->fname);
904                 appendStringInfoString(ctx->dest, ":\n");
905                 appendStringInfoSpaces(ctx->dest, (ctx->level + 1) * INDENT_STEP);
906                 ctx->fname = NULL;
907         }
908
909         ctx->level++;
910         ctx->first = bms_add_member(ctx->first, ctx->level);
911 }
912
913 static void
914 yaml_objend(void *state)
915 {
916         pgspParserContext *ctx = (pgspParserContext *)state;
917
918         ctx->level--;
919         ctx->last_elem_is_object = true;
920         ctx->first = bms_del_member(ctx->first, ctx->level);
921 }
922
923 static void
924 yaml_arrstart(void *state)
925 {
926         pgspParserContext *ctx = (pgspParserContext *)state;
927
928         if (ctx->fname)
929         {
930                 appendStringInfoString(ctx->dest, ctx->fname);
931                 appendStringInfoString(ctx->dest, ":");
932         }
933
934         ctx->fname = NULL;
935         ctx->level++;
936         ctx->first = bms_add_member(ctx->first, ctx->level);
937 }
938
939 static void
940 yaml_arrend(void *state)
941 {
942         pgspParserContext *ctx = (pgspParserContext *)state;
943         ctx->level--;
944 }
945 static void
946 yaml_ofstart(void *state, char *fname, bool isnull)
947 {
948         word_table *p;
949         pgspParserContext *ctx = (pgspParserContext *)state;
950         char *s;
951
952         p = search_word_table(propfields, fname, ctx->mode);
953         if (!p)
954         {
955                 ereport(DEBUG1,
956                                 (errmsg("Short JSON parser encoutered unknown field name: \"%s\".", fname),
957                                  errdetail_log("INPUT: \"%s\"", ctx->org_string)));
958         }
959         s = (p ? p->longname : fname);
960
961         if (!bms_is_member(ctx->level, ctx->first))
962         {
963                 appendStringInfoString(ctx->dest, "\n");
964                 appendStringInfoSpaces(ctx->dest, ctx->level * INDENT_STEP);
965         }
966         else
967                 ctx->first = bms_del_member(ctx->first, ctx->level);
968
969         ctx->valconverter = NULL;
970         ctx->fname = s;
971         ctx->valconverter = (p ? p->converter : NULL);
972 }
973
974 static void
975 yaml_aestart(void *state, bool isnull)
976 {
977         pgspParserContext *ctx = (pgspParserContext *)state;
978
979         appendStringInfoString(ctx->dest, "\n");
980         bms_del_member(ctx->first, ctx->level);
981         appendStringInfoSpaces(ctx->dest, ctx->level * INDENT_STEP);
982         appendStringInfoString(ctx->dest, "- ");
983 }
984
985 static void
986 yaml_scalar(void *state, char *token, JsonTokenType tokentype)
987 {
988         pgspParserContext *ctx = (pgspParserContext *)state;
989
990         if (ctx->fname)
991         {
992                 appendStringInfoString(ctx->dest, ctx->fname);
993                 appendStringInfoString(ctx->dest, ": ");
994                 ctx->fname = NULL;
995         }
996
997         json_scalar(state, token, tokentype);
998
999         ctx->last_elem_is_object = false;
1000 }
1001
1002
1003 /* XML */
1004 static void
1005 xml_objstart(void *state)
1006 {
1007         pgspParserContext *ctx = (pgspParserContext *)state;
1008
1009         ctx->level ++;
1010         ctx->first = bms_add_member(ctx->first, ctx->level);
1011 }
1012
1013
1014 static void
1015 xml_objend(void *state)
1016 {
1017         pgspParserContext *ctx = (pgspParserContext *)state;
1018         appendStringInfoChar(ctx->dest, '\n');
1019         appendStringInfoSpaces(ctx->dest, ctx->level * INDENT_STEP);
1020
1021         ctx->level--;
1022         ctx->first = bms_del_member(ctx->first, ctx->level);
1023
1024         ctx->last_elem_is_object = true;
1025 }
1026
1027 static void
1028 xml_arrend(void *state)
1029 {
1030         pgspParserContext *ctx = (pgspParserContext *)state;
1031
1032         appendStringInfoChar(ctx->dest, '\n');
1033         appendStringInfoSpaces(ctx->dest, (ctx->level + 1) * INDENT_STEP);
1034 }
1035
1036 static void
1037 adjust_wbuf(pgspParserContext *ctx, int len)
1038 {
1039         int buflen;
1040
1041         for (buflen = ctx->wbuflen ; len > buflen ; buflen *= 2);
1042         if (buflen > ctx->wbuflen)
1043         {
1044                 ctx->wbuf = (char *)palloc(buflen);
1045                 ctx->wbuflen = buflen;
1046         }
1047 }
1048
1049 static char *
1050 hyphenate_words(pgspParserContext *ctx, char *src)
1051 {
1052         char *p;
1053
1054         adjust_wbuf(ctx, strlen(src) + 1);
1055         strcpy(ctx->wbuf, src);
1056
1057         for (p = ctx->wbuf ; *p ; p++)
1058                 if (*p == ' ') *p = '-';
1059
1060         return ctx->wbuf;
1061 }
1062
1063 static void
1064 xml_ofstart(void *state, char *fname, bool isnull)
1065 {
1066         word_table *p;
1067         pgspParserContext *ctx = (pgspParserContext *)state;
1068         char *s;
1069
1070         p = search_word_table(propfields, fname, ctx->mode);
1071         if (!p)
1072         {
1073                 ereport(DEBUG1,
1074                                 (errmsg("Short JSON parser encoutered unknown field name: \"%s\".", fname),
1075                                  errdetail_log("INPUT: \"%s\"", ctx->org_string)));
1076         }
1077         s = (p ? p->longname : fname);
1078
1079         /*
1080          * save current process context
1081          * There's no problem if P_Plan appears recursively.
1082          */
1083         if (p && (p->tag == P_Plan || p->tag == P_Triggers))
1084                 ctx->section = p->tag;
1085
1086         appendStringInfoChar(ctx->dest, '\n');
1087         appendStringInfoSpaces(ctx->dest, (ctx->level + 1) * INDENT_STEP);
1088
1089         ctx->valconverter = NULL;
1090
1091         appendStringInfoChar(ctx->dest, '<');
1092         appendStringInfoString(ctx->dest, escape_xml(hyphenate_words(ctx, s)));
1093         appendStringInfoChar(ctx->dest, '>');
1094         ctx->valconverter = (p ? p->converter : NULL);
1095
1096         /*
1097          * If the object field name is Plan or Triggers, the value should be an
1098          * array and the items are tagged by other than "Item". "Item"s appear
1099          * only in Output field.
1100          */
1101         if (p && (p->tag == P_Plans || p->tag == P_Triggers))
1102                 ctx->not_item = bms_add_member(ctx->not_item, ctx->level + 1);
1103         else
1104                 ctx->not_item = bms_del_member(ctx->not_item, ctx->level + 1);
1105 }
1106
1107 static void
1108 xml_ofend(void *state, char *fname, bool isnull)
1109 {
1110         pgspParserContext *ctx = (pgspParserContext *)state;
1111         word_table *p;
1112         char *s;
1113
1114         p =     search_word_table(propfields, fname, ctx->mode);
1115         s = (p ? p->longname : fname);
1116
1117         appendStringInfoString(ctx->dest, "</");
1118         appendStringInfoString(ctx->dest, escape_xml(hyphenate_words(ctx, s)));
1119         appendStringInfoChar(ctx->dest, '>');
1120 }
1121
1122 static void
1123 xml_aestart(void *state, bool isnull)
1124 {
1125         pgspParserContext *ctx = (pgspParserContext *)state;
1126         char *tag;
1127
1128         /*
1129          * The "Trigger" in "Triggers", "Plan" in "Plans" and "Item" nodes are
1130          * implicitly represented in JSON format.  Restore them for XML format.
1131          */
1132
1133         ctx->level++;
1134         if (bms_is_member(ctx->level, ctx->not_item))
1135         {
1136                 if (ctx->section == P_Plan)
1137                         tag = "<Plan>";
1138                 else
1139                         tag = "<Trigger>";
1140         }
1141         else
1142                 tag = "<Item>";
1143
1144         appendStringInfoChar(ctx->dest, '\n');
1145         appendStringInfoSpaces(ctx->dest, (ctx->level + 1) * INDENT_STEP);
1146         appendStringInfoString(ctx->dest, tag);
1147 }
1148
1149 static void
1150 xml_aeend(void *state, bool isnull)
1151 {
1152         pgspParserContext *ctx = (pgspParserContext *)state;
1153         char *tag;
1154
1155         /*
1156          * The "Plan" in "Plans" or "Item" nodes are implicitly represented in
1157          * JSON format.  Restore it for XML format.
1158          */
1159
1160         if (bms_is_member(ctx->level, ctx->not_item))
1161         {
1162                 if (ctx->section == P_Plan)
1163                         tag = "</Plan>";
1164                 else
1165                         tag = "</Trigger>";
1166         }
1167         else
1168                 tag = "</Item>";
1169         appendStringInfoString(ctx->dest, tag);
1170         ctx->level--;
1171 }
1172
1173 static void
1174 xml_scalar(void *state, char *token, JsonTokenType tokentype)
1175 {
1176         pgspParserContext *ctx = (pgspParserContext *)state;
1177         const char *s = token;
1178
1179         if (ctx->valconverter)
1180                 s = ctx->valconverter(token, PGSP_JSON_XMLIZE);
1181
1182         if (tokentype == JSON_TOKEN_STRING)
1183                 s = escape_xml(s);
1184
1185         appendStringInfoString(ctx->dest, s);
1186         ctx->last_elem_is_object = false;
1187 }
1188
1189 /********************************/
1190 void
1191 init_parser_context(pgspParserContext *ctx, int mode,
1192                                            char *orgstr, char *buf, int buflen){
1193         memset(ctx, 0, sizeof(*ctx));
1194         ctx->dest = makeStringInfo();
1195         ctx->mode = mode;
1196         ctx->org_string = orgstr;
1197         ctx->wbuf = buf;
1198         ctx->wbuflen = buflen;
1199 }
1200
1201 /*
1202  * run_pg_parse_json:
1203  *
1204  * Wrap pg_parse_json in order to restore InterruptHoldoffCount when parse
1205  * error occured.
1206  *
1207  * Returns true when parse completed. False for unexpected end of string.
1208  */
1209 bool
1210 run_pg_parse_json(JsonLexContext *lex, JsonSemAction *sem)
1211 {
1212 #if PG_VERSION_NUM >= 130000
1213         return pg_parse_json(lex, sem) == JSON_SUCCESS;
1214 #else
1215         MemoryContext ccxt = CurrentMemoryContext;
1216         uint32 saved_IntrHoldoffCount;
1217
1218         /*
1219          * "ereport(ERROR.." occurs on error in pg_parse_json resets
1220          * InterruptHoldoffCount to zero, so we must save the value before calling
1221          * json parser to restore it on parse error. See errfinish().
1222          */
1223         saved_IntrHoldoffCount = InterruptHoldoffCount;
1224
1225         PG_TRY();
1226         {
1227                 pg_parse_json(lex, sem);
1228         }
1229         PG_CATCH();
1230         {
1231                 ErrorData *errdata;
1232                 MemoryContext ecxt;
1233
1234                 InterruptHoldoffCount = saved_IntrHoldoffCount;
1235
1236                 ecxt = MemoryContextSwitchTo(ccxt);
1237                 errdata = CopyErrorData();
1238
1239                 if (errdata->sqlerrcode == ERRCODE_INVALID_TEXT_REPRESENTATION)
1240                 {
1241                         FlushErrorState();
1242                         return false;
1243                 }
1244                 else
1245                 {
1246                         MemoryContextSwitchTo(ecxt);
1247                         PG_RE_THROW();
1248                 }
1249         }
1250         PG_END_TRY();
1251
1252         return true;
1253 #endif
1254 }
1255
1256 void
1257 init_json_lex_context(JsonLexContext *lex, char *json)
1258 {
1259         lex->input = lex->token_terminator = lex->line_start = json;
1260         lex->line_number = 1;
1261         lex->input_length = strlen(json);
1262         lex->strval = makeStringInfo();
1263 }
1264
1265 static void
1266 init_json_semaction(JsonSemAction *sem, pgspParserContext *ctx)
1267 {
1268         sem->semstate = (void*)ctx;
1269         sem->object_start       = json_objstart;
1270         sem->object_end         = json_objend;
1271         sem->array_start        = json_arrstart;
1272         sem->array_end          = json_arrend;
1273         sem->object_field_start = json_ofstart;
1274         sem->object_field_end   = json_ofend;
1275         sem->array_element_start= json_aestart;
1276         sem->array_element_end  = NULL;
1277         sem->scalar             = json_scalar;
1278 }
1279
1280 char *
1281 pgsp_json_shorten(char *json)
1282 {
1283         JsonLexContext lex;
1284         JsonSemAction sem;
1285         pgspParserContext    ctx;
1286
1287         init_json_lex_context(&lex, json);
1288         init_parser_context(&ctx, PGSP_JSON_SHORTEN, json, NULL, 0);
1289         init_json_semaction(&sem, &ctx);
1290
1291         run_pg_parse_json(&lex, &sem);
1292
1293         return ctx.dest->data;
1294 }
1295
1296 char *
1297 pgsp_json_normalize(char *json)
1298 {
1299         JsonLexContext lex;
1300         JsonSemAction sem;
1301         pgspParserContext    ctx;
1302
1303         init_json_lex_context(&lex, json);
1304         init_parser_context(&ctx,PGSP_JSON_NORMALIZE, json, NULL, 0);
1305         init_json_semaction(&sem, &ctx);
1306
1307         run_pg_parse_json(&lex, &sem);
1308
1309         return ctx.dest->data;
1310 }
1311
1312 char *
1313 pgsp_json_inflate(char *json)
1314 {
1315         JsonLexContext lex;
1316         JsonSemAction sem;
1317         pgspParserContext    ctx;
1318
1319         init_json_lex_context(&lex, json);
1320         init_parser_context(&ctx, PGSP_JSON_INFLATE, json, NULL, 0);
1321         init_json_semaction(&sem, &ctx);
1322
1323         if (!run_pg_parse_json(&lex, &sem))
1324         {
1325                 if (ctx.dest->len > 0 &&
1326                         ctx.dest->data[ctx.dest->len - 1] != '\n')
1327                         appendStringInfoChar(ctx.dest, '\n');
1328
1329                 if (ctx.dest->len == 0)
1330                         appendStringInfoString(ctx.dest, "<Input was not JSON>");
1331                 else
1332                         appendStringInfoString(ctx.dest, "<truncated>");
1333         }
1334
1335         return ctx.dest->data;
1336 }
1337
1338 char *
1339 pgsp_json_yamlize(char *json)
1340 {
1341         pgspParserContext    ctx;
1342         JsonSemAction sem;
1343         JsonLexContext lex;
1344
1345         init_json_lex_context(&lex, json);
1346         init_parser_context(&ctx, PGSP_JSON_YAMLIZE, json, NULL, 0);
1347
1348         sem.semstate = (void*)&ctx;
1349         sem.object_start       = yaml_objstart;
1350         sem.object_end         = yaml_objend;
1351         sem.array_start        = yaml_arrstart;
1352         sem.array_end          = yaml_arrend;
1353         sem.object_field_start = yaml_ofstart;
1354         sem.object_field_end   = NULL;
1355         sem.array_element_start= yaml_aestart;
1356         sem.array_element_end  = NULL;
1357         sem.scalar             = yaml_scalar;
1358
1359         if (!run_pg_parse_json(&lex, &sem))
1360         {
1361                 if (ctx.dest->len > 0 &&
1362                         ctx.dest->data[ctx.dest->len - 1] != '\n')
1363                         appendStringInfoChar(ctx.dest, '\n');
1364
1365                 if (ctx.dest->len == 0)
1366                         appendStringInfoString(ctx.dest, "<Input was not JSON>");
1367                 else
1368                         appendStringInfoString(ctx.dest, "<truncated>");
1369         }
1370
1371         return ctx.dest->data;
1372 }
1373
1374 char *
1375 pgsp_json_xmlize(char *json)
1376 {
1377         pgspParserContext      ctx;
1378         JsonSemAction sem;
1379         JsonLexContext lex;
1380         int start_len;
1381         char buf[32];
1382
1383         init_json_lex_context(&lex, json);
1384         init_parser_context(&ctx, PGSP_JSON_XMLIZE, json, buf, sizeof(buf));
1385
1386         sem.semstate = (void*)&ctx;
1387         sem.object_start       = xml_objstart;
1388         sem.object_end         = xml_objend;
1389         sem.array_start        = NULL;
1390         sem.array_end          = xml_arrend;
1391         sem.object_field_start = xml_ofstart;
1392         sem.object_field_end   = xml_ofend;
1393         sem.array_element_start= xml_aestart;
1394         sem.array_element_end  = xml_aeend;
1395         sem.scalar             = xml_scalar;
1396
1397         appendStringInfo(ctx.dest,
1398                                          "<explain xmlns=\"http://www.postgresql.org/2009/explain\">\n  <Query>");
1399         start_len = ctx.dest->len;
1400
1401         if (!run_pg_parse_json(&lex, &sem))
1402         {
1403                 if (ctx.dest->len > start_len &&
1404                         ctx.dest->data[ctx.dest->len - 1] != '\n')
1405                         appendStringInfoChar(ctx.dest, '\n');
1406
1407                 if (ctx.dest->len == start_len)
1408                 {
1409                         resetStringInfo(ctx.dest);
1410                         appendStringInfoString(ctx.dest, "<Input was not JSON>");
1411                 }
1412                 else
1413                         appendStringInfoString(ctx.dest, "<truncated>");
1414         }
1415         else
1416                 appendStringInfo(ctx.dest, "</Query>\n</explain>\n");
1417
1418         return ctx.dest->data;
1419 }