OSDN Git Service

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