OSDN Git Service

Added test for unknown JSON field names.
[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         tok = core_yylex(yylval, yylloc, yyscanner);
347
348         /*
349          * '?' alone is assumed to be an IDENT.  If there's a real
350          * operator '?', this should be confused but there's hardly be.
351          */
352         if (tok == Op && str[*yylloc] == '?' &&
353                 strchr(OPCHARS, str[*yylloc + 1]) == NULL)
354                 tok = SCONST;
355
356         /*
357          * Replace tokens with '=' if the operator is consists of two or
358          * more opchars only. Assuming that opchars do not compose a token
359          * with non-opchars, check the first char only is sufficient.
360          */
361         if (tok == Op && strchr(OPCHARS, str[*yylloc]) != NULL)
362                 tok = '=';
363
364         return tok;
365 }
366
367 /*
368  * normalize_expr - Normalize statements or expressions.
369  *
370  * Mask constants, strip unnecessary whitespaces and upcase keywords. expr is
371  * modified in-place (destructively). If readablity is more important than
372  * uniqueness, preserve_space puts one space for one existent whitespace for
373  * more readabilty.
374  */
375 void
376 normalize_expr(char *expr, bool preserve_space)
377 {
378         core_yyscan_t yyscanner;
379         core_yy_extra_type yyextra;
380         core_YYSTYPE yylval;
381         YYLTYPE         yylloc;
382         YYLTYPE         lastloc;
383         YYLTYPE start;
384         char *wp;
385         int                     tok, lasttok;
386
387         wp = expr;
388         yyscanner = scanner_init(expr,
389                                                          &yyextra,
390                                                          ScanKeywords,
391                                                          NumScanKeywords);
392
393         lasttok = 0;
394         lastloc = -1;
395
396         for (;;)
397         {
398                 tok = norm_yylex(expr, &yylval, &yylloc, yyscanner);
399                 start = yylloc;
400
401                 if (lastloc >= 0)
402                 {
403                         int i, i2;
404                         
405                         /* Skipping preceding whitespaces */
406                         for(i = lastloc ; i < start && IS_WSCHAR(expr[i]) ; i++);
407
408                         /* Searching for trailing whitespace */
409                         for(i2 = i; i2 < start && !IS_WSCHAR(expr[i2]) ; i2++);
410
411                         if (lasttok == IDENT)
412                         {
413                                 /* Identifiers are copied in case-sensitive manner. */
414                                 memcpy(wp, expr + i, i2 - i);
415                                 wp += i2 - i;
416                         }
417                         else
418                         {
419                                 /* Upcase keywords */
420                                 char *sp;
421                                 for (sp = expr + i ; sp < expr + i2 ; sp++, wp++)
422                                         *wp = (*sp >= 'a' && *sp <= 'z' ?
423                                                    *sp - ('a' - 'A') : *sp);
424                         }
425
426                         /*
427                          * Because of destructive writing, wp must not go advance the
428                          * reading point.
429                          * Altough this function's output does not need any validity as a
430                          * statement or an expression, spaces are added where it should be
431                          * to keep some extent of sanity.  If readablity is more important
432                          * than uniqueness, preserve_space adds one space for each
433                          * existent whitespace to keep readabilty.
434                          */
435                         if (i2 < start &&
436                                 (preserve_space || 
437                                  (tok >= IDENT && lasttok >= IDENT &&
438                                   !IS_CONST(tok) && !IS_CONST(lasttok))))
439                                 *wp++ = ' ';
440
441                         start = i2;
442                         lasttok = tok;
443                 }
444                 /*
445                  * Negative signs before numbers are tokenized separately. And
446                  * explicit positive signs won't appear in deparsed expressions.
447                  */
448                 if (tok == '-')
449                         tok = norm_yylex(expr, &yylval, &yylloc, yyscanner);
450                 
451                 if (IS_CONST(tok))
452                 {
453                         YYLTYPE end;
454                         
455                         tok = norm_yylex(expr, &yylval, &end, yyscanner);
456
457                         /*
458                          * Negative values may be surrounded with parens by the
459                          * deparser. Mask involving them.
460                          */
461                         if (lasttok == '(' && tok == ')')
462                         {
463                                 start = lastloc;
464                                 end++;
465                         }
466
467                         while (expr[end - 1] == ' ') end--;                     
468
469                         *wp++ = '?';
470                         yylloc = end;
471                 }
472
473                 if (tok == 0)
474                         break;
475
476                 lasttok = tok;
477                 lastloc = yylloc;
478         }
479         *wp = 0;
480 }
481
482 const char *
483 conv_expression(const char *src, pgsp_parser_mode mode)
484 {
485         const char *ret = src;
486
487         if (mode == PGSP_JSON_NORMALIZE)
488         {
489                 char *t = pstrdup(src);
490                 normalize_expr(t, true);
491                 ret = (const char *)t;
492         }
493         return ret;
494 }
495
496 const char *
497 conv_operation(const char *src, pgsp_parser_mode mode)
498 {
499         return coverter_core(operations, src, mode);
500
501 }
502
503 const char *
504 conv_jointype(const char *src, pgsp_parser_mode mode)
505 {
506         return coverter_core(jointypes, src, mode);
507 }
508
509 const char *
510 conv_setsetopcommand(const char *src, pgsp_parser_mode mode)
511 {
512         return coverter_core(setsetopcommands, src, mode);
513 }
514
515 const char *
516 conv_sortmethod(const char *src, pgsp_parser_mode mode)
517 {
518         return coverter_core(sortmethods, src, mode);
519 }
520
521 const char *
522 conv_sortspacetype(const char *src, pgsp_parser_mode mode)
523 {
524         return coverter_core(sortspacetype, src, mode);
525 }
526
527 /**** Parser callbacks ****/
528
529 /* JSON */
530 static void
531 json_objstart(void *state)
532 {
533         pgspParserContext *ctx = (pgspParserContext *)state;
534
535         if (ctx->mode == PGSP_JSON_INFLATE)
536         {
537                 if (!ctx->fname && ctx->dest->len > 0)
538                 {
539                         appendStringInfoChar(ctx->dest, '\n');
540                         appendStringInfoSpaces(ctx->dest, (ctx->level) * INDENT_STEP);
541                 }
542                 ctx->fname = NULL;
543         }
544         appendStringInfoChar(ctx->dest, '{');
545
546         ctx->level++;
547         ctx->first = bms_add_member(ctx->first, ctx->level);
548
549         if (ctx->mode == PGSP_JSON_INFLATE)
550                 appendStringInfoChar(ctx->dest, '\n');
551 }
552
553 static void
554 json_objend(void *state)
555 {
556         pgspParserContext *ctx = (pgspParserContext *)state;
557         if (ctx->mode == PGSP_JSON_INFLATE)
558         {
559                 if (!bms_is_member(ctx->level, ctx->first))
560                         appendStringInfoChar(ctx->dest, '\n');
561                 appendStringInfoSpaces(ctx->dest, (ctx->level - 1) * INDENT_STEP);
562         }
563
564         appendStringInfoChar(ctx->dest, '}');
565
566         ctx->level--;
567         ctx->last_elem_is_object = true;
568         ctx->first = bms_del_member(ctx->first, ctx->level);
569         ctx->fname = NULL;
570 }
571
572 static void
573 json_arrstart(void *state)
574 {
575         pgspParserContext *ctx = (pgspParserContext *)state;
576
577         appendStringInfoChar(ctx->dest, '[');
578         ctx->fname = NULL;
579         ctx->level++;
580         ctx->last_elem_is_object = true;
581         ctx->first = bms_add_member(ctx->first, ctx->level);
582 }
583
584 static void
585 json_arrend(void *state)
586 {
587         pgspParserContext *ctx = (pgspParserContext *)state;
588         if (ctx->mode == PGSP_JSON_INFLATE &&
589                 ctx->last_elem_is_object)
590         {
591                 appendStringInfoChar(ctx->dest, '\n');
592                 appendStringInfoSpaces(ctx->dest, (ctx->level - 1) * INDENT_STEP);
593         }
594
595         appendStringInfoChar(ctx->dest, ']');
596         ctx->level--;
597 }
598
599 static void
600 json_ofstart(void *state, char *fname, bool isnull)
601 {
602         word_table *p;
603         pgspParserContext *ctx = (pgspParserContext *)state;
604         char *fn;
605
606         ctx->remove = false;
607         p = search_word_table(propfields, fname, ctx->mode);
608         if (!p)
609         {
610                 ereport(DEBUG1,
611                                 (errmsg("JSON parser encoutered unknown field name: \"%s\".", fname),
612                                  errdetail_log("INPUT: \"%s\"", ctx->org_string)));
613         }               
614
615         ctx->remove = (ctx->mode == PGSP_JSON_NORMALIZE &&
616                                    (!p || !p->normalize_use));
617
618         if (ctx->remove)
619                 return;
620
621         if (!bms_is_member(ctx->level, ctx->first))
622         {
623                 appendStringInfoChar(ctx->dest, ',');
624                 if (ctx->mode == PGSP_JSON_INFLATE)
625                         appendStringInfoChar(ctx->dest, '\n');
626         }
627         else
628                 ctx->first = bms_del_member(ctx->first, ctx->level);
629
630         if (ctx->mode == PGSP_JSON_INFLATE)
631                 appendStringInfoSpaces(ctx->dest, ctx->level * INDENT_STEP);
632
633         if (!p || !p->longname)
634                 fn = fname;
635         else if (ctx->mode == PGSP_JSON_INFLATE)
636                 fn = p->longname;
637         else
638                 fn = p->shortname;
639
640         escape_json(ctx->dest, fn);
641         ctx->fname = fn;
642         ctx->valconverter = (p ? p->converter : NULL);
643
644         appendStringInfoChar(ctx->dest, ':');
645
646         if (ctx->mode == PGSP_JSON_INFLATE)
647                 appendStringInfoChar(ctx->dest, ' ');
648 }
649
650 static void
651 json_aestart(void *state, bool isnull)
652 {
653         pgspParserContext *ctx = (pgspParserContext *)state;
654         if (ctx->remove)
655                 return;
656
657         if (!bms_is_member(ctx->level, ctx->first))
658         {
659                 appendStringInfoChar(ctx->dest, ',');
660                 if (ctx->mode == PGSP_JSON_INFLATE &&
661                         !ctx->last_elem_is_object)
662                         appendStringInfoChar(ctx->dest, ' ');
663         }
664         else
665                 ctx->first = bms_del_member(ctx->first, ctx->level);
666 }
667
668 static void
669 json_scalar(void *state, char *token, JsonTokenType tokentype)
670 {
671         pgspParserContext *ctx = (pgspParserContext *)state;
672         const char *val = token;
673
674         if (ctx->remove)
675                 return;
676
677         if (ctx->valconverter)
678                 val = ctx->valconverter(token, ctx->mode);
679
680         if (tokentype == JSON_TOKEN_STRING)
681                 escape_json(ctx->dest, val);
682         else
683                 appendStringInfoString(ctx->dest, val);
684         ctx->last_elem_is_object = false;
685 }
686
687
688 /* YAML */
689 static void
690 yaml_objstart(void *state)
691 {
692         pgspParserContext *ctx = (pgspParserContext *)state;
693
694         if (ctx->fname)
695         {
696                 if (ctx->dest->len > 0)
697                         appendStringInfoChar(ctx->dest, '\n');
698                 appendStringInfoSpaces(ctx->dest, (ctx->level - 1) * INDENT_STEP);
699                 appendStringInfoString(ctx->dest, "- ");
700                 appendStringInfoString(ctx->dest, ctx->fname);
701                 appendStringInfoString(ctx->dest, ":\n");
702                 appendStringInfoSpaces(ctx->dest, (ctx->level + 1) * INDENT_STEP);
703                 ctx->fname = NULL;
704         }
705
706         ctx->level++;
707         ctx->first = bms_add_member(ctx->first, ctx->level);
708 }
709
710 static void
711 yaml_objend(void *state)
712 {
713         pgspParserContext *ctx = (pgspParserContext *)state;
714
715         ctx->level--;
716         ctx->last_elem_is_object = true;
717         ctx->first = bms_del_member(ctx->first, ctx->level);
718 }
719
720 static void
721 yaml_arrstart(void *state)
722 {
723         pgspParserContext *ctx = (pgspParserContext *)state;
724
725         if (ctx->fname)
726         {
727                 appendStringInfoString(ctx->dest, ctx->fname);
728                 appendStringInfoString(ctx->dest, ":");
729         }
730
731         ctx->fname = NULL;
732         ctx->level++;
733         ctx->first = bms_add_member(ctx->first, ctx->level);
734 }
735
736 static void
737 yaml_arrend(void *state)
738 {
739         pgspParserContext *ctx = (pgspParserContext *)state;
740         ctx->level--;
741 }
742 static void
743 yaml_ofstart(void *state, char *fname, bool isnull)
744 {
745         word_table *p;
746         pgspParserContext *ctx = (pgspParserContext *)state;
747         char *s;
748
749         p = search_word_table(propfields, fname, ctx->mode);
750         if (!p)
751         {
752                 ereport(DEBUG1,
753                                 (errmsg("Short JSON parser encoutered unknown field name: \"%s\".", fname),
754                                  errdetail_log("INPUT: \"%s\"", ctx->org_string)));
755         }               
756         s = (p ? p->longname : fname);
757
758         if (!bms_is_member(ctx->level, ctx->first))
759         {
760                 appendStringInfoString(ctx->dest, "\n");
761                 appendStringInfoSpaces(ctx->dest, ctx->level * INDENT_STEP);
762         }
763         else
764                 ctx->first = bms_del_member(ctx->first, ctx->level);
765
766         ctx->valconverter = NULL;
767         ctx->fname = s;
768         ctx->valconverter = (p ? p->converter : NULL);
769 }
770
771 static void
772 yaml_aestart(void *state, bool isnull)
773 {
774         pgspParserContext *ctx = (pgspParserContext *)state;
775
776         appendStringInfoString(ctx->dest, "\n");
777         bms_del_member(ctx->first, ctx->level);
778         appendStringInfoSpaces(ctx->dest, ctx->level * INDENT_STEP);
779         appendStringInfoString(ctx->dest, "- ");
780 }
781
782 static void
783 yaml_scalar(void *state, char *token, JsonTokenType tokentype)
784 {
785         pgspParserContext *ctx = (pgspParserContext *)state;
786
787         if (ctx->fname)
788         {
789                 appendStringInfoString(ctx->dest, ctx->fname);
790                 appendStringInfoString(ctx->dest, ": ");
791                 ctx->fname = NULL;
792         }
793
794         json_scalar(state, token, tokentype);
795
796         ctx->last_elem_is_object = false;
797 }
798
799
800 /* XML */
801 static void
802 xml_objstart(void *state)
803 {
804         pgspParserContext *ctx = (pgspParserContext *)state;
805
806         ctx->level ++;
807         ctx->first = bms_add_member(ctx->first, ctx->level);
808 }
809
810
811 static void
812 xml_objend(void *state)
813 {
814         pgspParserContext *ctx = (pgspParserContext *)state;
815         appendStringInfoChar(ctx->dest, '\n');
816         appendStringInfoSpaces(ctx->dest, ctx->level * INDENT_STEP);
817
818         ctx->level--;
819         ctx->first = bms_del_member(ctx->first, ctx->level);
820
821         ctx->last_elem_is_object = true;
822 }
823
824 static void
825 xml_arrend(void *state)
826 {
827         pgspParserContext *ctx = (pgspParserContext *)state;
828
829         appendStringInfoChar(ctx->dest, '\n');
830         appendStringInfoSpaces(ctx->dest, (ctx->level + 1) * INDENT_STEP);
831 }
832
833 static void
834 adjust_wbuf(pgspParserContext *ctx, int len)
835 {
836         int buflen;
837
838         for (buflen = ctx->wbuflen ; len > buflen ; buflen *= 2);
839         if (buflen > ctx->wbuflen)
840         {
841                 ctx->wbuf = (char *)palloc(buflen);
842                 ctx->wbuflen = buflen;
843         }
844 }
845
846 static char *
847 hyphenate_words(pgspParserContext *ctx, char *src)
848 {
849         char *p;
850
851         adjust_wbuf(ctx, strlen(src) + 1);
852         strcpy(ctx->wbuf, src);
853
854         for (p = ctx->wbuf ; *p ; p++)
855                 if (*p == ' ') *p = '-';
856
857         return ctx->wbuf;
858 }
859
860 static void
861 xml_ofstart(void *state, char *fname, bool isnull)
862 {
863         word_table *p;
864         pgspParserContext *ctx = (pgspParserContext *)state;
865         char *s;
866
867         p = search_word_table(propfields, fname, ctx->mode);
868         if (!p)
869         {
870                 ereport(DEBUG1,
871                                 (errmsg("Short JSON parser encoutered unknown field name: \"%s\".", fname),
872                                  errdetail_log("INPUT: \"%s\"", ctx->org_string)));
873         }               
874         s = (p ? p->longname : fname);
875
876         /*
877          * save current process context
878          * There's no problem if P_Plan appears recursively.
879          */
880         if (p && (p->tag == P_Plan || p->tag == P_Triggers))
881                 ctx->processing = p->tag;
882
883         appendStringInfoChar(ctx->dest, '\n');
884         appendStringInfoSpaces(ctx->dest, (ctx->level + 1) * INDENT_STEP);
885
886         ctx->valconverter = NULL;
887
888         appendStringInfoChar(ctx->dest, '<');
889         appendStringInfoString(ctx->dest, escape_xml(hyphenate_words(ctx, s)));
890         appendStringInfoChar(ctx->dest, '>');
891         ctx->valconverter = (p ? p->converter : NULL);
892
893         /*
894          * If the object field name is Plan or Triggers, the value should be an
895          * array and the items are tagged by other than "Item". "Item"s appear
896          * only in Output field.
897          */
898         if (p && (p->tag == P_Plans || p->tag == P_Triggers))
899                 ctx->not_item = bms_add_member(ctx->not_item, ctx->level + 1);
900         else
901                 ctx->not_item = bms_del_member(ctx->not_item, ctx->level + 1);
902 }
903
904 static void
905 xml_ofend(void *state, char *fname, bool isnull)
906 {
907         pgspParserContext *ctx = (pgspParserContext *)state;
908         word_table *p;
909         char *s;
910
911         p =     search_word_table(propfields, fname, ctx->mode);
912         s = (p ? p->longname : fname);
913         
914         appendStringInfoString(ctx->dest, "</");
915         appendStringInfoString(ctx->dest, escape_xml(hyphenate_words(ctx, s)));
916         appendStringInfoChar(ctx->dest, '>');
917 }
918
919 static void
920 xml_aestart(void *state, bool isnull)
921 {
922         pgspParserContext *ctx = (pgspParserContext *)state;
923         char *tag;
924
925         /*
926          * The "Trigger" in "Triggers", "Plan" in "Plans" and "Item" nodes are
927          * implicitly represented in JSON format.  Restore them for XML format.
928          */
929
930         ctx->level++;
931         if (bms_is_member(ctx->level, ctx->not_item))
932         {
933                 if (ctx->processing == P_Plan)
934                         tag = "<Plan>";
935                 else
936                         tag = "<Trigger>";
937         }
938         else
939                 tag = "<Item>";
940
941         appendStringInfoChar(ctx->dest, '\n');
942         appendStringInfoSpaces(ctx->dest, (ctx->level + 1) * INDENT_STEP);
943         appendStringInfoString(ctx->dest, tag);
944 }
945
946 static void
947 xml_aeend(void *state, bool isnull)
948 {
949         pgspParserContext *ctx = (pgspParserContext *)state;
950         char *tag;
951
952         /*
953          * The "Plan" in "Plans" or "Item" nodes are implicitly represented in
954          * JSON format.  Restore it for XML format.
955          */
956
957         if (bms_is_member(ctx->level, ctx->not_item))
958         {
959                 if (ctx->processing == P_Plan)
960                         tag = "</Plan>";
961                 else
962                         tag = "</Trigger>";
963         }
964         else
965                 tag = "</Item>";
966         appendStringInfoString(ctx->dest, tag);
967         ctx->level--;
968 }
969
970 static void
971 xml_scalar(void *state, char *token, JsonTokenType tokentype)
972 {
973         pgspParserContext *ctx = (pgspParserContext *)state;
974         const char *s = token;
975
976         if (ctx->valconverter)
977                 s = ctx->valconverter(token, PGSP_JSON_XMLIZE);
978
979         if (tokentype == JSON_TOKEN_STRING)
980                 s = escape_xml(s);
981
982         appendStringInfoString(ctx->dest, s);
983         ctx->last_elem_is_object = false;
984 }
985
986 /********************************/
987 void
988 init_parser_context(pgspParserContext *ctx, int mode,
989                                            char *orgstr, char *buf, int buflen){
990         memset(ctx, 0, sizeof(*ctx));
991         ctx->dest = makeStringInfo();
992         ctx->mode = mode;
993         ctx->org_string = orgstr;
994         ctx->wbuf = buf;
995         ctx->wbuflen = buflen;
996 }
997
998 /*
999  * run_pg_parse_json:
1000  *
1001  * Wrap pg_parse_json in order to restore InterruptHoldoffCount when parse
1002  * error occured.
1003  *
1004  * Returns true when parse completed. False for unexpected end of string.
1005  */
1006 bool
1007 run_pg_parse_json(JsonLexContext *lex, JsonSemAction *sem)
1008 {
1009         MemoryContext ccxt = CurrentMemoryContext;
1010         uint32 saved_IntrHoldoffCount;
1011
1012         /*
1013          * "ereport(ERROR.." occurs on error in pg_parse_json resets
1014          * InterruptHoldoffCount to zero, so we must save the value before calling
1015          * json parser to restore it on parse error. See errfinish().
1016          */
1017         saved_IntrHoldoffCount = InterruptHoldoffCount;
1018
1019         PG_TRY();
1020         {
1021                 pg_parse_json(lex, sem);
1022         }
1023         PG_CATCH();
1024         {
1025                 ErrorData *errdata;
1026                 MemoryContext ecxt;
1027
1028                 InterruptHoldoffCount = saved_IntrHoldoffCount;
1029
1030                 ecxt = MemoryContextSwitchTo(ccxt);
1031                 errdata = CopyErrorData();
1032                 
1033                 if (errdata->sqlerrcode == ERRCODE_INVALID_TEXT_REPRESENTATION)
1034                 {
1035                         FlushErrorState();
1036                         return false;
1037                 }
1038                 else
1039                 {
1040                         MemoryContextSwitchTo(ecxt);
1041                         PG_RE_THROW();
1042                 }
1043         }
1044         PG_END_TRY();
1045
1046         return true;
1047 }
1048
1049 void
1050 init_json_lex_context(JsonLexContext *lex, char *json)
1051 {
1052         lex->input = lex->token_terminator = lex->line_start = json;
1053         lex->line_number = 1;
1054         lex->input_length = strlen(json);
1055         lex->strval = makeStringInfo();
1056 }
1057
1058 static void
1059 init_json_semaction(JsonSemAction *sem, pgspParserContext *ctx)
1060 {
1061         sem->semstate = (void*)ctx;
1062         sem->object_start       = json_objstart;
1063         sem->object_end         = json_objend;
1064         sem->array_start        = json_arrstart;
1065         sem->array_end          = json_arrend;
1066         sem->object_field_start = json_ofstart;
1067         sem->object_field_end   = NULL;
1068         sem->array_element_start= json_aestart;
1069         sem->array_element_end  = NULL;
1070         sem->scalar             = json_scalar;
1071 }
1072
1073 char *
1074 pgsp_json_shorten(char *json)
1075 {
1076         JsonLexContext lex;
1077         JsonSemAction sem;
1078         pgspParserContext    ctx;
1079
1080         init_json_lex_context(&lex, json);
1081         init_parser_context(&ctx, PGSP_JSON_SHORTEN, json, NULL, 0);
1082         init_json_semaction(&sem, &ctx);
1083
1084         run_pg_parse_json(&lex, &sem);
1085
1086         return ctx.dest->data;
1087 }
1088
1089 char *
1090 pgsp_json_normalize(char *json)
1091 {
1092         JsonLexContext lex;
1093         JsonSemAction sem;
1094         pgspParserContext    ctx;
1095
1096         init_json_lex_context(&lex, json);
1097         init_parser_context(&ctx,PGSP_JSON_NORMALIZE, json, NULL, 0);
1098         init_json_semaction(&sem, &ctx);
1099
1100         run_pg_parse_json(&lex, &sem);
1101
1102         return ctx.dest->data;
1103 }
1104
1105 char *
1106 pgsp_json_inflate(char *json)
1107 {
1108         JsonLexContext lex;
1109         JsonSemAction sem;
1110         pgspParserContext    ctx;
1111
1112         init_json_lex_context(&lex, json);
1113         init_parser_context(&ctx, PGSP_JSON_INFLATE, json, NULL, 0);
1114         init_json_semaction(&sem, &ctx);
1115
1116         if (!run_pg_parse_json(&lex, &sem))
1117         {
1118                 if (ctx.dest->len > 0 &&
1119                         ctx.dest->data[ctx.dest->len - 1] != '\n')
1120                         appendStringInfoChar(ctx.dest, '\n');
1121                 
1122                 if (ctx.dest->len == 0)
1123                         appendStringInfoString(ctx.dest, "<Input was not JSON>");
1124                 else
1125                         appendStringInfoString(ctx.dest, "<truncated>");
1126         }
1127
1128         return ctx.dest->data;
1129 }
1130
1131 char *
1132 pgsp_json_yamlize(char *json)
1133 {
1134         pgspParserContext    ctx;
1135         JsonSemAction sem;
1136         JsonLexContext lex;
1137
1138         init_json_lex_context(&lex, json);
1139         init_parser_context(&ctx, PGSP_JSON_YAMLIZE, json, NULL, 0);
1140
1141         sem.semstate = (void*)&ctx;
1142         sem.object_start       = yaml_objstart;
1143         sem.object_end         = yaml_objend;
1144         sem.array_start        = yaml_arrstart;
1145         sem.array_end          = yaml_arrend;
1146         sem.object_field_start = yaml_ofstart;
1147         sem.object_field_end   = NULL;
1148         sem.array_element_start= yaml_aestart;
1149         sem.array_element_end  = NULL;
1150         sem.scalar             = yaml_scalar;
1151
1152         if (!run_pg_parse_json(&lex, &sem))
1153         {
1154                 if (ctx.dest->len > 0 &&
1155                         ctx.dest->data[ctx.dest->len - 1] != '\n')
1156                         appendStringInfoChar(ctx.dest, '\n');
1157                 
1158                 if (ctx.dest->len == 0)
1159                         appendStringInfoString(ctx.dest, "<Input was not JSON>");
1160                 else
1161                         appendStringInfoString(ctx.dest, "<truncated>");
1162         }
1163
1164         return ctx.dest->data;
1165 }
1166
1167 char *
1168 pgsp_json_xmlize(char *json)
1169 {
1170         pgspParserContext      ctx;
1171         JsonSemAction sem;
1172         JsonLexContext lex;
1173         int start_len;
1174         char buf[32];
1175
1176         init_json_lex_context(&lex, json);
1177         init_parser_context(&ctx, PGSP_JSON_XMLIZE, json, buf, sizeof(buf));
1178
1179         sem.semstate = (void*)&ctx;
1180         sem.object_start       = xml_objstart;
1181         sem.object_end         = xml_objend;
1182         sem.array_start        = NULL;
1183         sem.array_end          = xml_arrend;
1184         sem.object_field_start = xml_ofstart;
1185         sem.object_field_end   = xml_ofend;
1186         sem.array_element_start= xml_aestart;
1187         sem.array_element_end  = xml_aeend;
1188         sem.scalar             = xml_scalar;
1189
1190         appendStringInfo(ctx.dest,
1191                                          "<explain xmlns=\"http://www.postgresql.org/2009/explain\">\n  <Query>");
1192         start_len = ctx.dest->len;
1193
1194         if (!run_pg_parse_json(&lex, &sem))
1195         {
1196                 if (ctx.dest->len > start_len &&
1197                         ctx.dest->data[ctx.dest->len - 1] != '\n')
1198                         appendStringInfoChar(ctx.dest, '\n');
1199                 
1200                 if (ctx.dest->len == start_len)
1201                 {
1202                         resetStringInfo(ctx.dest);
1203                         appendStringInfoString(ctx.dest, "<Input was not JSON>");
1204                 }
1205                 else
1206                         appendStringInfoString(ctx.dest, "<truncated>");
1207         }
1208         else
1209                 appendStringInfo(ctx.dest, "</Query>\n</explain>\n");
1210
1211         return ctx.dest->data;
1212 }