OSDN Git Service

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