OSDN Git Service

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