1 /*-------------------------------------------------------------------------
3 * pgsp_json.c: Plan handler for JSON/XML/YAML style plans
5 * Copyright (c) 2012-2022, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
8 * pg_store_plans/pgsp_json.c
10 *-------------------------------------------------------------------------
14 #include "miscadmin.h"
15 #include "nodes/nodes.h"
16 #include "nodes/parsenodes.h"
17 #include "nodes/bitmapset.h"
18 #include "parser/scanner.h"
19 #include "parser/gram.h"
20 #include "utils/xml.h"
21 #include "utils/json.h"
22 #if PG_VERSION_NUM < 130000
23 #include "utils/jsonapi.h"
25 #include "common/jsonapi.h"
27 #include "pgsp_json.h"
28 #include "pgsp_json_int.h"
33 void normalize_expr(char *expr, bool preserve_space);
34 static const char *converter_core(word_table *tbl,
35 const char *src, pgsp_parser_mode mode);
37 static void json_objstart(void *state);
38 static void json_objend(void *state);
39 static void json_arrstart(void *state);
40 static void json_arrend(void *state);
41 static void json_ofstart(void *state, char *fname, bool isnull);
42 static void json_aestart(void *state, bool isnull);
43 static void json_scalar(void *state, char *token, JsonTokenType tokentype);
45 static void yaml_objstart(void *state);
46 static void yaml_objend(void *state);
47 static void yaml_arrstart(void *state);
48 static void yaml_arrend(void *state);
49 static void yaml_ofstart(void *state, char *fname, bool isnull);
50 static void yaml_aestart(void *state, bool isnull);
51 static void yaml_scalar(void *state, char *token, JsonTokenType tokentype);
53 static void adjust_wbuf(pgspParserContext *ctx, int len);
54 static char *hyphenate_words(pgspParserContext *ctx, char *src);
55 static void xml_objstart(void *state);
56 static void xml_objend(void *state);
57 static void xml_arrend(void *state);
58 static void xml_ofstart(void *state, char *fname, bool isnull);
59 static void xml_ofend(void *state, char *fname, bool isnull);
60 static void xml_aestart(void *state, bool isnull);
61 static void xml_aeend(void *state, bool isnull);
62 static void xml_scalar(void *state, char *token, JsonTokenType tokentype) ;
64 static void init_json_semaction(JsonSemAction *sem,
65 pgspParserContext *ctx);
67 word_table propfields[] =
69 {P_NodeType, "t" ,"Node Type", NULL, true, conv_nodetype, SETTER(node_type)},
70 {P_RelationShip, "h" ,"Parent Relationship", NULL, true, conv_relasionship, NULL},
71 {P_RelationName, "n" ,"Relation Name", NULL, true, NULL, SETTER(obj_name)},
72 {P_FunctioName, "f" ,"Function Name", NULL, true, NULL, SETTER(obj_name)},
73 {P_IndexName, "i" ,"Index Name", NULL, true, NULL, SETTER(index_name)},
74 {P_CTEName, "c" ,"CTE Name", NULL, true, NULL, SETTER(obj_name)},
75 {P_TrgRelation, "w" ,"Relation", NULL, true, NULL, SETTER(trig_relation)},
76 {P_Schema, "s" ,"Schema", NULL, true, NULL, SETTER(schema_name)},
77 {P_Alias, "a" ,"Alias", NULL, true, NULL, SETTER(alias)},
78 {P_Output, "o" ,"Output", NULL, true, conv_expression, SETTER(output)},
79 {P_ScanDir, "d" ,"Scan Direction", NULL, true, conv_scandir, SETTER(scan_dir)},
80 {P_MergeCond, "m" ,"Merge Cond", NULL, true, conv_expression, SETTER(merge_cond)},
81 {P_Strategy, "g" ,"Strategy", NULL, true, conv_strategy, SETTER(strategy)},
82 {P_JoinType, "j" ,"Join Type", NULL, true, conv_jointype, SETTER(join_type)},
83 {P_SortMethod, "e" ,"Sort Method", NULL, true, conv_sortmethod, SETTER(sort_method)},
84 {P_SortKey, "k" ,"Sort Key", NULL, true, conv_expression, SETTER(sort_key)},
85 {P_Filter, "5" ,"Filter", NULL, true, conv_expression, SETTER(filter)},
86 {P_JoinFilter, "6" ,"Join Filter", NULL, true, conv_expression, SETTER(join_filter)},
87 {P_HashCond, "7" ,"Hash Cond", NULL, true, conv_expression, SETTER(hash_cond)},
88 {P_IndexCond, "8" ,"Index Cond", NULL, true, conv_expression, SETTER(index_cond)},
89 {P_TidCond, "9" ,"TID Cond", NULL, true, conv_expression, SETTER(tid_cond)},
90 {P_RecheckCond, "0" ,"Recheck Cond", NULL, true, conv_expression, SETTER(recheck_cond)},
91 {P_Operation, "!" ,"Operation", NULL, true, conv_operation, SETTER(operation)},
92 {P_SubplanName, "q" ,"Subplan Name", NULL, true, NULL, SETTER(subplan_name)},
93 {P_Command, "b" ,"Command", NULL, true, conv_setsetopcommand,SETTER(setopcommand)},
94 {P_Triggers, "r" ,"Triggers", NULL, true, NULL, NULL},
95 {P_Trigger, "u" ,"Trigger", NULL, true, NULL, SETTER(node_type)},
96 {P_TriggerName, "v" ,"Trigger Name", NULL, true, NULL, SETTER(trig_name)},
97 {P_ConstraintName, "x" ,"Constraint Name", NULL, true, NULL, NULL},
98 {P_Plans, "l" ,"Plans", NULL, true, NULL, NULL},
99 {P_Plan, "p" ,"Plan", NULL, true, NULL, NULL},
100 {P_GroupKey, "-" ,"Group Key", NULL, true, NULL, SETTER(group_key)},
101 {P_GroupSets, "=" ,"Grouping Sets", NULL, true, NULL, NULL},
102 {P_GroupKeys, "\\" ,"Group Keys", NULL, true, NULL, SETTER(group_key)},
104 {P_HashKeys, "~" ,"Hash Keys", NULL, true, NULL, SETTER(hash_key)},
105 {P_HashKey, "|" ,"Hash Key", NULL, true, NULL, SETTER(hash_key)},
107 {P_Parallel, "`" ,"Parallel Aware", NULL, true, NULL, SETTER(parallel_aware)},
108 {P_PartialMode, ">" ,"Partial Mode", NULL, true, conv_partialmode,SETTER(partial_mode)},
109 {P_WorkersPlanned, "{" ,"Workers Planned", NULL, true, NULL, SETTER(workers_planned)},
110 {P_WorkersLaunched, "}" ,"Workers Launched", NULL, true, NULL, SETTER(workers_launched)},
111 {P_InnerUnique, "?" ,"Inner Unique", NULL, true, NULL, SETTER(inner_unique)},
112 {P_AsyncCapable, "ac", "Async Capable", NULL, true, NULL, SETTER(async_capable)},
114 /* Values of these properties are ignored on normalization */
115 {P_FunctionCall, "y" ,"Function Call", NULL, false, NULL, SETTER(func_call)},
116 {P_StartupCost, "1" ,"Startup Cost", NULL, false, NULL, SETTER(startup_cost)},
117 {P_TotalCost, "2" ,"Total Cost", NULL, false, NULL, SETTER(total_cost)},
118 {P_PlanRows, "3" ,"Plan Rows", NULL, false, NULL, SETTER(plan_rows)},
119 {P_PlanWidth, "4" ,"Plan Width", NULL, false, NULL, SETTER(plan_width)},
120 {P_ActualStartupTime,"A","Actual Startup Time", NULL, false, NULL, SETTER(actual_startup_time)},
121 {P_ActualTotalTime, "B" ,"Actual Total Time", NULL, false, NULL, SETTER(actual_total_time)},
122 {P_ActualRows, "C" ,"Actual Rows", NULL, false, NULL, SETTER(actual_rows)},
123 {P_ActualLoops, "D" ,"Actual Loops", NULL, false, NULL, SETTER(actual_loops)},
124 {P_HeapFetches, "E" ,"Heap Fetches", NULL, false, NULL, SETTER(heap_fetches)},
125 {P_SharedHitBlks, "F" ,"Shared Hit Blocks", NULL, false, NULL, SETTER(shared_hit_blks)},
126 {P_SharedReadBlks, "G" ,"Shared Read Blocks", NULL, false, NULL, SETTER(shared_read_blks)},
127 {P_SharedDirtiedBlks,"H","Shared Dirtied Blocks",NULL,false, NULL, SETTER(shared_dirtied_blks)},
128 {P_SharedWrittenBlks,"I","Shared Written Blocks",NULL,false, NULL, SETTER(shared_written_blks)},
129 {P_LocalHitBlks, "J" ,"Local Hit Blocks", NULL, false, NULL, SETTER(local_hit_blks)},
130 {P_LocalReadBlks, "K" ,"Local Read Blocks", NULL, false, NULL, SETTER(local_read_blks)},
131 {P_LocalDirtiedBlks,"L" ,"Local Dirtied Blocks",NULL, false, NULL, SETTER(local_dirtied_blks)},
132 {P_LocalWrittenBlks,"M" ,"Local Written Blocks",NULL, false, NULL, SETTER(local_written_blks)},
133 {P_TempReadBlks, "N" ,"Temp Read Blocks", NULL, false, NULL, SETTER(temp_read_blks)},
134 {P_TempWrittenBlks, "O" ,"Temp Written Blocks", NULL, false, NULL, SETTER(temp_written_blks)},
135 {P_IOReadTime, "P" ,"I/O Read Time", NULL, false, NULL, SETTER(io_read_time)},
136 {P_IOWwriteTime, "Q" ,"I/O Write Time", NULL, false, NULL, SETTER(io_write_time)},
137 {P_SortSpaceUsed, "R" ,"Sort Space Used", NULL, false, NULL, SETTER(sort_space_used)},
138 {P_SortSpaceType, "S" ,"Sort Space Type", NULL, false, conv_sortspacetype,SETTER(sort_space_type)},
139 {P_PeakMemoryUsage, "T" ,"Peak Memory Usage", NULL, false, NULL, SETTER(peak_memory_usage)},
140 {P_OrgHashBatches, "U","Original Hash Batches",NULL, false, NULL, SETTER(org_hash_batches)},
141 {P_OrgHashBuckets, "*","Original Hash Buckets",NULL, false, NULL, SETTER(org_hash_buckets)},
142 {P_HashBatches, "V" ,"Hash Batches", NULL, false, NULL, SETTER(hash_batches)},
143 {P_HashBuckets, "W" ,"Hash Buckets", NULL, false, NULL, SETTER(hash_buckets)},
144 {P_RowsFilterRmvd, "X" ,"Rows Removed by Filter",NULL,false,NULL, SETTER(filter_removed)},
145 {P_RowsIdxRchkRmvd, "Y" ,"Rows Removed by Index Recheck",NULL,false, NULL, SETTER(idxrchk_removed)},
146 {P_TrgTime, "Z" ,"Time", NULL, false, NULL, SETTER(trig_time)},
147 {P_TrgCalls, "z" ,"Calls", NULL, false, NULL, SETTER(trig_calls)},
148 {P_PlanTime, "#" ,"Planning Time", NULL, false, NULL, SETTER(plan_time)},
149 {P_ExecTime, "$" ,"Execution Time", NULL, false, NULL, SETTER(exec_time)},
150 {P_ExactHeapBlks, "&" ,"Exact Heap Blocks", NULL, false, NULL, SETTER(exact_heap_blks)},
151 {P_LossyHeapBlks, "(" ,"Lossy Heap Blocks", NULL, false, NULL, SETTER(lossy_heap_blks)},
152 {P_RowsJoinFltRemvd,")" ,"Rows Removed by Join Filter", NULL, false, NULL, SETTER(joinfilt_removed)},
153 {P_TargetTables, "_" ,"Target Tables", NULL, false, NULL, NULL},
154 {P_ConfRes, "%" ,"Conflict Resolution", NULL, false, NULL, SETTER(conflict_resolution)},
155 {P_ConfArbitIdx, "@" ,"Conflict Arbiter Indexes",NULL, false, NULL, SETTER(conflict_arbiter_indexes)},
156 {P_TuplesInserted, "^" ,"Tuples Inserted", NULL, false, NULL, SETTER(tuples_inserted)},
157 {P_ConfTuples, "+" ,"Conflicting Tuples", NULL, false, NULL, SETTER(conflicting_tuples)},
158 {P_SamplingMethod, ":" ,"Sampling Method" , NULL, false, NULL, SETTER(sampling_method)},
159 {P_SamplingParams, ";" ,"Sampling Parameters" , NULL, false, NULL, SETTER(sampling_params)},
160 {P_RepeatableSeed, "<" ,"Repeatable Seed" , NULL, false, NULL, SETTER(repeatable_seed)},
161 {P_Workers, "[" ,"Workers", NULL, false, NULL, NULL},
162 {P_WorkerNumber, "]" ,"Worker Number", NULL, false, NULL, SETTER(worker_number)},
163 {P_TableFuncName, "aa" ,"Table Function Name",NULL, false, NULL, SETTER(table_func_name)},
165 {P_PresortedKey, "pk" ,"Presorted Key" ,NULL, false, NULL, SETTER(presorted_key)},
166 {P_FullsortGroups, "fg" ,"Full-sort Groups" ,NULL, false, NULL, NULL},
167 {P_SortMethodsUsed, "su" ,"Sort Methods Used" ,NULL, false, NULL, SETTER(sortmethod_used)},
168 {P_SortSpaceMemory, "sm" ,"Sort Space Memory" ,NULL, false, NULL, SETTER(sortspace_mem)},
169 {P_GroupCount, "gc" ,"Group Count" ,NULL, false, NULL, SETTER(group_count)},
170 {P_AvgSortSpcUsed, "as" ,"Average Sort Space Used",NULL, false, NULL, SETTER(avg_sortspc_used)},
171 {P_PeakSortSpcUsed, "ps" ,"Peak Sort Space Used",NULL, false, NULL, SETTER(peak_sortspc_used)},
172 {P_PreSortedGroups, "pg" ,"Pre-sorted Groups" ,NULL, false, NULL, NULL},
174 {P_Invalid, NULL, NULL, NULL, false, NULL, NULL}
177 word_table nodetypes[] =
179 {T_Result, "a" ,"Result", NULL, false, NULL, NULL},
180 {T_ModifyTable, "b" ,"ModifyTable", NULL, false, NULL, NULL},
181 {T_Append, "c" ,"Append", NULL, false, NULL, NULL},
182 {T_MergeAppend, "d" ,"Merge Append", NULL, false, NULL, NULL},
183 {T_RecursiveUnion,"e" ,"Recursive Union",NULL, false, NULL, NULL},
184 {T_BitmapAnd, "f" ,"BitmapAnd", NULL, false, NULL, NULL},
185 {T_BitmapOr, "g" ,"BitmapOr", NULL, false, NULL, NULL},
186 {T_Scan, "" , "", "", false, NULL, NULL},
187 {T_SeqScan, "h" ,"Seq Scan", NULL, false, NULL, NULL},
188 {T_IndexScan, "i" ,"Index Scan", NULL, false, NULL, NULL},
189 {T_IndexOnlyScan,"j","Index Only Scan",NULL, false, NULL, NULL},
190 {T_BitmapIndexScan,"k" ,"Bitmap Index Scan", NULL, false, NULL, NULL},
191 {T_BitmapHeapScan,"l" ,"Bitmap Heap Scan", NULL ,false, NULL, NULL},
192 {T_TidScan, "m" ,"Tid Scan", NULL, false, NULL, NULL},
193 {T_SubqueryScan,"n" ,"Subquery Scan", NULL, false, NULL, NULL},
194 {T_FunctionScan,"o" ,"Function Scan", NULL, false, NULL, NULL},
195 {T_ValuesScan, "p" ,"Values Scan", NULL, false, NULL, NULL},
196 {T_CteScan, "q" ,"CTE Scan", NULL, false, NULL, NULL},
197 {T_WorkTableScan,"r","WorkTable Scan", NULL, false, NULL, NULL},
198 {T_ForeignScan, "s" , "Foreign Scan", NULL, false, NULL, NULL},
199 {T_Join, "" , "", NULL, false, NULL, NULL},
200 {T_NestLoop, "t" ,"Nested Loop", NULL, false, NULL, NULL},
201 {T_MergeJoin, "u" ,"Merge Join", "Merge", false, NULL, NULL},
202 {T_HashJoin, "v" ,"Hash Join", "Hash", false, NULL, NULL},
203 {T_Material, "w" ,"Materialize", NULL, false, NULL, NULL},
204 {T_Sort, "x" ,"Sort", NULL, false, NULL, NULL},
205 {T_Group, "y" ,"Group", NULL, false, NULL, NULL},
206 {T_Agg, "z" ,"Aggregate", NULL, false, NULL, NULL},
207 {T_WindowAgg, "0" ,"WindowAgg", NULL, false, NULL, NULL},
208 {T_Unique, "1" ,"Unique", NULL, false, NULL, NULL},
209 {T_Hash, "2" ,"Hash", NULL, false, NULL, NULL},
210 {T_SetOp, "3" ,"SetOp", NULL, false, NULL, NULL},
211 {T_LockRows, "4" ,"LockRows", NULL, false, NULL, NULL},
212 {T_Limit, "5" ,"Limit", NULL, false, NULL, NULL},
213 #if PG_VERSION_NUM >= 90500
214 {T_SampleScan, "B" ,"Sample Scan", NULL, false, NULL, NULL},
216 #if PG_VERSION_NUM >= 90600
217 {T_Gather, "6" ,"Gather", NULL, false, NULL, NULL},
219 #if PG_VERSION_NUM >= 100000
220 {T_ProjectSet, "7" ,"ProjectSet", NULL, false, NULL, NULL},
221 {T_TableFuncScan,"8","Table Function Scan", NULL, false, NULL, NULL},
222 {T_NamedTuplestoreScan,"9","Named Tuplestore Scan", NULL, false, NULL, NULL},
223 {T_GatherMerge, "A" ,"Gather Merge", NULL, false, NULL, NULL},
225 #if PG_VERSION_NUM >= 130000
226 {T_IncrementalSort, "C" ,"Incremental Sort", NULL, false, NULL, NULL},
228 #if PG_VERSION_NUM >= 140000
229 {T_TidRangeScan,"D", "Tid Range Scan", NULL, false, NULL, NULL},
230 {T_Memoize, "E", "Memoize", NULL, false, NULL, NULL},
233 {T_Invalid, NULL, NULL, NULL, false, NULL, NULL}
236 word_table directions[] =
238 {T_Invalid, "b" ,"Backward", "Backward", false, NULL, NULL},
239 {T_Invalid, "n" ,"NoMovement","", false, NULL, NULL},
240 {T_Invalid, "f" ,"Forward", "", false, NULL, NULL},
241 {T_Invalid, NULL , NULL, NULL, false, NULL, NULL}
244 word_table relationships[] =
246 {T_Invalid, "o" ,"Outer", NULL, false, NULL, NULL},
247 {T_Invalid, "i" ,"Inner", NULL, false, NULL, NULL},
248 {T_Invalid, "s" ,"Subquery", NULL, false, NULL, NULL},
249 {T_Invalid, "m" ,"Member", NULL, false, NULL, NULL},
250 {T_Invalid, "I" ,"InitPlan", NULL, false, NULL, NULL},
251 {T_Invalid, "S" ,"SubPlan", NULL, false, NULL, NULL},
252 {T_Invalid, NULL, NULL, NULL, false, NULL, NULL}
255 word_table strategies[] =
257 {S_Plain, "p" ,"Plain", NULL, false, NULL, NULL},
258 {S_Sorted, "s" ,"Sorted", NULL, false, NULL, NULL},
259 {S_Hashed, "h" ,"Hashed", NULL, false, NULL, NULL},
260 {S_Mixed, "m" ,"Mixed", NULL, false, NULL, NULL},
261 {S_Invalid, NULL, NULL, NULL, false, NULL, NULL}
264 word_table operations[] =
266 {T_Invalid, "i" ,"Insert", NULL, false, NULL, NULL},
267 {T_Invalid, "d" ,"Delete", NULL, false, NULL, NULL},
268 {T_Invalid, "u" ,"Update", NULL, false, NULL, NULL},
269 {T_Invalid, NULL, NULL, NULL, false, NULL, NULL}
272 word_table jointypes[] =
274 {T_Invalid, "i" ,"Inner", NULL, false, NULL, NULL},
275 {T_Invalid, "l" ,"Left", NULL, false, NULL, NULL},
276 {T_Invalid, "f" ,"Full", NULL, false, NULL, NULL},
277 {T_Invalid, "r" ,"Right", NULL, false, NULL, NULL},
278 {T_Invalid, "s" ,"Semi", NULL, false, NULL, NULL},
279 {T_Invalid, "a" ,"Anti", NULL, false, NULL, NULL},
280 {T_Invalid, NULL, NULL, NULL, false, NULL, NULL}
283 word_table setsetopcommands[] =
285 {T_Invalid, "i" ,"Intersect", NULL, false, NULL, NULL},
286 {T_Invalid, "I" ,"Intersect All", NULL, false, NULL, NULL},
287 {T_Invalid, "e" ,"Except", NULL, false, NULL, NULL},
288 {T_Invalid, "E" ,"Except All", NULL, false, NULL, NULL},
289 {T_Invalid, NULL, NULL, NULL, false, NULL, NULL}
292 word_table sortmethods[] =
294 {T_Invalid, "h" ,"top-N heapsort", NULL, false, NULL, NULL},
295 {T_Invalid, "q" ,"quicksort", NULL, false, NULL, NULL},
296 {T_Invalid, "e" ,"external sort", NULL, false, NULL, NULL},
297 {T_Invalid, "E" ,"external merge", NULL, false, NULL, NULL},
298 {T_Invalid, "s" ,"still in progress", NULL, false, NULL, NULL},
299 {T_Invalid, NULL, NULL, NULL, false, NULL, NULL}
302 word_table sortspacetype[] =
304 {T_Invalid, "d" ,"Disk", NULL, false, NULL, NULL},
305 {T_Invalid, "m" ,"Memory",NULL, false, NULL, NULL},
306 {T_Invalid, NULL, NULL, NULL, false, NULL, NULL}
309 word_table partialmode[] =
311 {T_Invalid, "p" ,"Partial", NULL, false, NULL, NULL},
312 {T_Invalid, "f" ,"Finalize",NULL, false, NULL, NULL},
313 {T_Invalid, "s" ,"Simple",NULL, false, NULL, NULL},
314 {T_Invalid, NULL, NULL, NULL, false, NULL, NULL}
319 search_word_table(word_table *tbl, const char *word, int mode)
324 (mode == PGSP_JSON_SHORTEN || mode == PGSP_JSON_NORMALIZE);
328 * Use simple linear search. We can gain too small portion of the whole
329 * processing time using more 'clever' algorithms like b-tree or tries,
330 * which won't be worth the additional memory, complexity and
331 * initialization cost.
333 for (p = tbl ; p->longname ; p++)
335 if (strcmp(longname ? p->longname: p->shortname, word) == 0)
339 if (p->longname == NULL && mode == PGSP_JSON_TEXTIZE)
341 /* Fallback to long json prop name */
342 for (p = tbl ; p->longname ; p++)
343 if (strcmp(p->longname, word) == 0)
347 return (p->longname ? p : NULL);
352 converter_core(word_table *tbl,
353 const char *src, pgsp_parser_mode mode)
358 p = search_word_table(tbl, src, mode);
365 case PGSP_JSON_SHORTEN:
366 case PGSP_JSON_NORMALIZE:
369 case PGSP_JSON_INFLATE:
370 case PGSP_JSON_YAMLIZE:
371 case PGSP_JSON_XMLIZE:
374 case PGSP_JSON_TEXTIZE:
381 elog(ERROR, "Internal error");
387 conv_nodetype(const char *src, pgsp_parser_mode mode)
389 return converter_core(nodetypes, src, mode);
393 conv_scandir(const char *src, pgsp_parser_mode mode)
395 return converter_core(directions, src, mode);
399 conv_relasionship(const char *src, pgsp_parser_mode mode)
401 return converter_core(relationships, src, mode);
405 conv_strategy(const char *src, pgsp_parser_mode mode)
407 return converter_core(strategies, src, mode);
411 * Look for these operator characters in order to decide whether to strip
412 * whitespaces which are needless from the view of sql syntax in
413 * normalize_expr(). This must be synced with op_chars in scan.l.
415 #define OPCHARS "~!@#^&|`?+-*/%<>="
416 #define IS_WSCHAR(c) ((c) == ' ' || (c) == '\n' || (c) == '\t')
417 #define IS_CONST(tok) (tok == FCONST || tok == SCONST || tok == BCONST || \
418 tok == XCONST || tok == ICONST || tok == NULL_P || \
419 tok == TRUE_P || tok == FALSE_P || \
420 tok == CURRENT_DATE || tok == CURRENT_TIME || \
421 tok == LOCALTIME || tok == LOCALTIMESTAMP)
422 #define IS_INDENTED_ARRAY(v) ((v) == P_GroupKeys || (v) == P_HashKeys)
425 * norm_yylex: core_yylex with replacing some tokens.
428 norm_yylex(char *str, core_YYSTYPE *yylval, YYLTYPE *yylloc, core_yyscan_t yyscanner)
434 tok = core_yylex(yylval, yylloc, yyscanner);
439 * Error might occur during parsing quoted tokens that chopped
440 * halfway. Just ignore the rest of this query even if there might
441 * be other reasons for parsing to fail.
449 * '?' alone is assumed to be an IDENT. If there's a real
450 * operator '?', this should be confused but there's hardly be.
452 if (tok == Op && str[*yylloc] == '?' &&
453 strchr(OPCHARS, str[*yylloc + 1]) == NULL)
457 * Replace tokens with '=' if the operator is consists of two or
458 * more opchars only. Assuming that opchars do not compose a token
459 * with non-opchars, check the first char only is sufficient.
461 if (tok == Op && strchr(OPCHARS, str[*yylloc]) != NULL)
468 * normalize_expr - Normalize statements or expressions.
470 * Mask constants, strip unnecessary whitespaces and upcase keywords. expr is
471 * modified in-place (destructively). If readability is more important than
472 * uniqueness, preserve_space puts one space for one existent whitespace for
475 /* scanner interface is changed in PG12 */
476 #if PG_VERSION_NUM < 120000
477 #define ScanKeywords (*ScanKeywords)
478 #define ScanKeywordTokens NumScanKeywords
481 normalize_expr(char *expr, bool preserve_space)
483 core_yyscan_t yyscanner;
484 core_yy_extra_type yyextra;
493 yyscanner = scanner_init(expr,
499 * The warnings about nonstandard escape strings is already emitted in the
500 * core. Just silence them here.
502 #if PG_VERSION_NUM >= 90500
503 yyextra.escape_string_warning = false;
510 tok = norm_yylex(expr, &yylval, &yylloc, yyscanner);
518 /* Skipping preceding whitespaces */
519 for(i = lastloc ; i < start && IS_WSCHAR(expr[i]) ; i++);
521 /* Searching for trailing whitespace */
522 for(i2 = i; i2 < start && !IS_WSCHAR(expr[i2]) ; i2++);
524 if (lasttok == IDENT)
526 /* Identifiers are copied in case-sensitive manner. */
527 memcpy(wp, expr + i, i2 - i);
530 #if PG_VERSION_NUM >= 100000
532 * Since PG10 pg_stat_statements doesn't store trailing semicolon
533 * in the column "query". Normalization is basically useless in the
534 * version but still usefull to match utility commands so follow
535 * the behavior change.
537 else if (lasttok == ';')
539 /* Just do nothing */
544 /* Upcase keywords */
546 for (sp = expr + i ; sp < expr + i2 ; sp++, wp++)
547 *wp = (*sp >= 'a' && *sp <= 'z' ?
548 *sp - ('a' - 'A') : *sp);
552 * Because of destructive writing, wp must not go advance the
554 * Although this function's output does not need any validity as a
555 * statement or an expression, spaces are added where it should be
556 * to keep some extent of sanity. If readability is more important
557 * than uniqueness, preserve_space adds one space for each
558 * existent whitespace.
563 (tok >= IDENT && lasttok >= IDENT &&
564 !IS_CONST(tok) && !IS_CONST(lasttok))))
570 /* Exit on parse error. */
578 * Negative signs before numbers are tokenized separately. And
579 * explicit positive signs won't appear in deparsed expressions.
582 tok = norm_yylex(expr, &yylval, &yylloc, yyscanner);
584 /* Exit on parse error. */
595 tok = norm_yylex(expr, &yylval, &end, yyscanner);
597 /* Exit on parse error. */
605 * Negative values may be surrounded with parens by the
606 * deparser. Mask involving them.
608 if (lasttok == '(' && tok == ')')
610 wp -= (start - lastloc);
615 while (expr[end - 1] == ' ')
632 conv_expression(const char *src, pgsp_parser_mode mode)
634 const char *ret = src;
636 if (mode == PGSP_JSON_NORMALIZE)
638 char *t = pstrdup(src);
639 normalize_expr(t, true);
640 ret = (const char *)t;
646 conv_operation(const char *src, pgsp_parser_mode mode)
648 return converter_core(operations, src, mode);
653 conv_jointype(const char *src, pgsp_parser_mode mode)
655 return converter_core(jointypes, src, mode);
659 conv_setsetopcommand(const char *src, pgsp_parser_mode mode)
661 return converter_core(setsetopcommands, src, mode);
665 conv_sortmethod(const char *src, pgsp_parser_mode mode)
667 return converter_core(sortmethods, src, mode);
671 conv_sortspacetype(const char *src, pgsp_parser_mode mode)
673 return converter_core(sortspacetype, src, mode);
677 conv_partialmode(const char *src, pgsp_parser_mode mode)
679 return converter_core(partialmode, src, mode);
682 /**** Parser callbacks ****/
686 json_objstart(void *state)
688 pgspParserContext *ctx = (pgspParserContext *)state;
690 if (ctx->mode == PGSP_JSON_INFLATE)
692 if (!ctx->fname && ctx->dest->len > 0)
694 appendStringInfoChar(ctx->dest, '\n');
695 appendStringInfoSpaces(ctx->dest, (ctx->level) * INDENT_STEP);
699 appendStringInfoChar(ctx->dest, '{');
702 ctx->first = bms_add_member(ctx->first, ctx->level);
704 if (ctx->mode == PGSP_JSON_INFLATE)
705 appendStringInfoChar(ctx->dest, '\n');
709 json_objend(void *state)
711 pgspParserContext *ctx = (pgspParserContext *)state;
712 if (ctx->mode == PGSP_JSON_INFLATE)
714 if (!bms_is_member(ctx->level, ctx->first))
715 appendStringInfoChar(ctx->dest, '\n');
716 appendStringInfoSpaces(ctx->dest, (ctx->level - 1) * INDENT_STEP);
719 appendStringInfoChar(ctx->dest, '}');
722 ctx->last_elem_is_object = true;
723 ctx->first = bms_del_member(ctx->first, ctx->level);
728 json_arrstart(void *state)
730 pgspParserContext *ctx = (pgspParserContext *)state;
732 if (IS_INDENTED_ARRAY(ctx->current_list))
735 appendStringInfoChar(ctx->dest, '[');
738 ctx->last_elem_is_object = true;
739 ctx->first = bms_add_member(ctx->first, ctx->level);
743 json_arrend(void *state)
745 pgspParserContext *ctx = (pgspParserContext *)state;
747 if (IS_INDENTED_ARRAY(ctx->current_list))
750 if (ctx->mode == PGSP_JSON_INFLATE &&
751 (IS_INDENTED_ARRAY(ctx->current_list) ?
752 ctx->wlist_level == 0 : ctx->last_elem_is_object))
754 appendStringInfoChar(ctx->dest, '\n');
755 appendStringInfoSpaces(ctx->dest, (ctx->level - 1) * INDENT_STEP);
758 appendStringInfoChar(ctx->dest, ']');
763 json_ofstart(void *state, char *fname, bool isnull)
766 pgspParserContext *ctx = (pgspParserContext *)state;
770 p = search_word_table(propfields, fname, ctx->mode);
774 (errmsg("JSON parser encoutered unknown field name: \"%s\".", fname),
775 errdetail_log("INPUT: \"%s\"", ctx->org_string)));
778 ctx->remove = (ctx->mode == PGSP_JSON_NORMALIZE &&
779 (!p || !p->normalize_use));
784 if (!bms_is_member(ctx->level, ctx->first))
786 appendStringInfoChar(ctx->dest, ',');
787 if (ctx->mode == PGSP_JSON_INFLATE)
788 appendStringInfoChar(ctx->dest, '\n');
791 ctx->first = bms_del_member(ctx->first, ctx->level);
793 if (ctx->mode == PGSP_JSON_INFLATE)
794 appendStringInfoSpaces(ctx->dest, ctx->level * INDENT_STEP);
797 * We intentionally let some property names not have a short name. Use long
798 * name for the cases.
800 if (!p || !p->longname)
802 else if (ctx->mode == PGSP_JSON_INFLATE ||
803 !(p->shortname && p->shortname[0]))
808 escape_json(ctx->dest, fn);
810 ctx->valconverter = (p ? p->converter : NULL);
812 appendStringInfoChar(ctx->dest, ':');
814 if (ctx->mode == PGSP_JSON_INFLATE)
815 appendStringInfoChar(ctx->dest, ' ');
817 if (p && IS_INDENTED_ARRAY(p->tag))
819 ctx->current_list = p->tag;
820 ctx->list_fname = fname;
821 ctx->wlist_level = 0;
826 json_ofend(void *state, char *fname, bool isnull)
828 pgspParserContext *ctx = (pgspParserContext *)state;
830 if (ctx->list_fname && strcmp(fname, ctx->list_fname) == 0)
832 ctx->list_fname = NULL;
833 ctx->current_list = P_Invalid;
838 json_aestart(void *state, bool isnull)
840 pgspParserContext *ctx = (pgspParserContext *)state;
844 if (IS_INDENTED_ARRAY(ctx->current_list) &&
845 ctx->wlist_level == 1)
847 if (!bms_is_member(ctx->level, ctx->first))
848 appendStringInfoChar(ctx->dest, ',');
850 if (ctx->mode == PGSP_JSON_INFLATE)
852 appendStringInfoChar(ctx->dest, '\n');
853 appendStringInfoSpaces(ctx->dest, (ctx->level) * INDENT_STEP);
858 if (!bms_is_member(ctx->level, ctx->first))
860 appendStringInfoChar(ctx->dest, ',');
862 if (ctx->mode == PGSP_JSON_INFLATE &&
863 !ctx->last_elem_is_object)
864 appendStringInfoChar(ctx->dest, ' ');
868 ctx->first = bms_del_member(ctx->first, ctx->level);
872 json_scalar(void *state, char *token, JsonTokenType tokentype)
874 pgspParserContext *ctx = (pgspParserContext *)state;
875 const char *val = token;
880 if (ctx->valconverter)
881 val = ctx->valconverter(token, ctx->mode);
883 if (tokentype == JSON_TOKEN_STRING)
884 escape_json(ctx->dest, val);
886 appendStringInfoString(ctx->dest, val);
887 ctx->last_elem_is_object = false;
893 yaml_objstart(void *state)
895 pgspParserContext *ctx = (pgspParserContext *)state;
899 if (ctx->dest->len > 0)
900 appendStringInfoChar(ctx->dest, '\n');
901 appendStringInfoSpaces(ctx->dest, (ctx->level - 1) * INDENT_STEP);
902 appendStringInfoString(ctx->dest, "- ");
903 appendStringInfoString(ctx->dest, ctx->fname);
904 appendStringInfoString(ctx->dest, ":\n");
905 appendStringInfoSpaces(ctx->dest, (ctx->level + 1) * INDENT_STEP);
910 ctx->first = bms_add_member(ctx->first, ctx->level);
914 yaml_objend(void *state)
916 pgspParserContext *ctx = (pgspParserContext *)state;
919 ctx->last_elem_is_object = true;
920 ctx->first = bms_del_member(ctx->first, ctx->level);
924 yaml_arrstart(void *state)
926 pgspParserContext *ctx = (pgspParserContext *)state;
930 appendStringInfoString(ctx->dest, ctx->fname);
931 appendStringInfoString(ctx->dest, ":");
936 ctx->first = bms_add_member(ctx->first, ctx->level);
940 yaml_arrend(void *state)
942 pgspParserContext *ctx = (pgspParserContext *)state;
946 yaml_ofstart(void *state, char *fname, bool isnull)
949 pgspParserContext *ctx = (pgspParserContext *)state;
952 p = search_word_table(propfields, fname, ctx->mode);
956 (errmsg("Short JSON parser encoutered unknown field name: \"%s\".", fname),
957 errdetail_log("INPUT: \"%s\"", ctx->org_string)));
959 s = (p ? p->longname : fname);
961 if (!bms_is_member(ctx->level, ctx->first))
963 appendStringInfoString(ctx->dest, "\n");
964 appendStringInfoSpaces(ctx->dest, ctx->level * INDENT_STEP);
967 ctx->first = bms_del_member(ctx->first, ctx->level);
969 ctx->valconverter = NULL;
971 ctx->valconverter = (p ? p->converter : NULL);
975 yaml_aestart(void *state, bool isnull)
977 pgspParserContext *ctx = (pgspParserContext *)state;
979 appendStringInfoString(ctx->dest, "\n");
980 bms_del_member(ctx->first, ctx->level);
981 appendStringInfoSpaces(ctx->dest, ctx->level * INDENT_STEP);
982 appendStringInfoString(ctx->dest, "- ");
986 yaml_scalar(void *state, char *token, JsonTokenType tokentype)
988 pgspParserContext *ctx = (pgspParserContext *)state;
992 appendStringInfoString(ctx->dest, ctx->fname);
993 appendStringInfoString(ctx->dest, ": ");
997 json_scalar(state, token, tokentype);
999 ctx->last_elem_is_object = false;
1005 xml_objstart(void *state)
1007 pgspParserContext *ctx = (pgspParserContext *)state;
1010 ctx->first = bms_add_member(ctx->first, ctx->level);
1015 xml_objend(void *state)
1017 pgspParserContext *ctx = (pgspParserContext *)state;
1018 appendStringInfoChar(ctx->dest, '\n');
1019 appendStringInfoSpaces(ctx->dest, ctx->level * INDENT_STEP);
1022 ctx->first = bms_del_member(ctx->first, ctx->level);
1024 ctx->last_elem_is_object = true;
1028 xml_arrend(void *state)
1030 pgspParserContext *ctx = (pgspParserContext *)state;
1032 appendStringInfoChar(ctx->dest, '\n');
1033 appendStringInfoSpaces(ctx->dest, (ctx->level + 1) * INDENT_STEP);
1037 adjust_wbuf(pgspParserContext *ctx, int len)
1041 for (buflen = ctx->wbuflen ; len > buflen ; buflen *= 2);
1042 if (buflen > ctx->wbuflen)
1044 ctx->wbuf = (char *)palloc(buflen);
1045 ctx->wbuflen = buflen;
1050 hyphenate_words(pgspParserContext *ctx, char *src)
1054 adjust_wbuf(ctx, strlen(src) + 1);
1055 strcpy(ctx->wbuf, src);
1057 for (p = ctx->wbuf ; *p ; p++)
1058 if (*p == ' ') *p = '-';
1064 xml_ofstart(void *state, char *fname, bool isnull)
1067 pgspParserContext *ctx = (pgspParserContext *)state;
1070 p = search_word_table(propfields, fname, ctx->mode);
1074 (errmsg("Short JSON parser encoutered unknown field name: \"%s\".", fname),
1075 errdetail_log("INPUT: \"%s\"", ctx->org_string)));
1077 s = (p ? p->longname : fname);
1080 * save current process context
1081 * There's no problem if P_Plan appears recursively.
1083 if (p && (p->tag == P_Plan || p->tag == P_Triggers))
1084 ctx->section = p->tag;
1086 appendStringInfoChar(ctx->dest, '\n');
1087 appendStringInfoSpaces(ctx->dest, (ctx->level + 1) * INDENT_STEP);
1089 ctx->valconverter = NULL;
1091 appendStringInfoChar(ctx->dest, '<');
1092 appendStringInfoString(ctx->dest, escape_xml(hyphenate_words(ctx, s)));
1093 appendStringInfoChar(ctx->dest, '>');
1094 ctx->valconverter = (p ? p->converter : NULL);
1097 * If the object field name is Plan or Triggers, the value should be an
1098 * array and the items are tagged by other than "Item". "Item"s appear
1099 * only in Output field.
1101 if (p && (p->tag == P_Plans || p->tag == P_Triggers))
1102 ctx->not_item = bms_add_member(ctx->not_item, ctx->level + 1);
1104 ctx->not_item = bms_del_member(ctx->not_item, ctx->level + 1);
1108 xml_ofend(void *state, char *fname, bool isnull)
1110 pgspParserContext *ctx = (pgspParserContext *)state;
1114 p = search_word_table(propfields, fname, ctx->mode);
1115 s = (p ? p->longname : fname);
1117 appendStringInfoString(ctx->dest, "</");
1118 appendStringInfoString(ctx->dest, escape_xml(hyphenate_words(ctx, s)));
1119 appendStringInfoChar(ctx->dest, '>');
1123 xml_aestart(void *state, bool isnull)
1125 pgspParserContext *ctx = (pgspParserContext *)state;
1129 * The "Trigger" in "Triggers", "Plan" in "Plans" and "Item" nodes are
1130 * implicitly represented in JSON format. Restore them for XML format.
1134 if (bms_is_member(ctx->level, ctx->not_item))
1136 if (ctx->section == P_Plan)
1144 appendStringInfoChar(ctx->dest, '\n');
1145 appendStringInfoSpaces(ctx->dest, (ctx->level + 1) * INDENT_STEP);
1146 appendStringInfoString(ctx->dest, tag);
1150 xml_aeend(void *state, bool isnull)
1152 pgspParserContext *ctx = (pgspParserContext *)state;
1156 * The "Plan" in "Plans" or "Item" nodes are implicitly represented in
1157 * JSON format. Restore it for XML format.
1160 if (bms_is_member(ctx->level, ctx->not_item))
1162 if (ctx->section == P_Plan)
1169 appendStringInfoString(ctx->dest, tag);
1174 xml_scalar(void *state, char *token, JsonTokenType tokentype)
1176 pgspParserContext *ctx = (pgspParserContext *)state;
1177 const char *s = token;
1179 if (ctx->valconverter)
1180 s = ctx->valconverter(token, PGSP_JSON_XMLIZE);
1182 if (tokentype == JSON_TOKEN_STRING)
1185 appendStringInfoString(ctx->dest, s);
1186 ctx->last_elem_is_object = false;
1189 /********************************/
1191 init_parser_context(pgspParserContext *ctx, int mode,
1192 char *orgstr, char *buf, int buflen){
1193 memset(ctx, 0, sizeof(*ctx));
1194 ctx->dest = makeStringInfo();
1196 ctx->org_string = orgstr;
1198 ctx->wbuflen = buflen;
1202 * run_pg_parse_json:
1204 * Wrap pg_parse_json in order to restore InterruptHoldoffCount when parse
1207 * Returns true when parse completed. False for unexpected end of string.
1210 run_pg_parse_json(JsonLexContext *lex, JsonSemAction *sem)
1212 #if PG_VERSION_NUM >= 130000
1213 return pg_parse_json(lex, sem) == JSON_SUCCESS;
1215 MemoryContext ccxt = CurrentMemoryContext;
1216 uint32 saved_IntrHoldoffCount;
1219 * "ereport(ERROR.." occurs on error in pg_parse_json resets
1220 * InterruptHoldoffCount to zero, so we must save the value before calling
1221 * json parser to restore it on parse error. See errfinish().
1223 saved_IntrHoldoffCount = InterruptHoldoffCount;
1227 pg_parse_json(lex, sem);
1234 InterruptHoldoffCount = saved_IntrHoldoffCount;
1236 ecxt = MemoryContextSwitchTo(ccxt);
1237 errdata = CopyErrorData();
1239 if (errdata->sqlerrcode == ERRCODE_INVALID_TEXT_REPRESENTATION)
1246 MemoryContextSwitchTo(ecxt);
1257 init_json_lex_context(JsonLexContext *lex, char *json)
1259 lex->input = lex->token_terminator = lex->line_start = json;
1260 lex->line_number = 1;
1261 lex->input_length = strlen(json);
1262 lex->strval = makeStringInfo();
1266 init_json_semaction(JsonSemAction *sem, pgspParserContext *ctx)
1268 sem->semstate = (void*)ctx;
1269 sem->object_start = json_objstart;
1270 sem->object_end = json_objend;
1271 sem->array_start = json_arrstart;
1272 sem->array_end = json_arrend;
1273 sem->object_field_start = json_ofstart;
1274 sem->object_field_end = json_ofend;
1275 sem->array_element_start= json_aestart;
1276 sem->array_element_end = NULL;
1277 sem->scalar = json_scalar;
1281 pgsp_json_shorten(char *json)
1285 pgspParserContext ctx;
1287 init_json_lex_context(&lex, json);
1288 init_parser_context(&ctx, PGSP_JSON_SHORTEN, json, NULL, 0);
1289 init_json_semaction(&sem, &ctx);
1291 run_pg_parse_json(&lex, &sem);
1293 return ctx.dest->data;
1297 pgsp_json_normalize(char *json)
1301 pgspParserContext ctx;
1303 init_json_lex_context(&lex, json);
1304 init_parser_context(&ctx,PGSP_JSON_NORMALIZE, json, NULL, 0);
1305 init_json_semaction(&sem, &ctx);
1307 run_pg_parse_json(&lex, &sem);
1309 return ctx.dest->data;
1313 pgsp_json_inflate(char *json)
1317 pgspParserContext ctx;
1319 init_json_lex_context(&lex, json);
1320 init_parser_context(&ctx, PGSP_JSON_INFLATE, json, NULL, 0);
1321 init_json_semaction(&sem, &ctx);
1323 if (!run_pg_parse_json(&lex, &sem))
1325 if (ctx.dest->len > 0 &&
1326 ctx.dest->data[ctx.dest->len - 1] != '\n')
1327 appendStringInfoChar(ctx.dest, '\n');
1329 if (ctx.dest->len == 0)
1330 appendStringInfoString(ctx.dest, "<Input was not JSON>");
1332 appendStringInfoString(ctx.dest, "<truncated>");
1335 return ctx.dest->data;
1339 pgsp_json_yamlize(char *json)
1341 pgspParserContext ctx;
1345 init_json_lex_context(&lex, json);
1346 init_parser_context(&ctx, PGSP_JSON_YAMLIZE, json, NULL, 0);
1348 sem.semstate = (void*)&ctx;
1349 sem.object_start = yaml_objstart;
1350 sem.object_end = yaml_objend;
1351 sem.array_start = yaml_arrstart;
1352 sem.array_end = yaml_arrend;
1353 sem.object_field_start = yaml_ofstart;
1354 sem.object_field_end = NULL;
1355 sem.array_element_start= yaml_aestart;
1356 sem.array_element_end = NULL;
1357 sem.scalar = yaml_scalar;
1359 if (!run_pg_parse_json(&lex, &sem))
1361 if (ctx.dest->len > 0 &&
1362 ctx.dest->data[ctx.dest->len - 1] != '\n')
1363 appendStringInfoChar(ctx.dest, '\n');
1365 if (ctx.dest->len == 0)
1366 appendStringInfoString(ctx.dest, "<Input was not JSON>");
1368 appendStringInfoString(ctx.dest, "<truncated>");
1371 return ctx.dest->data;
1375 pgsp_json_xmlize(char *json)
1377 pgspParserContext ctx;
1383 init_json_lex_context(&lex, json);
1384 init_parser_context(&ctx, PGSP_JSON_XMLIZE, json, buf, sizeof(buf));
1386 sem.semstate = (void*)&ctx;
1387 sem.object_start = xml_objstart;
1388 sem.object_end = xml_objend;
1389 sem.array_start = NULL;
1390 sem.array_end = xml_arrend;
1391 sem.object_field_start = xml_ofstart;
1392 sem.object_field_end = xml_ofend;
1393 sem.array_element_start= xml_aestart;
1394 sem.array_element_end = xml_aeend;
1395 sem.scalar = xml_scalar;
1397 appendStringInfo(ctx.dest,
1398 "<explain xmlns=\"http://www.postgresql.org/2009/explain\">\n <Query>");
1399 start_len = ctx.dest->len;
1401 if (!run_pg_parse_json(&lex, &sem))
1403 if (ctx.dest->len > start_len &&
1404 ctx.dest->data[ctx.dest->len - 1] != '\n')
1405 appendStringInfoChar(ctx.dest, '\n');
1407 if (ctx.dest->len == start_len)
1409 resetStringInfo(ctx.dest);
1410 appendStringInfoString(ctx.dest, "<Input was not JSON>");
1413 appendStringInfoString(ctx.dest, "<truncated>");
1416 appendStringInfo(ctx.dest, "</Query>\n</explain>\n");
1418 return ctx.dest->data;