Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * ruleutils.c
4 : : * Functions to convert stored expressions/querytrees back to
5 : : * source text
6 : : *
7 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
8 : : * Portions Copyright (c) 1994, Regents of the University of California
9 : : *
10 : : *
11 : : * IDENTIFICATION
12 : : * src/backend/utils/adt/ruleutils.c
13 : : *
14 : : *-------------------------------------------------------------------------
15 : : */
16 : : #include "postgres.h"
17 : :
18 : : #include <ctype.h>
19 : : #include <unistd.h>
20 : : #include <fcntl.h>
21 : :
22 : : #include "access/amapi.h"
23 : : #include "access/htup_details.h"
24 : : #include "access/relation.h"
25 : : #include "access/table.h"
26 : : #include "catalog/pg_aggregate.h"
27 : : #include "catalog/pg_am.h"
28 : : #include "catalog/pg_authid.h"
29 : : #include "catalog/pg_collation.h"
30 : : #include "catalog/pg_constraint.h"
31 : : #include "catalog/pg_depend.h"
32 : : #include "catalog/pg_language.h"
33 : : #include "catalog/pg_opclass.h"
34 : : #include "catalog/pg_operator.h"
35 : : #include "catalog/pg_partitioned_table.h"
36 : : #include "catalog/pg_proc.h"
37 : : #include "catalog/pg_statistic_ext.h"
38 : : #include "catalog/pg_trigger.h"
39 : : #include "catalog/pg_type.h"
40 : : #include "commands/defrem.h"
41 : : #include "commands/tablespace.h"
42 : : #include "common/keywords.h"
43 : : #include "executor/spi.h"
44 : : #include "funcapi.h"
45 : : #include "mb/pg_wchar.h"
46 : : #include "miscadmin.h"
47 : : #include "nodes/makefuncs.h"
48 : : #include "nodes/nodeFuncs.h"
49 : : #include "nodes/pathnodes.h"
50 : : #include "optimizer/optimizer.h"
51 : : #include "parser/parse_agg.h"
52 : : #include "parser/parse_func.h"
53 : : #include "parser/parse_oper.h"
54 : : #include "parser/parse_relation.h"
55 : : #include "parser/parser.h"
56 : : #include "parser/parsetree.h"
57 : : #include "rewrite/rewriteHandler.h"
58 : : #include "rewrite/rewriteManip.h"
59 : : #include "rewrite/rewriteSupport.h"
60 : : #include "utils/array.h"
61 : : #include "utils/builtins.h"
62 : : #include "utils/fmgroids.h"
63 : : #include "utils/guc.h"
64 : : #include "utils/hsearch.h"
65 : : #include "utils/lsyscache.h"
66 : : #include "utils/partcache.h"
67 : : #include "utils/rel.h"
68 : : #include "utils/ruleutils.h"
69 : : #include "utils/snapmgr.h"
70 : : #include "utils/syscache.h"
71 : : #include "utils/typcache.h"
72 : : #include "utils/varlena.h"
73 : : #include "utils/xml.h"
74 : :
75 : : /* ----------
76 : : * Pretty formatting constants
77 : : * ----------
78 : : */
79 : :
80 : : /* Indent counts */
81 : : #define PRETTYINDENT_STD 8
82 : : #define PRETTYINDENT_JOIN 4
83 : : #define PRETTYINDENT_VAR 4
84 : :
85 : : #define PRETTYINDENT_LIMIT 40 /* wrap limit */
86 : :
87 : : /* Pretty flags */
88 : : #define PRETTYFLAG_PAREN 0x0001
89 : : #define PRETTYFLAG_INDENT 0x0002
90 : : #define PRETTYFLAG_SCHEMA 0x0004
91 : :
92 : : /* Standard conversion of a "bool pretty" option to detailed flags */
93 : : #define GET_PRETTY_FLAGS(pretty) \
94 : : ((pretty) ? (PRETTYFLAG_PAREN | PRETTYFLAG_INDENT | PRETTYFLAG_SCHEMA) \
95 : : : PRETTYFLAG_INDENT)
96 : :
97 : : /* Default line length for pretty-print wrapping: 0 means wrap always */
98 : : #define WRAP_COLUMN_DEFAULT 0
99 : :
100 : : /* macros to test if pretty action needed */
101 : : #define PRETTY_PAREN(context) ((context)->prettyFlags & PRETTYFLAG_PAREN)
102 : : #define PRETTY_INDENT(context) ((context)->prettyFlags & PRETTYFLAG_INDENT)
103 : : #define PRETTY_SCHEMA(context) ((context)->prettyFlags & PRETTYFLAG_SCHEMA)
104 : :
105 : :
106 : : /* ----------
107 : : * Local data types
108 : : * ----------
109 : : */
110 : :
111 : : /* Context info needed for invoking a recursive querytree display routine */
112 : : typedef struct
113 : : {
114 : : StringInfo buf; /* output buffer to append to */
115 : : List *namespaces; /* List of deparse_namespace nodes */
116 : : TupleDesc resultDesc; /* if top level of a view, the view's tupdesc */
117 : : List *targetList; /* Current query level's SELECT targetlist */
118 : : List *windowClause; /* Current query level's WINDOW clause */
119 : : int prettyFlags; /* enabling of pretty-print functions */
120 : : int wrapColumn; /* max line length, or -1 for no limit */
121 : : int indentLevel; /* current indent level for pretty-print */
122 : : bool varprefix; /* true to print prefixes on Vars */
123 : : bool colNamesVisible; /* do we care about output column names? */
124 : : bool inGroupBy; /* deparsing GROUP BY clause? */
125 : : bool varInOrderBy; /* deparsing simple Var in ORDER BY? */
126 : : Bitmapset *appendparents; /* if not null, map child Vars of these relids
127 : : * back to the parent rel */
128 : : } deparse_context;
129 : :
130 : : /*
131 : : * Each level of query context around a subtree needs a level of Var namespace.
132 : : * A Var having varlevelsup=N refers to the N'th item (counting from 0) in
133 : : * the current context's namespaces list.
134 : : *
135 : : * rtable is the list of actual RTEs from the Query or PlannedStmt.
136 : : * rtable_names holds the alias name to be used for each RTE (either a C
137 : : * string, or NULL for nameless RTEs such as unnamed joins).
138 : : * rtable_columns holds the column alias names to be used for each RTE.
139 : : *
140 : : * subplans is a list of Plan trees for SubPlans and CTEs (it's only used
141 : : * in the PlannedStmt case).
142 : : * ctes is a list of CommonTableExpr nodes (only used in the Query case).
143 : : * appendrels, if not null (it's only used in the PlannedStmt case), is an
144 : : * array of AppendRelInfo nodes, indexed by child relid. We use that to map
145 : : * child-table Vars to their inheritance parents.
146 : : *
147 : : * In some cases we need to make names of merged JOIN USING columns unique
148 : : * across the whole query, not only per-RTE. If so, unique_using is true
149 : : * and using_names is a list of C strings representing names already assigned
150 : : * to USING columns.
151 : : *
152 : : * When deparsing plan trees, there is always just a single item in the
153 : : * deparse_namespace list (since a plan tree never contains Vars with
154 : : * varlevelsup > 0). We store the Plan node that is the immediate
155 : : * parent of the expression to be deparsed, as well as a list of that
156 : : * Plan's ancestors. In addition, we store its outer and inner subplan nodes,
157 : : * as well as their targetlists, and the index tlist if the current plan node
158 : : * might contain INDEX_VAR Vars. (These fields could be derived on-the-fly
159 : : * from the current Plan node, but it seems notationally clearer to set them
160 : : * up as separate fields.)
161 : : */
162 : : typedef struct
163 : : {
164 : : List *rtable; /* List of RangeTblEntry nodes */
165 : : List *rtable_names; /* Parallel list of names for RTEs */
166 : : List *rtable_columns; /* Parallel list of deparse_columns structs */
167 : : List *subplans; /* List of Plan trees for SubPlans */
168 : : List *ctes; /* List of CommonTableExpr nodes */
169 : : AppendRelInfo **appendrels; /* Array of AppendRelInfo nodes, or NULL */
170 : : char *ret_old_alias; /* alias for OLD in RETURNING list */
171 : : char *ret_new_alias; /* alias for NEW in RETURNING list */
172 : : /* Workspace for column alias assignment: */
173 : : bool unique_using; /* Are we making USING names globally unique */
174 : : List *using_names; /* List of assigned names for USING columns */
175 : : /* Remaining fields are used only when deparsing a Plan tree: */
176 : : Plan *plan; /* immediate parent of current expression */
177 : : List *ancestors; /* ancestors of plan */
178 : : Plan *outer_plan; /* outer subnode, or NULL if none */
179 : : Plan *inner_plan; /* inner subnode, or NULL if none */
180 : : List *outer_tlist; /* referent for OUTER_VAR Vars */
181 : : List *inner_tlist; /* referent for INNER_VAR Vars */
182 : : List *index_tlist; /* referent for INDEX_VAR Vars */
183 : : /* Special namespace representing a function signature: */
184 : : char *funcname;
185 : : int numargs;
186 : : char **argnames;
187 : : } deparse_namespace;
188 : :
189 : : /*
190 : : * Per-relation data about column alias names.
191 : : *
192 : : * Selecting aliases is unreasonably complicated because of the need to dump
193 : : * rules/views whose underlying tables may have had columns added, deleted, or
194 : : * renamed since the query was parsed. We must nonetheless print the rule/view
195 : : * in a form that can be reloaded and will produce the same results as before.
196 : : *
197 : : * For each RTE used in the query, we must assign column aliases that are
198 : : * unique within that RTE. SQL does not require this of the original query,
199 : : * but due to factors such as *-expansion we need to be able to uniquely
200 : : * reference every column in a decompiled query. As long as we qualify all
201 : : * column references, per-RTE uniqueness is sufficient for that.
202 : : *
203 : : * However, we can't ensure per-column name uniqueness for unnamed join RTEs,
204 : : * since they just inherit column names from their input RTEs, and we can't
205 : : * rename the columns at the join level. Most of the time this isn't an issue
206 : : * because we don't need to reference the join's output columns as such; we
207 : : * can reference the input columns instead. That approach can fail for merged
208 : : * JOIN USING columns, however, so when we have one of those in an unnamed
209 : : * join, we have to make that column's alias globally unique across the whole
210 : : * query to ensure it can be referenced unambiguously.
211 : : *
212 : : * Another problem is that a JOIN USING clause requires the columns to be
213 : : * merged to have the same aliases in both input RTEs, and that no other
214 : : * columns in those RTEs or their children conflict with the USING names.
215 : : * To handle that, we do USING-column alias assignment in a recursive
216 : : * traversal of the query's jointree. When descending through a JOIN with
217 : : * USING, we preassign the USING column names to the child columns, overriding
218 : : * other rules for column alias assignment. We also mark each RTE with a list
219 : : * of all USING column names selected for joins containing that RTE, so that
220 : : * when we assign other columns' aliases later, we can avoid conflicts.
221 : : *
222 : : * Another problem is that if a JOIN's input tables have had columns added or
223 : : * deleted since the query was parsed, we must generate a column alias list
224 : : * for the join that matches the current set of input columns --- otherwise, a
225 : : * change in the number of columns in the left input would throw off matching
226 : : * of aliases to columns of the right input. Thus, positions in the printable
227 : : * column alias list are not necessarily one-for-one with varattnos of the
228 : : * JOIN, so we need a separate new_colnames[] array for printing purposes.
229 : : *
230 : : * Finally, when dealing with wide tables we risk O(N^2) costs in assigning
231 : : * non-duplicate column names. We ameliorate that by using a hash table that
232 : : * holds all the strings appearing in colnames, new_colnames, and parentUsing.
233 : : */
234 : : typedef struct
235 : : {
236 : : /*
237 : : * colnames is an array containing column aliases to use for columns that
238 : : * existed when the query was parsed. Dropped columns have NULL entries.
239 : : * This array can be directly indexed by varattno to get a Var's name.
240 : : *
241 : : * Non-NULL entries are guaranteed unique within the RTE, *except* when
242 : : * this is for an unnamed JOIN RTE. In that case we merely copy up names
243 : : * from the two input RTEs.
244 : : *
245 : : * During the recursive descent in set_using_names(), forcible assignment
246 : : * of a child RTE's column name is represented by pre-setting that element
247 : : * of the child's colnames array. So at that stage, NULL entries in this
248 : : * array just mean that no name has been preassigned, not necessarily that
249 : : * the column is dropped.
250 : : */
251 : : int num_cols; /* length of colnames[] array */
252 : : char **colnames; /* array of C strings and NULLs */
253 : :
254 : : /*
255 : : * new_colnames is an array containing column aliases to use for columns
256 : : * that would exist if the query was re-parsed against the current
257 : : * definitions of its base tables. This is what to print as the column
258 : : * alias list for the RTE. This array does not include dropped columns,
259 : : * but it will include columns added since original parsing. Indexes in
260 : : * it therefore have little to do with current varattno values. As above,
261 : : * entries are unique unless this is for an unnamed JOIN RTE. (In such an
262 : : * RTE, we never actually print this array, but we must compute it anyway
263 : : * for possible use in computing column names of upper joins.) The
264 : : * parallel array is_new_col marks which of these columns are new since
265 : : * original parsing. Entries with is_new_col false must match the
266 : : * non-NULL colnames entries one-for-one.
267 : : */
268 : : int num_new_cols; /* length of new_colnames[] array */
269 : : char **new_colnames; /* array of C strings */
270 : : bool *is_new_col; /* array of bool flags */
271 : :
272 : : /* This flag tells whether we should actually print a column alias list */
273 : : bool printaliases;
274 : :
275 : : /* This list has all names used as USING names in joins above this RTE */
276 : : List *parentUsing; /* names assigned to parent merged columns */
277 : :
278 : : /*
279 : : * If this struct is for a JOIN RTE, we fill these fields during the
280 : : * set_using_names() pass to describe its relationship to its child RTEs.
281 : : *
282 : : * leftattnos and rightattnos are arrays with one entry per existing
283 : : * output column of the join (hence, indexable by join varattno). For a
284 : : * simple reference to a column of the left child, leftattnos[i] is the
285 : : * child RTE's attno and rightattnos[i] is zero; and conversely for a
286 : : * column of the right child. But for merged columns produced by JOIN
287 : : * USING/NATURAL JOIN, both leftattnos[i] and rightattnos[i] are nonzero.
288 : : * Note that a simple reference might be to a child RTE column that's been
289 : : * dropped; but that's OK since the column could not be used in the query.
290 : : *
291 : : * If it's a JOIN USING, usingNames holds the alias names selected for the
292 : : * merged columns (these might be different from the original USING list,
293 : : * if we had to modify names to achieve uniqueness).
294 : : */
295 : : int leftrti; /* rangetable index of left child */
296 : : int rightrti; /* rangetable index of right child */
297 : : int *leftattnos; /* left-child varattnos of join cols, or 0 */
298 : : int *rightattnos; /* right-child varattnos of join cols, or 0 */
299 : : List *usingNames; /* names assigned to merged columns */
300 : :
301 : : /*
302 : : * Hash table holding copies of all the strings appearing in this struct's
303 : : * colnames, new_colnames, and parentUsing. We use a hash table only for
304 : : * sufficiently wide relations, and only during the colname-assignment
305 : : * functions set_relation_column_names and set_join_column_names;
306 : : * otherwise, names_hash is NULL.
307 : : */
308 : : HTAB *names_hash; /* entries are just strings */
309 : : } deparse_columns;
310 : :
311 : : /* This macro is analogous to rt_fetch(), but for deparse_columns structs */
312 : : #define deparse_columns_fetch(rangetable_index, dpns) \
313 : : ((deparse_columns *) list_nth((dpns)->rtable_columns, (rangetable_index)-1))
314 : :
315 : : /*
316 : : * Entry in set_rtable_names' hash table
317 : : */
318 : : typedef struct
319 : : {
320 : : char name[NAMEDATALEN]; /* Hash key --- must be first */
321 : : int counter; /* Largest addition used so far for name */
322 : : } NameHashEntry;
323 : :
324 : : /* Callback signature for resolve_special_varno() */
325 : : typedef void (*rsv_callback) (Node *node, deparse_context *context,
326 : : void *callback_arg);
327 : :
328 : :
329 : : /* ----------
330 : : * Global data
331 : : * ----------
332 : : */
333 : : static SPIPlanPtr plan_getrulebyoid = NULL;
334 : : static const char *const query_getrulebyoid = "SELECT * FROM pg_catalog.pg_rewrite WHERE oid = $1";
335 : : static SPIPlanPtr plan_getviewrule = NULL;
336 : : static const char *const query_getviewrule = "SELECT * FROM pg_catalog.pg_rewrite WHERE ev_class = $1 AND rulename = $2";
337 : :
338 : : /* GUC parameters */
339 : : bool quote_all_identifiers = false;
340 : :
341 : :
342 : : /* ----------
343 : : * Local functions
344 : : *
345 : : * Most of these functions used to use fixed-size buffers to build their
346 : : * results. Now, they take an (already initialized) StringInfo object
347 : : * as a parameter, and append their text output to its contents.
348 : : * ----------
349 : : */
350 : : static char *deparse_expression_pretty(Node *expr, List *dpcontext,
351 : : bool forceprefix, bool showimplicit,
352 : : int prettyFlags, int startIndent);
353 : : static char *pg_get_viewdef_worker(Oid viewoid,
354 : : int prettyFlags, int wrapColumn);
355 : : static char *pg_get_triggerdef_worker(Oid trigid, bool pretty);
356 : : static int decompile_column_index_array(Datum column_index_array, Oid relId,
357 : : bool withPeriod, StringInfo buf);
358 : : static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
359 : : static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
360 : : const Oid *excludeOps,
361 : : bool attrsOnly, bool keysOnly,
362 : : bool showTblSpc, bool inherits,
363 : : int prettyFlags, bool missing_ok);
364 : : static char *pg_get_statisticsobj_worker(Oid statextid, bool columns_only,
365 : : bool missing_ok);
366 : : static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags,
367 : : bool attrsOnly, bool missing_ok);
368 : : static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
369 : : int prettyFlags, bool missing_ok);
370 : : static text *pg_get_expr_worker(text *expr, Oid relid, int prettyFlags);
371 : : static int print_function_arguments(StringInfo buf, HeapTuple proctup,
372 : : bool print_table_args, bool print_defaults);
373 : : static void print_function_rettype(StringInfo buf, HeapTuple proctup);
374 : : static void print_function_trftypes(StringInfo buf, HeapTuple proctup);
375 : : static void print_function_sqlbody(StringInfo buf, HeapTuple proctup);
376 : : static void set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
377 : : Bitmapset *rels_used);
378 : : static void set_deparse_for_query(deparse_namespace *dpns, Query *query,
379 : : List *parent_namespaces);
380 : : static void set_simple_column_names(deparse_namespace *dpns);
381 : : static bool has_dangerous_join_using(deparse_namespace *dpns, Node *jtnode);
382 : : static void set_using_names(deparse_namespace *dpns, Node *jtnode,
383 : : List *parentUsing);
384 : : static void set_relation_column_names(deparse_namespace *dpns,
385 : : RangeTblEntry *rte,
386 : : deparse_columns *colinfo);
387 : : static void set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
388 : : deparse_columns *colinfo);
389 : : static bool colname_is_unique(const char *colname, deparse_namespace *dpns,
390 : : deparse_columns *colinfo);
391 : : static char *make_colname_unique(char *colname, deparse_namespace *dpns,
392 : : deparse_columns *colinfo);
393 : : static void expand_colnames_array_to(deparse_columns *colinfo, int n);
394 : : static void build_colinfo_names_hash(deparse_columns *colinfo);
395 : : static void add_to_names_hash(deparse_columns *colinfo, const char *name);
396 : : static void destroy_colinfo_names_hash(deparse_columns *colinfo);
397 : : static void identify_join_columns(JoinExpr *j, RangeTblEntry *jrte,
398 : : deparse_columns *colinfo);
399 : : static char *get_rtable_name(int rtindex, deparse_context *context);
400 : : static void set_deparse_plan(deparse_namespace *dpns, Plan *plan);
401 : : static Plan *find_recursive_union(deparse_namespace *dpns,
402 : : WorkTableScan *wtscan);
403 : : static void push_child_plan(deparse_namespace *dpns, Plan *plan,
404 : : deparse_namespace *save_dpns);
405 : : static void pop_child_plan(deparse_namespace *dpns,
406 : : deparse_namespace *save_dpns);
407 : : static void push_ancestor_plan(deparse_namespace *dpns, ListCell *ancestor_cell,
408 : : deparse_namespace *save_dpns);
409 : : static void pop_ancestor_plan(deparse_namespace *dpns,
410 : : deparse_namespace *save_dpns);
411 : : static void make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
412 : : int prettyFlags);
413 : : static void make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
414 : : int prettyFlags, int wrapColumn);
415 : : static void get_query_def(Query *query, StringInfo buf, List *parentnamespace,
416 : : TupleDesc resultDesc, bool colNamesVisible,
417 : : int prettyFlags, int wrapColumn, int startIndent);
418 : : static void get_values_def(List *values_lists, deparse_context *context);
419 : : static void get_with_clause(Query *query, deparse_context *context);
420 : : static void get_select_query_def(Query *query, deparse_context *context);
421 : : static void get_insert_query_def(Query *query, deparse_context *context);
422 : : static void get_update_query_def(Query *query, deparse_context *context);
423 : : static void get_update_query_targetlist_def(Query *query, List *targetList,
424 : : deparse_context *context,
425 : : RangeTblEntry *rte);
426 : : static void get_delete_query_def(Query *query, deparse_context *context);
427 : : static void get_merge_query_def(Query *query, deparse_context *context);
428 : : static void get_utility_query_def(Query *query, deparse_context *context);
429 : : static char *get_lock_clause_strength(LockClauseStrength strength);
430 : : static void get_basic_select_query(Query *query, deparse_context *context);
431 : : static void get_target_list(List *targetList, deparse_context *context);
432 : : static void get_returning_clause(Query *query, deparse_context *context);
433 : : static void get_setop_query(Node *setOp, Query *query,
434 : : deparse_context *context);
435 : : static Node *get_rule_sortgroupclause(Index ref, List *tlist,
436 : : bool force_colno,
437 : : deparse_context *context);
438 : : static void get_rule_groupingset(GroupingSet *gset, List *targetlist,
439 : : bool omit_parens, deparse_context *context);
440 : : static void get_rule_orderby(List *orderList, List *targetList,
441 : : bool force_colno, deparse_context *context);
442 : : static void get_rule_windowclause(Query *query, deparse_context *context);
443 : : static void get_rule_windowspec(WindowClause *wc, List *targetList,
444 : : deparse_context *context);
445 : : static void get_window_frame_options(int frameOptions,
446 : : Node *startOffset, Node *endOffset,
447 : : deparse_context *context);
448 : : static char *get_variable(Var *var, int levelsup, bool istoplevel,
449 : : deparse_context *context);
450 : : static void get_special_variable(Node *node, deparse_context *context,
451 : : void *callback_arg);
452 : : static void resolve_special_varno(Node *node, deparse_context *context,
453 : : rsv_callback callback, void *callback_arg);
454 : : static Node *find_param_referent(Param *param, deparse_context *context,
455 : : deparse_namespace **dpns_p, ListCell **ancestor_cell_p);
456 : : static SubPlan *find_param_generator(Param *param, deparse_context *context,
457 : : int *column_p);
458 : : static SubPlan *find_param_generator_initplan(Param *param, Plan *plan,
459 : : int *column_p);
460 : : static void get_parameter(Param *param, deparse_context *context);
461 : : static const char *get_simple_binary_op_name(OpExpr *expr);
462 : : static bool isSimpleNode(Node *node, Node *parentNode, int prettyFlags);
463 : : static void appendContextKeyword(deparse_context *context, const char *str,
464 : : int indentBefore, int indentAfter, int indentPlus);
465 : : static void removeStringInfoSpaces(StringInfo str);
466 : : static void get_rule_expr(Node *node, deparse_context *context,
467 : : bool showimplicit);
468 : : static void get_rule_expr_toplevel(Node *node, deparse_context *context,
469 : : bool showimplicit);
470 : : static void get_rule_list_toplevel(List *lst, deparse_context *context,
471 : : bool showimplicit);
472 : : static void get_rule_expr_funccall(Node *node, deparse_context *context,
473 : : bool showimplicit);
474 : : static bool looks_like_function(Node *node);
475 : : static void get_oper_expr(OpExpr *expr, deparse_context *context);
476 : : static void get_func_expr(FuncExpr *expr, deparse_context *context,
477 : : bool showimplicit);
478 : : static void get_agg_expr(Aggref *aggref, deparse_context *context,
479 : : Aggref *original_aggref);
480 : : static void get_agg_expr_helper(Aggref *aggref, deparse_context *context,
481 : : Aggref *original_aggref, const char *funcname,
482 : : const char *options, bool is_json_objectagg);
483 : : static void get_agg_combine_expr(Node *node, deparse_context *context,
484 : : void *callback_arg);
485 : : static void get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context);
486 : : static void get_windowfunc_expr_helper(WindowFunc *wfunc, deparse_context *context,
487 : : const char *funcname, const char *options,
488 : : bool is_json_objectagg);
489 : : static bool get_func_sql_syntax(FuncExpr *expr, deparse_context *context);
490 : : static void get_coercion_expr(Node *arg, deparse_context *context,
491 : : Oid resulttype, int32 resulttypmod,
492 : : Node *parentNode);
493 : : static void get_const_expr(Const *constval, deparse_context *context,
494 : : int showtype);
495 : : static void get_const_collation(Const *constval, deparse_context *context);
496 : : static void get_json_format(JsonFormat *format, StringInfo buf);
497 : : static void get_json_returning(JsonReturning *returning, StringInfo buf,
498 : : bool json_format_by_default);
499 : : static void get_json_constructor(JsonConstructorExpr *ctor,
500 : : deparse_context *context, bool showimplicit);
501 : : static void get_json_constructor_options(JsonConstructorExpr *ctor,
502 : : StringInfo buf);
503 : : static void get_json_agg_constructor(JsonConstructorExpr *ctor,
504 : : deparse_context *context,
505 : : const char *funcname,
506 : : bool is_json_objectagg);
507 : : static void get_json_agg_constructor_expr(Node *node, deparse_context *context,
508 : : void *callback_arg);
509 : : static void simple_quote_literal(StringInfo buf, const char *val);
510 : : static void get_sublink_expr(SubLink *sublink, deparse_context *context);
511 : : static void get_tablefunc(TableFunc *tf, deparse_context *context,
512 : : bool showimplicit);
513 : : static void get_from_clause(Query *query, const char *prefix,
514 : : deparse_context *context);
515 : : static void get_from_clause_item(Node *jtnode, Query *query,
516 : : deparse_context *context);
517 : : static void get_rte_alias(RangeTblEntry *rte, int varno, bool use_as,
518 : : deparse_context *context);
519 : : static void get_column_alias_list(deparse_columns *colinfo,
520 : : deparse_context *context);
521 : : static void get_from_clause_coldeflist(RangeTblFunction *rtfunc,
522 : : deparse_columns *colinfo,
523 : : deparse_context *context);
524 : : static void get_tablesample_def(TableSampleClause *tablesample,
525 : : deparse_context *context);
526 : : static void get_opclass_name(Oid opclass, Oid actual_datatype,
527 : : StringInfo buf);
528 : : static Node *processIndirection(Node *node, deparse_context *context);
529 : : static void printSubscripts(SubscriptingRef *sbsref, deparse_context *context);
530 : : static char *get_relation_name(Oid relid);
531 : : static char *generate_relation_name(Oid relid, List *namespaces);
532 : : static char *generate_qualified_relation_name(Oid relid);
533 : : static char *generate_function_name(Oid funcid, int nargs,
534 : : List *argnames, Oid *argtypes,
535 : : bool has_variadic, bool *use_variadic_p,
536 : : bool inGroupBy);
537 : : static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2);
538 : : static void add_cast_to(StringInfo buf, Oid typid);
539 : : static char *generate_qualified_type_name(Oid typid);
540 : : static text *string_to_text(char *str);
541 : : static char *flatten_reloptions(Oid relid);
542 : : void get_reloptions(StringInfo buf, Datum reloptions);
543 : : static void get_json_path_spec(Node *path_spec, deparse_context *context,
544 : : bool showimplicit);
545 : : static void get_json_table_columns(TableFunc *tf, JsonTablePathScan *scan,
546 : : deparse_context *context,
547 : : bool showimplicit);
548 : : static void get_json_table_nested_columns(TableFunc *tf, JsonTablePlan *plan,
549 : : deparse_context *context,
550 : : bool showimplicit,
551 : : bool needcomma);
552 : :
553 : : #define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
554 : :
555 : :
556 : : /* ----------
557 : : * pg_get_ruledef - Do it all and return a text
558 : : * that could be used as a statement
559 : : * to recreate the rule
560 : : * ----------
561 : : */
562 : : Datum
9618 tgl@sss.pgh.pa.us 563 :CBC 237 : pg_get_ruledef(PG_FUNCTION_ARGS)
564 : : {
8967 565 : 237 : Oid ruleoid = PG_GETARG_OID(0);
566 : : int prettyFlags;
567 : : char *res;
568 : :
5023 569 : 237 : prettyFlags = PRETTYFLAG_INDENT;
570 : :
3754 rhaas@postgresql.org 571 : 237 : res = pg_get_ruledef_worker(ruleoid, prettyFlags);
572 : :
573 [ + + ]: 237 : if (res == NULL)
574 : 4 : PG_RETURN_NULL();
575 : :
576 : 233 : PG_RETURN_TEXT_P(string_to_text(res));
577 : : }
578 : :
579 : :
580 : : Datum
8499 tgl@sss.pgh.pa.us 581 : 76 : pg_get_ruledef_ext(PG_FUNCTION_ARGS)
582 : : {
583 : 76 : Oid ruleoid = PG_GETARG_OID(0);
584 : 76 : bool pretty = PG_GETARG_BOOL(1);
585 : : int prettyFlags;
586 : : char *res;
587 : :
1683 588 [ + - ]: 76 : prettyFlags = GET_PRETTY_FLAGS(pretty);
589 : :
3754 rhaas@postgresql.org 590 : 76 : res = pg_get_ruledef_worker(ruleoid, prettyFlags);
591 : :
592 [ - + ]: 76 : if (res == NULL)
3754 rhaas@postgresql.org 593 :UBC 0 : PG_RETURN_NULL();
594 : :
3754 rhaas@postgresql.org 595 :CBC 76 : PG_RETURN_TEXT_P(string_to_text(res));
596 : : }
597 : :
598 : :
599 : : static char *
8499 tgl@sss.pgh.pa.us 600 : 313 : pg_get_ruledef_worker(Oid ruleoid, int prettyFlags)
601 : : {
602 : : Datum args[1];
603 : : char nulls[1];
604 : : int spirc;
605 : : HeapTuple ruletup;
606 : : TupleDesc rulettc;
607 : : StringInfoData buf;
608 : :
609 : : /*
610 : : * Do this first so that string is alloc'd in outer context not SPI's.
611 : : */
8219 612 : 313 : initStringInfo(&buf);
613 : :
614 : : /*
615 : : * Connect to SPI manager
616 : : */
787 617 : 313 : SPI_connect();
618 : :
619 : : /*
620 : : * On the first call prepare the plan to lookup pg_rewrite. We read
621 : : * pg_rewrite over the SPI manager instead of using the syscache to be
622 : : * checked for read access on pg_rewrite.
623 : : */
8967 624 [ + + ]: 313 : if (plan_getrulebyoid == NULL)
625 : : {
626 : : Oid argtypes[1];
627 : : SPIPlanPtr plan;
628 : :
629 : 24 : argtypes[0] = OIDOID;
630 : 24 : plan = SPI_prepare(query_getrulebyoid, 1, argtypes);
10292 bruce@momjian.us 631 [ - + ]: 24 : if (plan == NULL)
8502 tgl@sss.pgh.pa.us 632 [ # # ]:UBC 0 : elog(ERROR, "SPI_prepare failed for \"%s\"", query_getrulebyoid);
5529 tgl@sss.pgh.pa.us 633 :CBC 24 : SPI_keepplan(plan);
634 : 24 : plan_getrulebyoid = plan;
635 : : }
636 : :
637 : : /*
638 : : * Get the pg_rewrite tuple for this rule
639 : : */
8967 640 : 313 : args[0] = ObjectIdGetDatum(ruleoid);
641 : 313 : nulls[0] = ' ';
4730 peter_e@gmx.net 642 : 313 : spirc = SPI_execute_plan(plan_getrulebyoid, args, nulls, true, 0);
10292 bruce@momjian.us 643 [ - + ]: 313 : if (spirc != SPI_OK_SELECT)
8502 tgl@sss.pgh.pa.us 644 [ # # ]:UBC 0 : elog(ERROR, "failed to get pg_rewrite tuple for rule %u", ruleoid);
10292 bruce@momjian.us 645 [ + + ]:CBC 313 : if (SPI_processed != 1)
646 : : {
647 : : /*
648 : : * There is no tuple data available here, just keep the output buffer
649 : : * empty.
650 : : */
651 : : }
652 : : else
653 : : {
654 : : /*
655 : : * Get the rule's definition and put it into executor's memory
656 : : */
8219 tgl@sss.pgh.pa.us 657 : 309 : ruletup = SPI_tuptable->vals[0];
658 : 309 : rulettc = SPI_tuptable->tupdesc;
659 : 309 : make_ruledef(&buf, ruletup, rulettc, prettyFlags);
660 : : }
661 : :
662 : : /*
663 : : * Disconnect from SPI manager
664 : : */
10292 bruce@momjian.us 665 [ - + ]: 313 : if (SPI_finish() != SPI_OK_FINISH)
8502 tgl@sss.pgh.pa.us 666 [ # # ]:UBC 0 : elog(ERROR, "SPI_finish failed");
667 : :
3754 rhaas@postgresql.org 668 [ + + ]:CBC 313 : if (buf.len == 0)
669 : 4 : return NULL;
670 : :
8219 tgl@sss.pgh.pa.us 671 : 309 : return buf.data;
672 : : }
673 : :
674 : :
675 : : /* ----------
676 : : * pg_get_viewdef - Mainly the same thing, but we
677 : : * only return the SELECT part of a view
678 : : * ----------
679 : : */
680 : : Datum
9618 681 : 1418 : pg_get_viewdef(PG_FUNCTION_ARGS)
682 : : {
683 : : /* By OID */
8967 684 : 1418 : Oid viewoid = PG_GETARG_OID(0);
685 : : int prettyFlags;
686 : : char *res;
687 : :
5023 688 : 1418 : prettyFlags = PRETTYFLAG_INDENT;
689 : :
3754 rhaas@postgresql.org 690 : 1418 : res = pg_get_viewdef_worker(viewoid, prettyFlags, WRAP_COLUMN_DEFAULT);
691 : :
692 [ + + ]: 1418 : if (res == NULL)
693 : 4 : PG_RETURN_NULL();
694 : :
695 : 1414 : PG_RETURN_TEXT_P(string_to_text(res));
696 : : }
697 : :
698 : :
699 : : Datum
8499 tgl@sss.pgh.pa.us 700 : 407 : pg_get_viewdef_ext(PG_FUNCTION_ARGS)
701 : : {
702 : : /* By OID */
703 : 407 : Oid viewoid = PG_GETARG_OID(0);
704 : 407 : bool pretty = PG_GETARG_BOOL(1);
705 : : int prettyFlags;
706 : : char *res;
707 : :
1683 708 [ + - ]: 407 : prettyFlags = GET_PRETTY_FLAGS(pretty);
709 : :
3754 rhaas@postgresql.org 710 : 407 : res = pg_get_viewdef_worker(viewoid, prettyFlags, WRAP_COLUMN_DEFAULT);
711 : :
712 [ - + ]: 407 : if (res == NULL)
3754 rhaas@postgresql.org 713 :UBC 0 : PG_RETURN_NULL();
714 : :
3754 rhaas@postgresql.org 715 :CBC 407 : PG_RETURN_TEXT_P(string_to_text(res));
716 : : }
717 : :
718 : : Datum
5373 andrew@dunslane.net 719 : 4 : pg_get_viewdef_wrap(PG_FUNCTION_ARGS)
720 : : {
721 : : /* By OID */
722 : 4 : Oid viewoid = PG_GETARG_OID(0);
5261 bruce@momjian.us 723 : 4 : int wrap = PG_GETARG_INT32(1);
724 : : int prettyFlags;
725 : : char *res;
726 : :
727 : : /* calling this implies we want pretty printing */
1683 tgl@sss.pgh.pa.us 728 : 4 : prettyFlags = GET_PRETTY_FLAGS(true);
729 : :
3754 rhaas@postgresql.org 730 : 4 : res = pg_get_viewdef_worker(viewoid, prettyFlags, wrap);
731 : :
732 [ - + ]: 4 : if (res == NULL)
3754 rhaas@postgresql.org 733 :UBC 0 : PG_RETURN_NULL();
734 : :
3754 rhaas@postgresql.org 735 :CBC 4 : PG_RETURN_TEXT_P(string_to_text(res));
736 : : }
737 : :
738 : : Datum
8967 tgl@sss.pgh.pa.us 739 : 52 : pg_get_viewdef_name(PG_FUNCTION_ARGS)
740 : : {
741 : : /* By qualified name */
3525 noah@leadboat.com 742 : 52 : text *viewname = PG_GETARG_TEXT_PP(0);
743 : : int prettyFlags;
744 : : RangeVar *viewrel;
745 : : Oid viewoid;
746 : : char *res;
747 : :
5023 tgl@sss.pgh.pa.us 748 : 52 : prettyFlags = PRETTYFLAG_INDENT;
749 : :
750 : : /* Look up view name. Can't lock it - we might not have privileges. */
7832 neilc@samurai.com 751 : 52 : viewrel = makeRangeVarFromNameList(textToQualifiedNameList(viewname));
5454 rhaas@postgresql.org 752 : 52 : viewoid = RangeVarGetRelid(viewrel, NoLock, false);
753 : :
3754 754 : 52 : res = pg_get_viewdef_worker(viewoid, prettyFlags, WRAP_COLUMN_DEFAULT);
755 : :
756 [ - + ]: 52 : if (res == NULL)
3754 rhaas@postgresql.org 757 :UBC 0 : PG_RETURN_NULL();
758 : :
3754 rhaas@postgresql.org 759 :CBC 52 : PG_RETURN_TEXT_P(string_to_text(res));
760 : : }
761 : :
762 : :
763 : : Datum
8499 tgl@sss.pgh.pa.us 764 : 272 : pg_get_viewdef_name_ext(PG_FUNCTION_ARGS)
765 : : {
766 : : /* By qualified name */
3525 noah@leadboat.com 767 : 272 : text *viewname = PG_GETARG_TEXT_PP(0);
8499 tgl@sss.pgh.pa.us 768 : 272 : bool pretty = PG_GETARG_BOOL(1);
769 : : int prettyFlags;
770 : : RangeVar *viewrel;
771 : : Oid viewoid;
772 : : char *res;
773 : :
1683 774 [ + - ]: 272 : prettyFlags = GET_PRETTY_FLAGS(pretty);
775 : :
776 : : /* Look up view name. Can't lock it - we might not have privileges. */
7832 neilc@samurai.com 777 : 272 : viewrel = makeRangeVarFromNameList(textToQualifiedNameList(viewname));
5454 rhaas@postgresql.org 778 : 272 : viewoid = RangeVarGetRelid(viewrel, NoLock, false);
779 : :
3742 tgl@sss.pgh.pa.us 780 : 272 : res = pg_get_viewdef_worker(viewoid, prettyFlags, WRAP_COLUMN_DEFAULT);
781 : :
782 [ - + ]: 272 : if (res == NULL)
3742 tgl@sss.pgh.pa.us 783 :UBC 0 : PG_RETURN_NULL();
784 : :
3742 tgl@sss.pgh.pa.us 785 :CBC 272 : PG_RETURN_TEXT_P(string_to_text(res));
786 : : }
787 : :
788 : : /*
789 : : * Common code for by-OID and by-name variants of pg_get_viewdef
790 : : */
791 : : static char *
5064 792 : 2153 : pg_get_viewdef_worker(Oid viewoid, int prettyFlags, int wrapColumn)
793 : : {
794 : : Datum args[2];
795 : : char nulls[2];
796 : : int spirc;
797 : : HeapTuple ruletup;
798 : : TupleDesc rulettc;
799 : : StringInfoData buf;
800 : :
801 : : /*
802 : : * Do this first so that string is alloc'd in outer context not SPI's.
803 : : */
8219 804 : 2153 : initStringInfo(&buf);
805 : :
806 : : /*
807 : : * Connect to SPI manager
808 : : */
787 809 : 2153 : SPI_connect();
810 : :
811 : : /*
812 : : * On the first call prepare the plan to lookup pg_rewrite. We read
813 : : * pg_rewrite over the SPI manager instead of using the syscache to be
814 : : * checked for read access on pg_rewrite.
815 : : */
8967 816 [ + + ]: 2153 : if (plan_getviewrule == NULL)
817 : : {
818 : : Oid argtypes[2];
819 : : SPIPlanPtr plan;
820 : :
821 : 151 : argtypes[0] = OIDOID;
822 : 151 : argtypes[1] = NAMEOID;
823 : 151 : plan = SPI_prepare(query_getviewrule, 2, argtypes);
10292 bruce@momjian.us 824 [ - + ]: 151 : if (plan == NULL)
8502 tgl@sss.pgh.pa.us 825 [ # # ]:UBC 0 : elog(ERROR, "SPI_prepare failed for \"%s\"", query_getviewrule);
5529 tgl@sss.pgh.pa.us 826 :CBC 151 : SPI_keepplan(plan);
827 : 151 : plan_getviewrule = plan;
828 : : }
829 : :
830 : : /*
831 : : * Get the pg_rewrite tuple for the view's SELECT rule
832 : : */
8967 833 : 2153 : args[0] = ObjectIdGetDatum(viewoid);
4730 peter_e@gmx.net 834 : 2153 : args[1] = DirectFunctionCall1(namein, CStringGetDatum(ViewSelectRuleName));
10292 bruce@momjian.us 835 : 2153 : nulls[0] = ' ';
8967 tgl@sss.pgh.pa.us 836 : 2153 : nulls[1] = ' ';
4730 peter_e@gmx.net 837 : 2153 : spirc = SPI_execute_plan(plan_getviewrule, args, nulls, true, 0);
10292 bruce@momjian.us 838 [ - + ]: 2153 : if (spirc != SPI_OK_SELECT)
8952 tgl@sss.pgh.pa.us 839 [ # # ]:UBC 0 : elog(ERROR, "failed to get pg_rewrite tuple for view %u", viewoid);
10292 bruce@momjian.us 840 [ + + ]:CBC 2153 : if (SPI_processed != 1)
841 : : {
842 : : /*
843 : : * There is no tuple data available here, just keep the output buffer
844 : : * empty.
845 : : */
846 : : }
847 : : else
848 : : {
849 : : /*
850 : : * Get the rule's definition and put it into executor's memory
851 : : */
852 : 2149 : ruletup = SPI_tuptable->vals[0];
853 : 2149 : rulettc = SPI_tuptable->tupdesc;
5064 tgl@sss.pgh.pa.us 854 : 2149 : make_viewdef(&buf, ruletup, rulettc, prettyFlags, wrapColumn);
855 : : }
856 : :
857 : : /*
858 : : * Disconnect from SPI manager
859 : : */
10292 bruce@momjian.us 860 [ - + ]: 2153 : if (SPI_finish() != SPI_OK_FINISH)
8502 tgl@sss.pgh.pa.us 861 [ # # ]:UBC 0 : elog(ERROR, "SPI_finish failed");
862 : :
3754 rhaas@postgresql.org 863 [ + + ]:CBC 2153 : if (buf.len == 0)
864 : 4 : return NULL;
865 : :
8219 tgl@sss.pgh.pa.us 866 : 2149 : return buf.data;
867 : : }
868 : :
869 : : /* ----------
870 : : * pg_get_triggerdef - Get the definition of a trigger
871 : : * ----------
872 : : */
873 : : Datum
8631 bruce@momjian.us 874 : 96 : pg_get_triggerdef(PG_FUNCTION_ARGS)
875 : : {
876 : 96 : Oid trigid = PG_GETARG_OID(0);
877 : : char *res;
878 : :
3754 rhaas@postgresql.org 879 : 96 : res = pg_get_triggerdef_worker(trigid, false);
880 : :
881 [ + + ]: 96 : if (res == NULL)
882 : 4 : PG_RETURN_NULL();
883 : :
884 : 92 : PG_RETURN_TEXT_P(string_to_text(res));
885 : : }
886 : :
887 : : Datum
6236 peter_e@gmx.net 888 : 631 : pg_get_triggerdef_ext(PG_FUNCTION_ARGS)
889 : : {
890 : 631 : Oid trigid = PG_GETARG_OID(0);
891 : 631 : bool pretty = PG_GETARG_BOOL(1);
892 : : char *res;
893 : :
3754 rhaas@postgresql.org 894 : 631 : res = pg_get_triggerdef_worker(trigid, pretty);
895 : :
896 [ - + ]: 631 : if (res == NULL)
3754 rhaas@postgresql.org 897 :UBC 0 : PG_RETURN_NULL();
898 : :
3754 rhaas@postgresql.org 899 :CBC 631 : PG_RETURN_TEXT_P(string_to_text(res));
900 : : }
901 : :
902 : : static char *
6236 peter_e@gmx.net 903 : 727 : pg_get_triggerdef_worker(Oid trigid, bool pretty)
904 : : {
905 : : HeapTuple ht_trig;
906 : : Form_pg_trigger trigrec;
907 : : StringInfoData buf;
908 : : Relation tgrel;
909 : : ScanKeyData skey[1];
910 : : SysScanDesc tgscan;
8570 tgl@sss.pgh.pa.us 911 : 727 : int findx = 0;
912 : : char *tgname;
913 : : char *tgoldtable;
914 : : char *tgnewtable;
915 : : Datum value;
916 : : bool isnull;
917 : :
918 : : /*
919 : : * Fetch the pg_trigger tuple by the Oid of the trigger
920 : : */
2845 andres@anarazel.de 921 : 727 : tgrel = table_open(TriggerRelationId, AccessShareLock);
922 : :
8394 tgl@sss.pgh.pa.us 923 : 727 : ScanKeyInit(&skey[0],
924 : : Anum_pg_trigger_oid,
925 : : BTEqualStrategyNumber, F_OIDEQ,
926 : : ObjectIdGetDatum(trigid));
927 : :
7875 928 : 727 : tgscan = systable_beginscan(tgrel, TriggerOidIndexId, true,
929 : : NULL, 1, skey);
930 : :
8570 931 : 727 : ht_trig = systable_getnext(tgscan);
932 : :
933 [ + + ]: 727 : if (!HeapTupleIsValid(ht_trig))
934 : : {
3754 rhaas@postgresql.org 935 : 4 : systable_endscan(tgscan);
2845 andres@anarazel.de 936 : 4 : table_close(tgrel, AccessShareLock);
3754 rhaas@postgresql.org 937 : 4 : return NULL;
938 : : }
939 : :
8631 bruce@momjian.us 940 : 723 : trigrec = (Form_pg_trigger) GETSTRUCT(ht_trig);
941 : :
942 : : /*
943 : : * Start the trigger definition. Note that the trigger's name should never
944 : : * be schema-qualified, but the trigger rel's name may be.
945 : : */
946 : 723 : initStringInfo(&buf);
947 : :
948 : 723 : tgname = NameStr(trigrec->tgname);
6132 itagaki.takahiro@gma 949 : 1446 : appendStringInfo(&buf, "CREATE %sTRIGGER %s ",
6136 tgl@sss.pgh.pa.us 950 [ - + ]: 723 : OidIsValid(trigrec->tgconstraint) ? "CONSTRAINT " : "",
951 : : quote_identifier(tgname));
952 : :
8631 bruce@momjian.us 953 [ + + ]: 723 : if (TRIGGER_FOR_BEFORE(trigrec->tgtype))
4753 rhaas@postgresql.org 954 : 263 : appendStringInfoString(&buf, "BEFORE");
5870 tgl@sss.pgh.pa.us 955 [ + + ]: 460 : else if (TRIGGER_FOR_AFTER(trigrec->tgtype))
4753 rhaas@postgresql.org 956 : 444 : appendStringInfoString(&buf, "AFTER");
5870 tgl@sss.pgh.pa.us 957 [ + - ]: 16 : else if (TRIGGER_FOR_INSTEAD(trigrec->tgtype))
4753 rhaas@postgresql.org 958 : 16 : appendStringInfoString(&buf, "INSTEAD OF");
959 : : else
5870 tgl@sss.pgh.pa.us 960 [ # # ]:UBC 0 : elog(ERROR, "unexpected tgtype value: %d", trigrec->tgtype);
961 : :
8631 bruce@momjian.us 962 [ + + ]:CBC 723 : if (TRIGGER_FOR_INSERT(trigrec->tgtype))
963 : : {
4753 rhaas@postgresql.org 964 : 488 : appendStringInfoString(&buf, " INSERT");
8631 bruce@momjian.us 965 : 488 : findx++;
966 : : }
967 [ + + ]: 723 : if (TRIGGER_FOR_DELETE(trigrec->tgtype))
968 : : {
969 [ + + ]: 113 : if (findx > 0)
4753 rhaas@postgresql.org 970 : 45 : appendStringInfoString(&buf, " OR DELETE");
971 : : else
972 : 68 : appendStringInfoString(&buf, " DELETE");
8631 bruce@momjian.us 973 : 113 : findx++;
974 : : }
975 [ + + ]: 723 : if (TRIGGER_FOR_UPDATE(trigrec->tgtype))
976 : : {
977 [ + + ]: 332 : if (findx > 0)
4753 rhaas@postgresql.org 978 : 165 : appendStringInfoString(&buf, " OR UPDATE");
979 : : else
980 : 167 : appendStringInfoString(&buf, " UPDATE");
6194 tgl@sss.pgh.pa.us 981 : 332 : findx++;
982 : : /* tgattr is first var-width field, so OK to access directly */
6231 983 [ + + ]: 332 : if (trigrec->tgattr.dim1 > 0)
984 : : {
985 : : int i;
986 : :
987 : 44 : appendStringInfoString(&buf, " OF ");
988 [ + + ]: 97 : for (i = 0; i < trigrec->tgattr.dim1; i++)
989 : : {
990 : : char *attname;
991 : :
992 [ + + ]: 53 : if (i > 0)
993 : 9 : appendStringInfoString(&buf, ", ");
3188 alvherre@alvh.no-ip. 994 : 53 : attname = get_attname(trigrec->tgrelid,
995 : 53 : trigrec->tgattr.values[i], false);
6231 tgl@sss.pgh.pa.us 996 : 53 : appendStringInfoString(&buf, quote_identifier(attname));
997 : : }
998 : : }
999 : : }
6796 1000 [ - + ]: 723 : if (TRIGGER_FOR_TRUNCATE(trigrec->tgtype))
1001 : : {
6796 tgl@sss.pgh.pa.us 1002 [ # # ]:UBC 0 : if (findx > 0)
4753 rhaas@postgresql.org 1003 : 0 : appendStringInfoString(&buf, " OR TRUNCATE");
1004 : : else
1005 : 0 : appendStringInfoString(&buf, " TRUNCATE");
6194 tgl@sss.pgh.pa.us 1006 : 0 : findx++;
1007 : : }
1008 : :
1009 : : /*
1010 : : * In non-pretty mode, always schema-qualify the target table name for
1011 : : * safety. In pretty mode, schema-qualify only if not visible.
1012 : : */
6132 itagaki.takahiro@gma 1013 [ + + ]:CBC 1446 : appendStringInfo(&buf, " ON %s ",
1014 : : pretty ?
3174 tgl@sss.pgh.pa.us 1015 : 92 : generate_relation_name(trigrec->tgrelid, NIL) :
1016 : 631 : generate_qualified_relation_name(trigrec->tgrelid));
1017 : :
6136 1018 [ - + ]: 723 : if (OidIsValid(trigrec->tgconstraint))
1019 : : {
6194 tgl@sss.pgh.pa.us 1020 [ # # ]:UBC 0 : if (OidIsValid(trigrec->tgconstrrelid))
6132 itagaki.takahiro@gma 1021 : 0 : appendStringInfo(&buf, "FROM %s ",
1022 : : generate_relation_name(trigrec->tgconstrrelid, NIL));
8631 bruce@momjian.us 1023 [ # # ]: 0 : if (!trigrec->tgdeferrable)
4753 rhaas@postgresql.org 1024 : 0 : appendStringInfoString(&buf, "NOT ");
1025 : 0 : appendStringInfoString(&buf, "DEFERRABLE INITIALLY ");
8631 bruce@momjian.us 1026 [ # # ]: 0 : if (trigrec->tginitdeferred)
4753 rhaas@postgresql.org 1027 : 0 : appendStringInfoString(&buf, "DEFERRED ");
1028 : : else
1029 : 0 : appendStringInfoString(&buf, "IMMEDIATE ");
1030 : : }
1031 : :
3653 kgrittn@postgresql.o 1032 :CBC 723 : value = fastgetattr(ht_trig, Anum_pg_trigger_tgoldtable,
1033 : : tgrel->rd_att, &isnull);
1034 [ + + ]: 723 : if (!isnull)
2939 tgl@sss.pgh.pa.us 1035 : 57 : tgoldtable = NameStr(*DatumGetName(value));
1036 : : else
3653 kgrittn@postgresql.o 1037 : 666 : tgoldtable = NULL;
1038 : 723 : value = fastgetattr(ht_trig, Anum_pg_trigger_tgnewtable,
1039 : : tgrel->rd_att, &isnull);
1040 [ + + ]: 723 : if (!isnull)
2939 tgl@sss.pgh.pa.us 1041 : 62 : tgnewtable = NameStr(*DatumGetName(value));
1042 : : else
3653 kgrittn@postgresql.o 1043 : 661 : tgnewtable = NULL;
1044 [ + + + + ]: 723 : if (tgoldtable != NULL || tgnewtable != NULL)
1045 : : {
1046 : 88 : appendStringInfoString(&buf, "REFERENCING ");
1047 [ + + ]: 88 : if (tgoldtable != NULL)
2939 tgl@sss.pgh.pa.us 1048 : 57 : appendStringInfo(&buf, "OLD TABLE AS %s ",
1049 : : quote_identifier(tgoldtable));
3653 kgrittn@postgresql.o 1050 [ + + ]: 88 : if (tgnewtable != NULL)
2939 tgl@sss.pgh.pa.us 1051 : 62 : appendStringInfo(&buf, "NEW TABLE AS %s ",
1052 : : quote_identifier(tgnewtable));
1053 : : }
1054 : :
8631 bruce@momjian.us 1055 [ + + ]: 723 : if (TRIGGER_FOR_ROW(trigrec->tgtype))
4753 rhaas@postgresql.org 1056 : 534 : appendStringInfoString(&buf, "FOR EACH ROW ");
1057 : : else
1058 : 189 : appendStringInfoString(&buf, "FOR EACH STATEMENT ");
1059 : :
1060 : : /* If the trigger has a WHEN qualification, add that */
6194 tgl@sss.pgh.pa.us 1061 : 723 : value = fastgetattr(ht_trig, Anum_pg_trigger_tgqual,
1062 : : tgrel->rd_att, &isnull);
1063 [ + + ]: 723 : if (!isnull)
1064 : : {
1065 : : Node *qual;
1066 : : char relkind;
1067 : : deparse_context context;
1068 : : deparse_namespace dpns;
1069 : : RangeTblEntry *oldrte;
1070 : : RangeTblEntry *newrte;
1071 : :
1072 : 88 : appendStringInfoString(&buf, "WHEN (");
1073 : :
1074 : 88 : qual = stringToNode(TextDatumGetCString(value));
1075 : :
5735 1076 : 88 : relkind = get_rel_relkind(trigrec->tgrelid);
1077 : :
1078 : : /* Build minimal OLD and NEW RTEs for the rel */
6194 1079 : 88 : oldrte = makeNode(RangeTblEntry);
1080 : 88 : oldrte->rtekind = RTE_RELATION;
1081 : 88 : oldrte->relid = trigrec->tgrelid;
5735 1082 : 88 : oldrte->relkind = relkind;
2958 1083 : 88 : oldrte->rellockmode = AccessShareLock;
5158 1084 : 88 : oldrte->alias = makeAlias("old", NIL);
1085 : 88 : oldrte->eref = oldrte->alias;
5203 1086 : 88 : oldrte->lateral = false;
6194 1087 : 88 : oldrte->inh = false;
1088 : 88 : oldrte->inFromCl = true;
1089 : :
1090 : 88 : newrte = makeNode(RangeTblEntry);
1091 : 88 : newrte->rtekind = RTE_RELATION;
1092 : 88 : newrte->relid = trigrec->tgrelid;
5735 1093 : 88 : newrte->relkind = relkind;
2958 1094 : 88 : newrte->rellockmode = AccessShareLock;
5158 1095 : 88 : newrte->alias = makeAlias("new", NIL);
1096 : 88 : newrte->eref = newrte->alias;
5203 1097 : 88 : newrte->lateral = false;
6194 1098 : 88 : newrte->inh = false;
1099 : 88 : newrte->inFromCl = true;
1100 : :
1101 : : /* Build two-element rtable */
5959 1102 : 88 : memset(&dpns, 0, sizeof(dpns));
6194 1103 : 88 : dpns.rtable = list_make2(oldrte, newrte);
2521 1104 : 88 : dpns.subplans = NIL;
6194 1105 : 88 : dpns.ctes = NIL;
2521 1106 : 88 : dpns.appendrels = NULL;
5158 1107 : 88 : set_rtable_names(&dpns, NIL, NULL);
5057 1108 : 88 : set_simple_column_names(&dpns);
1109 : :
1110 : : /* Set up context with one-deep namespace stack */
6194 1111 : 88 : context.buf = &buf;
1112 : 88 : context.namespaces = list_make1(&dpns);
798 1113 : 88 : context.resultDesc = NULL;
1114 : 88 : context.targetList = NIL;
6194 1115 : 88 : context.windowClause = NIL;
1116 : 88 : context.varprefix = true;
1683 1117 [ + + ]: 88 : context.prettyFlags = GET_PRETTY_FLAGS(pretty);
5064 1118 : 88 : context.wrapColumn = WRAP_COLUMN_DEFAULT;
6194 1119 : 88 : context.indentLevel = PRETTYINDENT_STD;
798 1120 : 88 : context.colNamesVisible = true;
1121 : 88 : context.inGroupBy = false;
1122 : 88 : context.varInOrderBy = false;
2521 1123 : 88 : context.appendparents = NULL;
1124 : :
6194 1125 : 88 : get_rule_expr(qual, &context, false);
1126 : :
4753 rhaas@postgresql.org 1127 : 88 : appendStringInfoString(&buf, ") ");
1128 : : }
1129 : :
2828 peter@eisentraut.org 1130 : 723 : appendStringInfo(&buf, "EXECUTE FUNCTION %s(",
1131 : : generate_function_name(trigrec->tgfoid, 0,
1132 : : NIL, NULL,
1133 : : false, NULL, false));
1134 : :
8570 tgl@sss.pgh.pa.us 1135 [ + + ]: 723 : if (trigrec->tgnargs > 0)
1136 : : {
1137 : : char *p;
1138 : : int i;
1139 : :
6194 1140 : 225 : value = fastgetattr(ht_trig, Anum_pg_trigger_tgargs,
1141 : : tgrel->rd_att, &isnull);
8570 1142 [ - + ]: 225 : if (isnull)
8570 tgl@sss.pgh.pa.us 1143 [ # # ]:UBC 0 : elog(ERROR, "tgargs is null for trigger %u", trigid);
3525 noah@leadboat.com 1144 :CBC 225 : p = (char *) VARDATA_ANY(DatumGetByteaPP(value));
8570 tgl@sss.pgh.pa.us 1145 [ + + ]: 460 : for (i = 0; i < trigrec->tgnargs; i++)
1146 : : {
1147 [ + + ]: 235 : if (i > 0)
4753 rhaas@postgresql.org 1148 : 10 : appendStringInfoString(&buf, ", ");
6634 tgl@sss.pgh.pa.us 1149 : 235 : simple_quote_literal(&buf, p);
1150 : : /* advance p to next string embedded in tgargs */
1151 [ + + ]: 2825 : while (*p)
1152 : 2590 : p++;
7466 1153 : 235 : p++;
1154 : : }
1155 : : }
1156 : :
1157 : : /* We deliberately do not put semi-colon at end */
4753 rhaas@postgresql.org 1158 : 723 : appendStringInfoChar(&buf, ')');
1159 : :
1160 : : /* Clean up */
8570 tgl@sss.pgh.pa.us 1161 : 723 : systable_endscan(tgscan);
1162 : :
2845 andres@anarazel.de 1163 : 723 : table_close(tgrel, AccessShareLock);
1164 : :
6236 peter_e@gmx.net 1165 : 723 : return buf.data;
1166 : : }
1167 : :
1168 : : /* ----------
1169 : : * pg_get_indexdef - Get the definition of an index
1170 : : *
1171 : : * In the extended version, there is a colno argument as well as pretty bool.
1172 : : * if colno == 0, we want a complete index definition.
1173 : : * if colno > 0, we only want the Nth index key's variable or expression.
1174 : : *
1175 : : * Note that the SQL-function versions of this omit any info about the
1176 : : * index tablespace; this is intentional because pg_dump wants it that way.
1177 : : * However pg_get_indexdef_string() includes the index tablespace.
1178 : : * ----------
1179 : : */
1180 : : Datum
9645 tgl@sss.pgh.pa.us 1181 : 3088 : pg_get_indexdef(PG_FUNCTION_ARGS)
1182 : : {
1183 : 3088 : Oid indexrelid = PG_GETARG_OID(0);
1184 : : int prettyFlags;
1185 : : char *res;
1186 : :
5023 1187 : 3088 : prettyFlags = PRETTYFLAG_INDENT;
1188 : :
3031 1189 : 3088 : res = pg_get_indexdef_worker(indexrelid, 0, NULL,
1190 : : false, false,
1191 : : false, false,
1192 : : prettyFlags, true);
1193 : :
3754 rhaas@postgresql.org 1194 [ + + ]: 3088 : if (res == NULL)
1195 : 4 : PG_RETURN_NULL();
1196 : :
1197 : 3084 : PG_RETURN_TEXT_P(string_to_text(res));
1198 : : }
1199 : :
1200 : : Datum
8499 tgl@sss.pgh.pa.us 1201 : 1375 : pg_get_indexdef_ext(PG_FUNCTION_ARGS)
1202 : : {
1203 : 1375 : Oid indexrelid = PG_GETARG_OID(0);
8494 bruce@momjian.us 1204 : 1375 : int32 colno = PG_GETARG_INT32(1);
8499 tgl@sss.pgh.pa.us 1205 : 1375 : bool pretty = PG_GETARG_BOOL(2);
1206 : : int prettyFlags;
1207 : : char *res;
1208 : :
1683 1209 [ + - ]: 1375 : prettyFlags = GET_PRETTY_FLAGS(pretty);
1210 : :
3031 1211 : 1375 : res = pg_get_indexdef_worker(indexrelid, colno, NULL,
1212 : : colno != 0, false,
1213 : : false, false,
1214 : : prettyFlags, true);
1215 : :
3754 rhaas@postgresql.org 1216 [ - + ]: 1375 : if (res == NULL)
3754 rhaas@postgresql.org 1217 :UBC 0 : PG_RETURN_NULL();
1218 : :
3754 rhaas@postgresql.org 1219 :CBC 1375 : PG_RETURN_TEXT_P(string_to_text(res));
1220 : : }
1221 : :
1222 : : /*
1223 : : * Internal version for use by ALTER TABLE.
1224 : : * Includes a tablespace clause in the result.
1225 : : * Returns a palloc'd C string; no pretty-printing.
1226 : : */
1227 : : char *
8219 tgl@sss.pgh.pa.us 1228 : 179 : pg_get_indexdef_string(Oid indexrelid)
1229 : : {
3031 1230 : 179 : return pg_get_indexdef_worker(indexrelid, 0, NULL,
1231 : : false, false,
1232 : : true, true,
1233 : : 0, false);
1234 : : }
1235 : :
1236 : : /* Internal version that just reports the key-column definitions */
1237 : : char *
6305 1238 : 783 : pg_get_indexdef_columns(Oid indexrelid, bool pretty)
1239 : : {
1240 : : int prettyFlags;
1241 : :
1683 1242 [ + - ]: 783 : prettyFlags = GET_PRETTY_FLAGS(pretty);
1243 : :
3031 1244 : 783 : return pg_get_indexdef_worker(indexrelid, 0, NULL,
1245 : : true, true,
1246 : : false, false,
1247 : : prettyFlags, false);
1248 : : }
1249 : :
1250 : : /* Internal version, extensible with flags to control its behavior */
1251 : : char *
220 nathan@postgresql.or 1252 : 4 : pg_get_indexdef_columns_extended(Oid indexrelid, uint16 flags)
1253 : : {
1266 michael@paquier.xyz 1254 : 4 : bool pretty = ((flags & RULE_INDEXDEF_PRETTY) != 0);
1255 : 4 : bool keys_only = ((flags & RULE_INDEXDEF_KEYS_ONLY) != 0);
1256 : : int prettyFlags;
1257 : :
1258 [ + - ]: 4 : prettyFlags = GET_PRETTY_FLAGS(pretty);
1259 : :
1260 : 4 : return pg_get_indexdef_worker(indexrelid, 0, NULL,
1261 : : true, keys_only,
1262 : : false, false,
1263 : : prettyFlags, false);
1264 : : }
1265 : :
1266 : : /*
1267 : : * Internal workhorse to decompile an index definition.
1268 : : *
1269 : : * This is now used for exclusion constraints as well: if excludeOps is not
1270 : : * NULL then it points to an array of exclusion operator OIDs.
1271 : : */
1272 : : static char *
6305 tgl@sss.pgh.pa.us 1273 : 5546 : pg_get_indexdef_worker(Oid indexrelid, int colno,
1274 : : const Oid *excludeOps,
1275 : : bool attrsOnly, bool keysOnly,
1276 : : bool showTblSpc, bool inherits,
1277 : : int prettyFlags, bool missing_ok)
1278 : : {
1279 : : /* might want a separate isConstraint parameter later */
6177 1280 : 5546 : bool isConstraint = (excludeOps != NULL);
1281 : : HeapTuple ht_idx;
1282 : : HeapTuple ht_idxrel;
1283 : : HeapTuple ht_am;
1284 : : Form_pg_index idxrec;
1285 : : Form_pg_class idxrelrec;
1286 : : Form_pg_am amrec;
1287 : : const IndexAmRoutine *amroutine;
1288 : : List *indexprs;
1289 : : ListCell *indexpr_item;
1290 : : List *context;
1291 : : Oid indrelid;
1292 : : int keyno;
1293 : : Datum indcollDatum;
1294 : : Datum indclassDatum;
1295 : : Datum indoptionDatum;
1296 : : oidvector *indcollation;
1297 : : oidvector *indclass;
1298 : : int2vector *indoption;
1299 : : StringInfoData buf;
1300 : : char *str;
1301 : : char *sep;
1302 : :
1303 : : /*
1304 : : * Fetch the pg_index tuple by the Oid of the index
1305 : : */
6108 rhaas@postgresql.org 1306 : 5546 : ht_idx = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexrelid));
10261 bruce@momjian.us 1307 [ + + ]: 5546 : if (!HeapTupleIsValid(ht_idx))
1308 : : {
3754 rhaas@postgresql.org 1309 [ + - ]: 4 : if (missing_ok)
1310 : 4 : return NULL;
8502 tgl@sss.pgh.pa.us 1311 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for index %u", indexrelid);
1312 : : }
10026 bruce@momjian.us 1313 :CBC 5542 : idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
1314 : :
8952 tgl@sss.pgh.pa.us 1315 : 5542 : indrelid = idxrec->indrelid;
1316 [ - + ]: 5542 : Assert(indexrelid == idxrec->indexrelid);
1317 : :
1318 : : /* Must get indcollation, indclass, and indoption the hard way */
1321 dgustafsson@postgres 1319 : 5542 : indcollDatum = SysCacheGetAttrNotNull(INDEXRELID, ht_idx,
1320 : : Anum_pg_index_indcollation);
5749 peter_e@gmx.net 1321 : 5542 : indcollation = (oidvector *) DatumGetPointer(indcollDatum);
1322 : :
1321 dgustafsson@postgres 1323 : 5542 : indclassDatum = SysCacheGetAttrNotNull(INDEXRELID, ht_idx,
1324 : : Anum_pg_index_indclass);
7891 tgl@sss.pgh.pa.us 1325 : 5542 : indclass = (oidvector *) DatumGetPointer(indclassDatum);
1326 : :
1321 dgustafsson@postgres 1327 : 5542 : indoptionDatum = SysCacheGetAttrNotNull(INDEXRELID, ht_idx,
1328 : : Anum_pg_index_indoption);
7240 tgl@sss.pgh.pa.us 1329 : 5542 : indoption = (int2vector *) DatumGetPointer(indoptionDatum);
1330 : :
1331 : : /*
1332 : : * Fetch the pg_class tuple of the index relation
1333 : : */
6108 rhaas@postgresql.org 1334 : 5542 : ht_idxrel = SearchSysCache1(RELOID, ObjectIdGetDatum(indexrelid));
10261 bruce@momjian.us 1335 [ - + ]: 5542 : if (!HeapTupleIsValid(ht_idxrel))
8502 tgl@sss.pgh.pa.us 1336 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", indexrelid);
10026 bruce@momjian.us 1337 :CBC 5542 : idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
1338 : :
1339 : : /*
1340 : : * Fetch the pg_am tuple of the index' access method
1341 : : */
6108 rhaas@postgresql.org 1342 : 5542 : ht_am = SearchSysCache1(AMOID, ObjectIdGetDatum(idxrelrec->relam));
9025 tgl@sss.pgh.pa.us 1343 [ - + ]: 5542 : if (!HeapTupleIsValid(ht_am))
8502 tgl@sss.pgh.pa.us 1344 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for access method %u",
1345 : : idxrelrec->relam);
9025 tgl@sss.pgh.pa.us 1346 :CBC 5542 : amrec = (Form_pg_am) GETSTRUCT(ht_am);
1347 : :
1348 : : /* Fetch the index AM's API struct */
3945 1349 : 5542 : amroutine = GetIndexAmRoutine(amrec->amhandler);
1350 : :
1351 : : /*
1352 : : * Get the index expressions, if any. (NOTE: we do not use the relcache
1353 : : * versions of the expressions and predicate, because we want to display
1354 : : * non-const-folded expressions.)
1355 : : */
3144 andrew@dunslane.net 1356 [ + + ]: 5542 : if (!heap_attisnull(ht_idx, Anum_pg_index_indexprs, NULL))
1357 : : {
1358 : : Datum exprsDatum;
1359 : : char *exprsString;
1360 : :
1321 dgustafsson@postgres 1361 : 512 : exprsDatum = SysCacheGetAttrNotNull(INDEXRELID, ht_idx,
1362 : : Anum_pg_index_indexprs);
6799 tgl@sss.pgh.pa.us 1363 : 512 : exprsString = TextDatumGetCString(exprsDatum);
8562 1364 : 512 : indexprs = (List *) stringToNode(exprsString);
1365 : 512 : pfree(exprsString);
1366 : : }
1367 : : else
1368 : 5030 : indexprs = NIL;
1369 : :
8198 neilc@samurai.com 1370 : 5542 : indexpr_item = list_head(indexprs);
1371 : :
5847 tgl@sss.pgh.pa.us 1372 : 5542 : context = deparse_context_for(get_relation_name(indrelid), indrelid);
1373 : :
1374 : : /*
1375 : : * Start the index definition. Note that the index's name should never be
1376 : : * schema-qualified, but the indexed rel's name may be.
1377 : : */
9896 1378 : 5542 : initStringInfo(&buf);
1379 : :
6305 1380 [ + + ]: 5542 : if (!attrsOnly)
1381 : : {
6177 1382 [ + + ]: 4445 : if (!isConstraint)
3212 alvherre@alvh.no-ip. 1383 : 8656 : appendStringInfo(&buf, "CREATE %sINDEX %s ON %s%s USING %s (",
6177 tgl@sss.pgh.pa.us 1384 [ + + ]: 4328 : idxrec->indisunique ? "UNIQUE " : "",
1385 : 4328 : quote_identifier(NameStr(idxrelrec->relname)),
3212 alvherre@alvh.no-ip. 1386 [ + + ]: 4328 : idxrelrec->relkind == RELKIND_PARTITIONED_INDEX
1387 [ + + ]: 414 : && !inherits ? "ONLY " : "",
3174 tgl@sss.pgh.pa.us 1388 [ + + ]: 4328 : (prettyFlags & PRETTYFLAG_SCHEMA) ?
1389 : 1065 : generate_relation_name(indrelid, NIL) :
1390 : 3263 : generate_qualified_relation_name(indrelid),
6177 1391 : 4328 : quote_identifier(NameStr(amrec->amname)));
1392 : : else /* currently, must be EXCLUDE constraint */
1393 : 117 : appendStringInfo(&buf, "EXCLUDE USING %s (",
1394 : 117 : quote_identifier(NameStr(amrec->amname)));
1395 : : }
1396 : :
1397 : : /*
1398 : : * Report the indexed attributes
1399 : : */
10261 bruce@momjian.us 1400 : 5542 : sep = "";
8562 tgl@sss.pgh.pa.us 1401 [ + + ]: 13929 : for (keyno = 0; keyno < idxrec->indnatts; keyno++)
1402 : : {
7891 1403 : 8452 : AttrNumber attnum = idxrec->indkey.values[keyno];
1404 : : Oid keycoltype;
1405 : : Oid keycolcollation;
1406 : :
1407 : : /*
1408 : : * Ignore non-key attributes if told to.
1409 : : */
3031 1410 [ + + + + ]: 8452 : if (keysOnly && keyno >= idxrec->indnkeyatts)
3134 teodor@sigaev.ru 1411 : 65 : break;
1412 : :
1413 : : /* Otherwise, print INCLUDE to divide key and non-key attrs. */
3031 tgl@sss.pgh.pa.us 1414 [ + + + + ]: 8387 : if (!colno && keyno == idxrec->indnkeyatts)
1415 : : {
3134 teodor@sigaev.ru 1416 : 148 : appendStringInfoString(&buf, ") INCLUDE (");
1417 : 148 : sep = "";
1418 : : }
1419 : :
8499 tgl@sss.pgh.pa.us 1420 [ + + ]: 8387 : if (!colno)
7859 neilc@samurai.com 1421 : 7961 : appendStringInfoString(&buf, sep);
10261 bruce@momjian.us 1422 : 8387 : sep = ", ";
1423 : :
8562 tgl@sss.pgh.pa.us 1424 [ + + ]: 8387 : if (attnum != 0)
1425 : : {
1426 : : /* Simple index column */
1427 : : char *attname;
1428 : : int32 keycoltypmod;
1429 : :
3188 alvherre@alvh.no-ip. 1430 : 7710 : attname = get_attname(indrelid, attnum, false);
8494 bruce@momjian.us 1431 [ + + + + ]: 7710 : if (!colno || colno == keyno + 1)
8314 neilc@samurai.com 1432 : 7602 : appendStringInfoString(&buf, quote_identifier(attname));
5703 tgl@sss.pgh.pa.us 1433 : 7710 : get_atttypetypmodcoll(indrelid, attnum,
1434 : : &keycoltype, &keycoltypmod,
1435 : : &keycolcollation);
1436 : : }
1437 : : else
1438 : : {
1439 : : /* expressional index */
1440 : : Node *indexkey;
1441 : :
8198 neilc@samurai.com 1442 [ - + ]: 677 : if (indexpr_item == NULL)
8562 tgl@sss.pgh.pa.us 1443 [ # # ]:UBC 0 : elog(ERROR, "too few entries in indexprs list");
8198 neilc@samurai.com 1444 :CBC 677 : indexkey = (Node *) lfirst(indexpr_item);
2670 tgl@sss.pgh.pa.us 1445 : 677 : indexpr_item = lnext(indexprs, indexpr_item);
1446 : : /* Deparse */
8499 1447 : 677 : str = deparse_expression_pretty(indexkey, context, false, false,
1448 : : prettyFlags, 0);
8494 bruce@momjian.us 1449 [ + + + + ]: 677 : if (!colno || colno == keyno + 1)
1450 : : {
1451 : : /* Need parens if it's not a bare function call */
3402 tgl@sss.pgh.pa.us 1452 [ + + ]: 669 : if (looks_like_function(indexkey))
8314 neilc@samurai.com 1453 : 33 : appendStringInfoString(&buf, str);
1454 : : else
8499 tgl@sss.pgh.pa.us 1455 : 636 : appendStringInfo(&buf, "(%s)", str);
1456 : : }
8562 1457 : 677 : keycoltype = exprType(indexkey);
5705 1458 : 677 : keycolcollation = exprCollation(indexkey);
1459 : : }
1460 : :
1461 : : /* Print additional decoration for (selected) key columns */
3031 1462 [ + + + + : 8387 : if (!attrsOnly && keyno < idxrec->indnkeyatts &&
- + ]
3031 tgl@sss.pgh.pa.us 1463 [ # # ]:UBC 0 : (!colno || colno == keyno + 1))
1464 : : {
2769 tgl@sss.pgh.pa.us 1465 :CBC 6625 : int16 opt = indoption->values[keyno];
1466 : 6625 : Oid indcoll = indcollation->values[keyno];
2411 akorotkov@postgresql 1467 : 6625 : Datum attoptions = get_attoptions(indexrelid, keyno + 1);
1468 : 6625 : bool has_options = attoptions != (Datum) 0;
1469 : :
1470 : : /* Add collation, if not default for column */
5705 tgl@sss.pgh.pa.us 1471 [ + + + + ]: 6625 : if (OidIsValid(indcoll) && indcoll != keycolcollation)
1472 : 62 : appendStringInfo(&buf, " COLLATE %s",
1473 : : generate_collation_name((indcoll)));
1474 : :
1475 : : /* Add the operator class name, if not default */
2411 akorotkov@postgresql 1476 [ + + ]: 6625 : get_opclass_name(indclass->values[keyno],
1477 : : has_options ? InvalidOid : keycoltype, &buf);
1478 : :
1479 [ + + ]: 6625 : if (has_options)
1480 : : {
1481 : 22 : appendStringInfoString(&buf, " (");
1482 : 22 : get_reloptions(&buf, attoptions);
1483 : 22 : appendStringInfoChar(&buf, ')');
1484 : : }
1485 : :
1486 : : /* Add options if relevant */
3945 tgl@sss.pgh.pa.us 1487 [ + + ]: 6625 : if (amroutine->amcanorder)
1488 : : {
1489 : : /* if it supports sort ordering, report DESC and NULLS opts */
6895 1490 [ - + ]: 5440 : if (opt & INDOPTION_DESC)
1491 : : {
4753 rhaas@postgresql.org 1492 :UBC 0 : appendStringInfoString(&buf, " DESC");
1493 : : /* NULLS FIRST is the default in this case */
6895 tgl@sss.pgh.pa.us 1494 [ # # ]: 0 : if (!(opt & INDOPTION_NULLS_FIRST))
4753 rhaas@postgresql.org 1495 : 0 : appendStringInfoString(&buf, " NULLS LAST");
1496 : : }
1497 : : else
1498 : : {
6895 tgl@sss.pgh.pa.us 1499 [ - + ]:CBC 5440 : if (opt & INDOPTION_NULLS_FIRST)
4753 rhaas@postgresql.org 1500 :UBC 0 : appendStringInfoString(&buf, " NULLS FIRST");
1501 : : }
1502 : : }
1503 : :
1504 : : /* Add the exclusion operator if relevant */
6177 tgl@sss.pgh.pa.us 1505 [ + + ]:CBC 6625 : if (excludeOps != NULL)
1506 : 219 : appendStringInfo(&buf, " WITH %s",
1507 : 219 : generate_operator_name(excludeOps[keyno],
1508 : : keycoltype,
1509 : : keycoltype));
1510 : : }
1511 : : }
1512 : :
6305 1513 [ + + ]: 5542 : if (!attrsOnly)
1514 : : {
8494 bruce@momjian.us 1515 : 4445 : appendStringInfoChar(&buf, ')');
1516 : :
1736 peter@eisentraut.org 1517 [ + + ]: 4445 : if (idxrec->indnullsnotdistinct)
1521 drowley@postgresql.o 1518 : 8 : appendStringInfoString(&buf, " NULLS NOT DISTINCT");
1519 : :
1520 : : /*
1521 : : * If it has options, append "WITH (options)"
1522 : : */
7430 tgl@sss.pgh.pa.us 1523 : 4445 : str = flatten_reloptions(indexrelid);
1524 [ + + ]: 4445 : if (str)
1525 : : {
1526 : 105 : appendStringInfo(&buf, " WITH (%s)", str);
1527 : 105 : pfree(str);
1528 : : }
1529 : :
1530 : : /*
1531 : : * Print tablespace, but only if requested
1532 : : */
6963 1533 [ + + ]: 4445 : if (showTblSpc)
1534 : : {
1535 : : Oid tblspc;
1536 : :
1537 : 179 : tblspc = get_rel_tablespace(indexrelid);
2751 alvherre@alvh.no-ip. 1538 [ + + ]: 179 : if (OidIsValid(tblspc))
1539 : : {
1540 [ - + ]: 36 : if (isConstraint)
2751 alvherre@alvh.no-ip. 1541 :UBC 0 : appendStringInfoString(&buf, " USING INDEX");
2751 alvherre@alvh.no-ip. 1542 :CBC 36 : appendStringInfo(&buf, " TABLESPACE %s",
1543 : 36 : quote_identifier(get_tablespace_name(tblspc)));
1544 : : }
1545 : : }
1546 : :
1547 : : /*
1548 : : * If it's a partial index, decompile and append the predicate
1549 : : */
3144 andrew@dunslane.net 1550 [ + + ]: 4445 : if (!heap_attisnull(ht_idx, Anum_pg_index_indpred, NULL))
1551 : : {
1552 : : Node *node;
1553 : : Datum predDatum;
1554 : : char *predString;
1555 : :
1556 : : /* Convert text string to node tree */
1321 dgustafsson@postgres 1557 : 234 : predDatum = SysCacheGetAttrNotNull(INDEXRELID, ht_idx,
1558 : : Anum_pg_index_indpred);
6799 tgl@sss.pgh.pa.us 1559 : 234 : predString = TextDatumGetCString(predDatum);
8499 1560 : 234 : node = (Node *) stringToNode(predString);
1561 : 234 : pfree(predString);
1562 : :
1563 : : /* Deparse */
1564 : 234 : str = deparse_expression_pretty(node, context, false, false,
1565 : : prettyFlags, 0);
6177 1566 [ + + ]: 234 : if (isConstraint)
1567 : 28 : appendStringInfo(&buf, " WHERE (%s)", str);
1568 : : else
1569 : 206 : appendStringInfo(&buf, " WHERE %s", str);
1570 : : }
1571 : : }
1572 : :
1573 : : /* Clean up */
9485 1574 : 5542 : ReleaseSysCache(ht_idx);
1575 : 5542 : ReleaseSysCache(ht_idxrel);
9025 1576 : 5542 : ReleaseSysCache(ht_am);
1577 : :
8219 1578 : 5542 : return buf.data;
1579 : : }
1580 : :
1581 : : /* ----------
1582 : : * pg_get_querydef
1583 : : *
1584 : : * Public entry point to deparse one query parsetree.
1585 : : * The pretty flags are determined by GET_PRETTY_FLAGS(pretty).
1586 : : *
1587 : : * The result is a palloc'd C string.
1588 : : * ----------
1589 : : */
1590 : : char *
1683 tgl@sss.pgh.pa.us 1591 :UBC 0 : pg_get_querydef(Query *query, bool pretty)
1592 : : {
1593 : : StringInfoData buf;
1594 : : int prettyFlags;
1595 : :
1596 [ # # ]: 0 : prettyFlags = GET_PRETTY_FLAGS(pretty);
1597 : :
1598 : 0 : initStringInfo(&buf);
1599 : :
1629 1600 : 0 : get_query_def(query, &buf, NIL, NULL, true,
1601 : : prettyFlags, WRAP_COLUMN_DEFAULT, 0);
1602 : :
1683 1603 : 0 : return buf.data;
1604 : : }
1605 : :
1606 : : /*
1607 : : * pg_get_statisticsobjdef
1608 : : * Get the definition of an extended statistics object
1609 : : */
1610 : : Datum
3462 tgl@sss.pgh.pa.us 1611 :CBC 163 : pg_get_statisticsobjdef(PG_FUNCTION_ARGS)
1612 : : {
3513 alvherre@alvh.no-ip. 1613 : 163 : Oid statextid = PG_GETARG_OID(0);
1614 : : char *res;
1615 : :
2050 tomas.vondra@postgre 1616 : 163 : res = pg_get_statisticsobj_worker(statextid, false, true);
1617 : :
1618 [ + + ]: 163 : if (res == NULL)
1619 : 4 : PG_RETURN_NULL();
1620 : :
1621 : 159 : PG_RETURN_TEXT_P(string_to_text(res));
1622 : : }
1623 : :
1624 : : /*
1625 : : * Internal version for use by ALTER TABLE.
1626 : : * Returns a palloc'd C string; no pretty-printing.
1627 : : */
1628 : : char *
1629 : 57 : pg_get_statisticsobjdef_string(Oid statextid)
1630 : : {
1631 : 57 : return pg_get_statisticsobj_worker(statextid, false, false);
1632 : : }
1633 : :
1634 : : /*
1635 : : * pg_get_statisticsobjdef_columns
1636 : : * Get columns and expressions for an extended statistics object
1637 : : */
1638 : : Datum
1639 : 276 : pg_get_statisticsobjdef_columns(PG_FUNCTION_ARGS)
1640 : : {
1641 : 276 : Oid statextid = PG_GETARG_OID(0);
1642 : : char *res;
1643 : :
1644 : 276 : res = pg_get_statisticsobj_worker(statextid, true, true);
1645 : :
3513 alvherre@alvh.no-ip. 1646 [ - + ]: 276 : if (res == NULL)
3513 alvherre@alvh.no-ip. 1647 :UBC 0 : PG_RETURN_NULL();
1648 : :
3513 alvherre@alvh.no-ip. 1649 :CBC 276 : PG_RETURN_TEXT_P(string_to_text(res));
1650 : : }
1651 : :
1652 : : /*
1653 : : * Internal workhorse to decompile an extended statistics object.
1654 : : */
1655 : : static char *
2050 tomas.vondra@postgre 1656 : 496 : pg_get_statisticsobj_worker(Oid statextid, bool columns_only, bool missing_ok)
1657 : : {
1658 : : Form_pg_statistic_ext statextrec;
1659 : : HeapTuple statexttup;
1660 : : StringInfoData buf;
1661 : : int colno;
1662 : : char *nsp;
1663 : : ArrayType *arr;
1664 : : char *enabled;
1665 : : Datum datum;
1666 : : bool ndistinct_enabled;
1667 : : bool dependencies_enabled;
1668 : : bool mcv_enabled;
1669 : : int i;
1670 : : List *context;
1671 : : ListCell *lc;
1672 : 496 : List *exprs = NIL;
1673 : : bool has_exprs;
1674 : : int ncolumns;
1675 : :
3513 alvherre@alvh.no-ip. 1676 : 496 : statexttup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(statextid));
1677 : :
1678 [ + + ]: 496 : if (!HeapTupleIsValid(statexttup))
1679 : : {
1680 [ + - ]: 4 : if (missing_ok)
1681 : 4 : return NULL;
3462 tgl@sss.pgh.pa.us 1682 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for statistics object %u", statextid);
1683 : : }
1684 : :
1685 : : /* has the statistics expressions? */
2050 tomas.vondra@postgre 1686 :CBC 492 : has_exprs = !heap_attisnull(statexttup, Anum_pg_statistic_ext_stxexprs, NULL);
1687 : :
1688 : 492 : statextrec = (Form_pg_statistic_ext) GETSTRUCT(statexttup);
1689 : :
1690 : : /*
1691 : : * Get the statistics expressions, if any. (NOTE: we do not use the
1692 : : * relcache versions of the expressions, because we want to display
1693 : : * non-const-folded expressions.)
1694 : : */
1695 [ + + ]: 492 : if (has_exprs)
1696 : : {
1697 : : Datum exprsDatum;
1698 : : char *exprsString;
1699 : :
1321 dgustafsson@postgres 1700 : 129 : exprsDatum = SysCacheGetAttrNotNull(STATEXTOID, statexttup,
1701 : : Anum_pg_statistic_ext_stxexprs);
2050 tomas.vondra@postgre 1702 : 129 : exprsString = TextDatumGetCString(exprsDatum);
1703 : 129 : exprs = (List *) stringToNode(exprsString);
1704 : 129 : pfree(exprsString);
1705 : : }
1706 : : else
1707 : 363 : exprs = NIL;
1708 : :
1709 : : /* count the number of columns (attributes and expressions) */
1710 : 492 : ncolumns = statextrec->stxkeys.dim1 + list_length(exprs);
1711 : :
1712 : 492 : initStringInfo(&buf);
1713 : :
1714 [ + + ]: 492 : if (!columns_only)
1715 : : {
1927 tgl@sss.pgh.pa.us 1716 : 216 : nsp = get_namespace_name_or_temp(statextrec->stxnamespace);
2050 tomas.vondra@postgre 1717 : 216 : appendStringInfo(&buf, "CREATE STATISTICS %s",
1718 : : quote_qualified_identifier(nsp,
1719 : 216 : NameStr(statextrec->stxname)));
1720 : :
1721 : : /*
1722 : : * Decode the stxkind column so that we know which stats types to
1723 : : * print.
1724 : : */
1321 dgustafsson@postgres 1725 : 216 : datum = SysCacheGetAttrNotNull(STATEXTOID, statexttup,
1726 : : Anum_pg_statistic_ext_stxkind);
2050 tomas.vondra@postgre 1727 : 216 : arr = DatumGetArrayTypeP(datum);
1728 [ + - ]: 216 : if (ARR_NDIM(arr) != 1 ||
1729 [ + - ]: 216 : ARR_HASNULL(arr) ||
1730 [ - + ]: 216 : ARR_ELEMTYPE(arr) != CHAROID)
2050 tomas.vondra@postgre 1731 [ # # ]:UBC 0 : elog(ERROR, "stxkind is not a 1-D char array");
2050 tomas.vondra@postgre 1732 [ - + ]:CBC 216 : enabled = (char *) ARR_DATA_PTR(arr);
1733 : :
1734 : 216 : ndistinct_enabled = false;
1735 : 216 : dependencies_enabled = false;
1736 : 216 : mcv_enabled = false;
1737 : :
1738 [ + + ]: 685 : for (i = 0; i < ARR_DIMS(arr)[0]; i++)
1739 : : {
1740 [ + + ]: 469 : if (enabled[i] == STATS_EXT_NDISTINCT)
1741 : 141 : ndistinct_enabled = true;
1742 [ + + ]: 328 : else if (enabled[i] == STATS_EXT_DEPENDENCIES)
1743 : 117 : dependencies_enabled = true;
1744 [ + + ]: 211 : else if (enabled[i] == STATS_EXT_MCV)
1745 : 126 : mcv_enabled = true;
1746 : :
1747 : : /* ignore STATS_EXT_EXPRESSIONS (it's built automatically) */
1748 : : }
1749 : :
1750 : : /*
1751 : : * If any option is disabled, then we'll need to append the types
1752 : : * clause to show which options are enabled. We omit the types clause
1753 : : * on purpose when all options are enabled, so a pg_dump/pg_restore
1754 : : * will create all statistics types on a newer postgres version, if
1755 : : * the statistics had all options enabled on the original version.
1756 : : *
1757 : : * But if the statistics is defined on just a single column, it has to
1758 : : * be an expression statistics. In that case we don't need to specify
1759 : : * kinds.
1760 : : */
1761 [ + + + + : 216 : if ((!ndistinct_enabled || !dependencies_enabled || !mcv_enabled) &&
- + + + ]
1762 : : (ncolumns > 1))
1763 : : {
1764 : 63 : bool gotone = false;
1765 : :
1766 : 63 : appendStringInfoString(&buf, " (");
1767 : :
1768 [ + + ]: 63 : if (ndistinct_enabled)
1769 : : {
1770 : 34 : appendStringInfoString(&buf, "ndistinct");
1771 : 34 : gotone = true;
1772 : : }
1773 : :
1774 [ + + ]: 63 : if (dependencies_enabled)
1775 : : {
1776 [ - + ]: 10 : appendStringInfo(&buf, "%sdependencies", gotone ? ", " : "");
1777 : 10 : gotone = true;
1778 : : }
1779 : :
1780 [ + + ]: 63 : if (mcv_enabled)
1781 [ - + ]: 19 : appendStringInfo(&buf, "%smcv", gotone ? ", " : "");
1782 : :
1783 : 63 : appendStringInfoChar(&buf, ')');
1784 : : }
1785 : :
1786 : 216 : appendStringInfoString(&buf, " ON ");
1787 : : }
1788 : :
1789 : : /* decode simple column references */
3489 alvherre@alvh.no-ip. 1790 [ + + ]: 1399 : for (colno = 0; colno < statextrec->stxkeys.dim1; colno++)
1791 : : {
1792 : 907 : AttrNumber attnum = statextrec->stxkeys.values[colno];
1793 : : char *attname;
1794 : :
3513 1795 [ + + ]: 907 : if (colno > 0)
1796 : 502 : appendStringInfoString(&buf, ", ");
1797 : :
3188 1798 : 907 : attname = get_attname(statextrec->stxrelid, attnum, false);
1799 : :
3513 1800 : 907 : appendStringInfoString(&buf, quote_identifier(attname));
1801 : : }
1802 : :
2050 tomas.vondra@postgre 1803 : 492 : context = deparse_context_for(get_relation_name(statextrec->stxrelid),
1804 : : statextrec->stxrelid);
1805 : :
1806 [ + + + + : 675 : foreach(lc, exprs)
+ + ]
1807 : : {
1808 : 183 : Node *expr = (Node *) lfirst(lc);
1809 : : char *str;
1891 1810 : 183 : int prettyFlags = PRETTYFLAG_PAREN;
1811 : :
2050 1812 : 183 : str = deparse_expression_pretty(expr, context, false, false,
1813 : : prettyFlags, 0);
1814 : :
1815 [ + + ]: 183 : if (colno > 0)
1816 : 96 : appendStringInfoString(&buf, ", ");
1817 : :
1818 : : /* Need parens if it's not a bare function call */
1819 [ + + ]: 183 : if (looks_like_function(expr))
1820 : 21 : appendStringInfoString(&buf, str);
1821 : : else
1822 : 162 : appendStringInfo(&buf, "(%s)", str);
1823 : :
1824 : 183 : colno++;
1825 : : }
1826 : :
1827 [ + + ]: 492 : if (!columns_only)
1828 : 216 : appendStringInfo(&buf, " FROM %s",
1829 : : generate_relation_name(statextrec->stxrelid, NIL));
1830 : :
3513 alvherre@alvh.no-ip. 1831 : 492 : ReleaseSysCache(statexttup);
1832 : :
1833 : 492 : return buf.data;
1834 : : }
1835 : :
1836 : : /*
1837 : : * Generate text array of expressions for statistics object.
1838 : : */
1839 : : Datum
2050 tomas.vondra@postgre 1840 : 136 : pg_get_statisticsobjdef_expressions(PG_FUNCTION_ARGS)
1841 : : {
1842 : 136 : Oid statextid = PG_GETARG_OID(0);
1843 : : Form_pg_statistic_ext statextrec;
1844 : : HeapTuple statexttup;
1845 : : Datum datum;
1846 : : List *context;
1847 : : ListCell *lc;
1848 : 136 : List *exprs = NIL;
1849 : : bool has_exprs;
1850 : : char *tmp;
1851 : 136 : ArrayBuildState *astate = NULL;
1852 : :
1853 : 136 : statexttup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(statextid));
1854 : :
1855 [ - + ]: 136 : if (!HeapTupleIsValid(statexttup))
2008 tomas.vondra@postgre 1856 :UBC 0 : PG_RETURN_NULL();
1857 : :
1858 : : /* Does the stats object have expressions? */
2050 tomas.vondra@postgre 1859 :CBC 136 : has_exprs = !heap_attisnull(statexttup, Anum_pg_statistic_ext_stxexprs, NULL);
1860 : :
1861 : : /* no expressions? we're done */
1862 [ + + ]: 136 : if (!has_exprs)
1863 : : {
1864 : 11 : ReleaseSysCache(statexttup);
1865 : 11 : PG_RETURN_NULL();
1866 : : }
1867 : :
1868 : 125 : statextrec = (Form_pg_statistic_ext) GETSTRUCT(statexttup);
1869 : :
1870 : : /*
1871 : : * Get the statistics expressions, and deparse them into text values.
1872 : : */
1321 dgustafsson@postgres 1873 : 125 : datum = SysCacheGetAttrNotNull(STATEXTOID, statexttup,
1874 : : Anum_pg_statistic_ext_stxexprs);
2050 tomas.vondra@postgre 1875 : 125 : tmp = TextDatumGetCString(datum);
1876 : 125 : exprs = (List *) stringToNode(tmp);
1877 : 125 : pfree(tmp);
1878 : :
1879 : 125 : context = deparse_context_for(get_relation_name(statextrec->stxrelid),
1880 : : statextrec->stxrelid);
1881 : :
1882 [ + - + + : 300 : foreach(lc, exprs)
+ + ]
1883 : : {
1884 : 175 : Node *expr = (Node *) lfirst(lc);
1885 : : char *str;
1886 : 175 : int prettyFlags = PRETTYFLAG_INDENT;
1887 : :
1888 : 175 : str = deparse_expression_pretty(expr, context, false, false,
1889 : : prettyFlags, 0);
1890 : :
1891 : 175 : astate = accumArrayResult(astate,
1892 : 175 : PointerGetDatum(cstring_to_text(str)),
1893 : : false,
1894 : : TEXTOID,
1895 : : CurrentMemoryContext);
1896 : : }
1897 : :
1898 : 125 : ReleaseSysCache(statexttup);
1899 : :
1900 : 125 : PG_RETURN_DATUM(makeArrayResult(astate, CurrentMemoryContext));
1901 : : }
1902 : :
1903 : : /*
1904 : : * pg_get_partkeydef
1905 : : *
1906 : : * Returns the partition key specification, ie, the following:
1907 : : *
1908 : : * { RANGE | LIST | HASH } (column opt_collation opt_opclass [, ...])
1909 : : */
1910 : : Datum
3620 rhaas@postgresql.org 1911 : 813 : pg_get_partkeydef(PG_FUNCTION_ARGS)
1912 : : {
1913 : 813 : Oid relid = PG_GETARG_OID(0);
1914 : : char *res;
1915 : :
3480 sfrost@snowman.net 1916 : 813 : res = pg_get_partkeydef_worker(relid, PRETTYFLAG_INDENT, false, true);
1917 : :
1918 [ + + ]: 813 : if (res == NULL)
1919 : 4 : PG_RETURN_NULL();
1920 : :
1921 : 809 : PG_RETURN_TEXT_P(string_to_text(res));
1922 : : }
1923 : :
1924 : : /* Internal version that just reports the column definitions */
1925 : : char *
3534 rhaas@postgresql.org 1926 : 94 : pg_get_partkeydef_columns(Oid relid, bool pretty)
1927 : : {
1928 : : int prettyFlags;
1929 : :
1683 tgl@sss.pgh.pa.us 1930 [ + - ]: 94 : prettyFlags = GET_PRETTY_FLAGS(pretty);
1931 : :
3480 sfrost@snowman.net 1932 : 94 : return pg_get_partkeydef_worker(relid, prettyFlags, true, false);
1933 : : }
1934 : :
1935 : : /*
1936 : : * Internal workhorse to decompile a partition key definition.
1937 : : */
1938 : : static char *
3534 rhaas@postgresql.org 1939 : 907 : pg_get_partkeydef_worker(Oid relid, int prettyFlags,
1940 : : bool attrsOnly, bool missing_ok)
1941 : : {
1942 : : Form_pg_partitioned_table form;
1943 : : HeapTuple tuple;
1944 : : oidvector *partclass;
1945 : : oidvector *partcollation;
1946 : : List *partexprs;
1947 : : ListCell *partexpr_item;
1948 : : List *context;
1949 : : Datum datum;
1950 : : StringInfoData buf;
1951 : : int keyno;
1952 : : char *str;
1953 : : char *sep;
1954 : :
3620 1955 : 907 : tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
1956 [ + + ]: 907 : if (!HeapTupleIsValid(tuple))
1957 : : {
3480 sfrost@snowman.net 1958 [ + - ]: 4 : if (missing_ok)
1959 : 4 : return NULL;
3620 rhaas@postgresql.org 1960 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for partition key of %u", relid);
1961 : : }
1962 : :
3620 rhaas@postgresql.org 1963 :CBC 903 : form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
1964 : :
1965 [ - + ]: 903 : Assert(form->partrelid == relid);
1966 : :
1967 : : /* Must get partclass and partcollation the hard way */
1321 dgustafsson@postgres 1968 : 903 : datum = SysCacheGetAttrNotNull(PARTRELID, tuple,
1969 : : Anum_pg_partitioned_table_partclass);
3620 rhaas@postgresql.org 1970 : 903 : partclass = (oidvector *) DatumGetPointer(datum);
1971 : :
1321 dgustafsson@postgres 1972 : 903 : datum = SysCacheGetAttrNotNull(PARTRELID, tuple,
1973 : : Anum_pg_partitioned_table_partcollation);
3620 rhaas@postgresql.org 1974 : 903 : partcollation = (oidvector *) DatumGetPointer(datum);
1975 : :
1976 : :
1977 : : /*
1978 : : * Get the expressions, if any. (NOTE: we do not use the relcache
1979 : : * versions of the expressions, because we want to display
1980 : : * non-const-folded expressions.)
1981 : : */
3144 andrew@dunslane.net 1982 [ + + ]: 903 : if (!heap_attisnull(tuple, Anum_pg_partitioned_table_partexprs, NULL))
1983 : : {
1984 : : Datum exprsDatum;
1985 : : char *exprsString;
1986 : :
1321 dgustafsson@postgres 1987 : 84 : exprsDatum = SysCacheGetAttrNotNull(PARTRELID, tuple,
1988 : : Anum_pg_partitioned_table_partexprs);
3620 rhaas@postgresql.org 1989 : 84 : exprsString = TextDatumGetCString(exprsDatum);
1990 : 84 : partexprs = (List *) stringToNode(exprsString);
1991 : :
1992 [ - + ]: 84 : if (!IsA(partexprs, List))
3620 rhaas@postgresql.org 1993 [ # # ]:UBC 0 : elog(ERROR, "unexpected node type found in partexprs: %d",
1994 : : (int) nodeTag(partexprs));
1995 : :
3620 rhaas@postgresql.org 1996 :CBC 84 : pfree(exprsString);
1997 : : }
1998 : : else
1999 : 819 : partexprs = NIL;
2000 : :
2001 : 903 : partexpr_item = list_head(partexprs);
2002 : 903 : context = deparse_context_for(get_relation_name(relid), relid);
2003 : :
2004 : 903 : initStringInfo(&buf);
2005 : :
2006 [ + + + - ]: 903 : switch (form->partstrat)
2007 : : {
3283 2008 : 61 : case PARTITION_STRATEGY_HASH:
2009 [ + - ]: 61 : if (!attrsOnly)
2681 drowley@postgresql.o 2010 : 61 : appendStringInfoString(&buf, "HASH");
3283 rhaas@postgresql.org 2011 : 61 : break;
3620 2012 : 339 : case PARTITION_STRATEGY_LIST:
3534 2013 [ + + ]: 339 : if (!attrsOnly)
3369 peter_e@gmx.net 2014 : 313 : appendStringInfoString(&buf, "LIST");
3620 rhaas@postgresql.org 2015 : 339 : break;
2016 : 503 : case PARTITION_STRATEGY_RANGE:
3534 2017 [ + + ]: 503 : if (!attrsOnly)
3369 peter_e@gmx.net 2018 : 435 : appendStringInfoString(&buf, "RANGE");
3620 rhaas@postgresql.org 2019 : 503 : break;
3620 rhaas@postgresql.org 2020 :UBC 0 : default:
2021 [ # # ]: 0 : elog(ERROR, "unexpected partition strategy: %d",
2022 : : (int) form->partstrat);
2023 : : }
2024 : :
3534 rhaas@postgresql.org 2025 [ + + ]:CBC 903 : if (!attrsOnly)
3369 peter_e@gmx.net 2026 : 809 : appendStringInfoString(&buf, " (");
3620 rhaas@postgresql.org 2027 : 903 : sep = "";
2028 [ + + ]: 1899 : for (keyno = 0; keyno < form->partnatts; keyno++)
2029 : : {
2030 : 996 : AttrNumber attnum = form->partattrs.values[keyno];
2031 : : Oid keycoltype;
2032 : : Oid keycolcollation;
2033 : : Oid partcoll;
2034 : :
2035 : 996 : appendStringInfoString(&buf, sep);
2036 : 996 : sep = ", ";
2037 [ + + ]: 996 : if (attnum != 0)
2038 : : {
2039 : : /* Simple attribute reference */
2040 : : char *attname;
2041 : : int32 keycoltypmod;
2042 : :
3188 alvherre@alvh.no-ip. 2043 : 904 : attname = get_attname(relid, attnum, false);
3620 rhaas@postgresql.org 2044 : 904 : appendStringInfoString(&buf, quote_identifier(attname));
2045 : 904 : get_atttypetypmodcoll(relid, attnum,
2046 : : &keycoltype, &keycoltypmod,
2047 : : &keycolcollation);
2048 : : }
2049 : : else
2050 : : {
2051 : : /* Expression */
2052 : : Node *partkey;
2053 : :
2054 [ - + ]: 92 : if (partexpr_item == NULL)
3620 rhaas@postgresql.org 2055 [ # # ]:UBC 0 : elog(ERROR, "too few entries in partexprs list");
3620 rhaas@postgresql.org 2056 :CBC 92 : partkey = (Node *) lfirst(partexpr_item);
2670 tgl@sss.pgh.pa.us 2057 : 92 : partexpr_item = lnext(partexprs, partexpr_item);
2058 : :
2059 : : /* Deparse */
3620 rhaas@postgresql.org 2060 : 92 : str = deparse_expression_pretty(partkey, context, false, false,
2061 : : prettyFlags, 0);
2062 : : /* Need parens if it's not a bare function call */
3402 tgl@sss.pgh.pa.us 2063 [ + + ]: 92 : if (looks_like_function(partkey))
2064 : 34 : appendStringInfoString(&buf, str);
2065 : : else
2066 : 58 : appendStringInfo(&buf, "(%s)", str);
2067 : :
3620 rhaas@postgresql.org 2068 : 92 : keycoltype = exprType(partkey);
2069 : 92 : keycolcollation = exprCollation(partkey);
2070 : : }
2071 : :
2072 : : /* Add collation, if not default for column */
2073 : 996 : partcoll = partcollation->values[keyno];
3534 2074 [ + + + + : 996 : if (!attrsOnly && OidIsValid(partcoll) && partcoll != keycolcollation)
+ + ]
3620 2075 : 4 : appendStringInfo(&buf, " COLLATE %s",
2076 : : generate_collation_name((partcoll)));
2077 : :
2078 : : /* Add the operator class name, if not default */
3534 2079 [ + + ]: 996 : if (!attrsOnly)
2080 : 866 : get_opclass_name(partclass->values[keyno], keycoltype, &buf);
2081 : : }
2082 : :
2083 [ + + ]: 903 : if (!attrsOnly)
2084 : 809 : appendStringInfoChar(&buf, ')');
2085 : :
2086 : : /* Clean up */
3620 2087 : 903 : ReleaseSysCache(tuple);
2088 : :
2089 : 903 : return buf.data;
2090 : : }
2091 : :
2092 : : /*
2093 : : * pg_get_partition_constraintdef
2094 : : *
2095 : : * Returns partition constraint expression as a string for the input relation
2096 : : */
2097 : : Datum
3463 2098 : 121 : pg_get_partition_constraintdef(PG_FUNCTION_ARGS)
2099 : : {
3459 bruce@momjian.us 2100 : 121 : Oid relationId = PG_GETARG_OID(0);
2101 : : Expr *constr_expr;
2102 : : int prettyFlags;
2103 : : List *context;
2104 : : char *consrc;
2105 : :
3463 rhaas@postgresql.org 2106 : 121 : constr_expr = get_partition_qual_relid(relationId);
2107 : :
2108 : : /* Quick exit if no partition constraint */
2109 [ + + ]: 121 : if (constr_expr == NULL)
2110 : 12 : PG_RETURN_NULL();
2111 : :
2112 : : /*
2113 : : * Deparse and return the constraint expression.
2114 : : */
2115 : 109 : prettyFlags = PRETTYFLAG_INDENT;
2116 : 109 : context = deparse_context_for(get_relation_name(relationId), relationId);
2117 : 109 : consrc = deparse_expression_pretty((Node *) constr_expr, context, false,
2118 : : false, prettyFlags, 0);
2119 : :
2120 : 109 : PG_RETURN_TEXT_P(string_to_text(consrc));
2121 : : }
2122 : :
2123 : : /*
2124 : : * pg_get_partconstrdef_string
2125 : : *
2126 : : * Returns the partition constraint as a C-string for the input relation, with
2127 : : * the given alias. No pretty-printing.
2128 : : */
2129 : : char *
2773 alvherre@alvh.no-ip. 2130 : 65 : pg_get_partconstrdef_string(Oid partitionId, char *aliasname)
2131 : : {
2132 : : Expr *constr_expr;
2133 : : List *context;
2134 : :
2135 : 65 : constr_expr = get_partition_qual_relid(partitionId);
2136 : 65 : context = deparse_context_for(aliasname, partitionId);
2137 : :
2138 : 65 : return deparse_expression((Node *) constr_expr, context, true, false);
2139 : : }
2140 : :
2141 : : /*
2142 : : * pg_get_constraintdef
2143 : : *
2144 : : * Returns the definition for the constraint, ie, everything that needs to
2145 : : * appear after "ALTER TABLE ... ADD CONSTRAINT <constraintname>".
2146 : : */
2147 : : Datum
8847 tgl@sss.pgh.pa.us 2148 : 1130 : pg_get_constraintdef(PG_FUNCTION_ARGS)
2149 : : {
8828 bruce@momjian.us 2150 : 1130 : Oid constraintId = PG_GETARG_OID(0);
2151 : : int prettyFlags;
2152 : : char *res;
2153 : :
5023 tgl@sss.pgh.pa.us 2154 : 1130 : prettyFlags = PRETTYFLAG_INDENT;
2155 : :
3754 rhaas@postgresql.org 2156 : 1130 : res = pg_get_constraintdef_worker(constraintId, false, prettyFlags, true);
2157 : :
2158 [ + + ]: 1130 : if (res == NULL)
2159 : 4 : PG_RETURN_NULL();
2160 : :
2161 : 1126 : PG_RETURN_TEXT_P(string_to_text(res));
2162 : : }
2163 : :
2164 : : Datum
8499 tgl@sss.pgh.pa.us 2165 : 2784 : pg_get_constraintdef_ext(PG_FUNCTION_ARGS)
2166 : : {
2167 : 2784 : Oid constraintId = PG_GETARG_OID(0);
2168 : 2784 : bool pretty = PG_GETARG_BOOL(1);
2169 : : int prettyFlags;
2170 : : char *res;
2171 : :
1683 2172 [ + + ]: 2784 : prettyFlags = GET_PRETTY_FLAGS(pretty);
2173 : :
3754 rhaas@postgresql.org 2174 : 2784 : res = pg_get_constraintdef_worker(constraintId, false, prettyFlags, true);
2175 : :
2176 [ - + ]: 2784 : if (res == NULL)
3754 rhaas@postgresql.org 2177 :UBC 0 : PG_RETURN_NULL();
2178 : :
3754 rhaas@postgresql.org 2179 :CBC 2784 : PG_RETURN_TEXT_P(string_to_text(res));
2180 : : }
2181 : :
2182 : : /*
2183 : : * Internal version that returns a full ALTER TABLE ... ADD CONSTRAINT command
2184 : : */
2185 : : char *
4003 tgl@sss.pgh.pa.us 2186 : 468 : pg_get_constraintdef_command(Oid constraintId)
2187 : : {
3754 rhaas@postgresql.org 2188 : 468 : return pg_get_constraintdef_worker(constraintId, true, 0, false);
2189 : : }
2190 : :
2191 : : /*
2192 : : * As of 9.4, we now use an MVCC snapshot for this.
2193 : : */
2194 : : static char *
8219 tgl@sss.pgh.pa.us 2195 : 4382 : pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
2196 : : int prettyFlags, bool missing_ok)
2197 : : {
2198 : : HeapTuple tup;
2199 : : Form_pg_constraint conForm;
2200 : : StringInfoData buf;
2201 : : SysScanDesc scandesc;
2202 : : ScanKeyData scankey[1];
4566 bruce@momjian.us 2203 : 4382 : Snapshot snapshot = RegisterSnapshot(GetTransactionSnapshot());
2845 andres@anarazel.de 2204 : 4382 : Relation relation = table_open(ConstraintRelationId, AccessShareLock);
2205 : :
4596 simon@2ndQuadrant.co 2206 : 4382 : ScanKeyInit(&scankey[0],
2207 : : Anum_pg_constraint_oid,
2208 : : BTEqualStrategyNumber, F_OIDEQ,
2209 : : ObjectIdGetDatum(constraintId));
2210 : :
2211 : 4382 : scandesc = systable_beginscan(relation,
2212 : : ConstraintOidIndexId,
2213 : : true,
2214 : : snapshot,
2215 : : 1,
2216 : : scankey);
2217 : :
2218 : : /*
2219 : : * We later use the tuple with SysCacheGetAttr() as if we had obtained it
2220 : : * via SearchSysCache, which works fine.
2221 : : */
2222 : 4382 : tup = systable_getnext(scandesc);
2223 : :
2224 : 4382 : UnregisterSnapshot(snapshot);
2225 : :
3754 rhaas@postgresql.org 2226 [ + + ]: 4382 : if (!HeapTupleIsValid(tup))
2227 : : {
2228 [ + - ]: 4 : if (missing_ok)
2229 : : {
2230 : 4 : systable_endscan(scandesc);
2845 andres@anarazel.de 2231 : 4 : table_close(relation, AccessShareLock);
3754 rhaas@postgresql.org 2232 : 4 : return NULL;
2233 : : }
3441 tgl@sss.pgh.pa.us 2234 [ # # ]:UBC 0 : elog(ERROR, "could not find tuple for constraint %u", constraintId);
2235 : : }
2236 : :
8847 tgl@sss.pgh.pa.us 2237 :CBC 4378 : conForm = (Form_pg_constraint) GETSTRUCT(tup);
2238 : :
2239 : 4378 : initStringInfo(&buf);
2240 : :
4003 2241 [ + + ]: 4378 : if (fullCommand)
2242 : : {
3291 2243 [ + + ]: 468 : if (OidIsValid(conForm->conrelid))
2244 : : {
2245 : : /*
2246 : : * Currently, callers want ALTER TABLE (without ONLY) for CHECK
2247 : : * constraints, and other types of constraints don't inherit
2248 : : * anyway so it doesn't matter whether we say ONLY or not. Someday
2249 : : * we might need to let callers specify whether to put ONLY in the
2250 : : * command.
2251 : : */
2252 : 455 : appendStringInfo(&buf, "ALTER TABLE %s ADD CONSTRAINT %s ",
2253 : : generate_qualified_relation_name(conForm->conrelid),
2254 : 455 : quote_identifier(NameStr(conForm->conname)));
2255 : : }
2256 : : else
2257 : : {
2258 : : /* Must be a domain constraint */
2259 [ - + ]: 13 : Assert(OidIsValid(conForm->contypid));
2260 : 13 : appendStringInfo(&buf, "ALTER DOMAIN %s ADD CONSTRAINT %s ",
2261 : : generate_qualified_type_name(conForm->contypid),
2262 : 13 : quote_identifier(NameStr(conForm->conname)));
2263 : : }
2264 : : }
2265 : :
8847 2266 [ + + + + : 4378 : switch (conForm->contype)
- + - ]
2267 : : {
2268 : 463 : case CONSTRAINT_FOREIGN:
2269 : : {
2270 : : Datum val;
2271 : : bool isnull;
2272 : : const char *string;
2273 : :
2274 : : /* Start off the constraint definition */
4753 rhaas@postgresql.org 2275 : 463 : appendStringInfoString(&buf, "FOREIGN KEY (");
2276 : :
2277 : : /* Fetch and build referencing-column list */
1321 dgustafsson@postgres 2278 : 463 : val = SysCacheGetAttrNotNull(CONSTROID, tup,
2279 : : Anum_pg_constraint_conkey);
2280 : :
2281 : : /* If it is a temporal foreign key then it uses PERIOD. */
779 peter@eisentraut.org 2282 : 463 : decompile_column_index_array(val, conForm->conrelid, conForm->conperiod, &buf);
2283 : :
2284 : : /* add foreign relation name */
8828 bruce@momjian.us 2285 : 463 : appendStringInfo(&buf, ") REFERENCES %s(",
2286 : : generate_relation_name(conForm->confrelid,
2287 : : NIL));
2288 : :
2289 : : /* Fetch and build referenced-column list */
1321 dgustafsson@postgres 2290 : 463 : val = SysCacheGetAttrNotNull(CONSTROID, tup,
2291 : : Anum_pg_constraint_confkey);
2292 : :
779 peter@eisentraut.org 2293 : 463 : decompile_column_index_array(val, conForm->confrelid, conForm->conperiod, &buf);
2294 : :
4753 rhaas@postgresql.org 2295 : 463 : appendStringInfoChar(&buf, ')');
2296 : :
2297 : : /* Add match type */
8828 bruce@momjian.us 2298 [ + - + - ]: 463 : switch (conForm->confmatchtype)
2299 : : {
2300 : 21 : case FKCONSTR_MATCH_FULL:
2301 : 21 : string = " MATCH FULL";
2302 : 21 : break;
8828 bruce@momjian.us 2303 :UBC 0 : case FKCONSTR_MATCH_PARTIAL:
2304 : 0 : string = " MATCH PARTIAL";
2305 : 0 : break;
5254 tgl@sss.pgh.pa.us 2306 :CBC 442 : case FKCONSTR_MATCH_SIMPLE:
8828 bruce@momjian.us 2307 : 442 : string = "";
2308 : 442 : break;
8828 bruce@momjian.us 2309 :UBC 0 : default:
8502 tgl@sss.pgh.pa.us 2310 [ # # ]: 0 : elog(ERROR, "unrecognized confmatchtype: %d",
2311 : : conForm->confmatchtype);
2312 : : string = ""; /* keep compiler quiet */
2313 : : break;
2314 : : }
8314 neilc@samurai.com 2315 :CBC 463 : appendStringInfoString(&buf, string);
2316 : :
2317 : : /* Add ON UPDATE and ON DELETE clauses, if needed */
8828 bruce@momjian.us 2318 [ + - + + : 463 : switch (conForm->confupdtype)
- - ]
2319 : : {
2320 : 379 : case FKCONSTR_ACTION_NOACTION:
8494 2321 : 379 : string = NULL; /* suppress default */
8828 2322 : 379 : break;
8828 bruce@momjian.us 2323 :UBC 0 : case FKCONSTR_ACTION_RESTRICT:
2324 : 0 : string = "RESTRICT";
2325 : 0 : break;
8828 bruce@momjian.us 2326 :CBC 67 : case FKCONSTR_ACTION_CASCADE:
2327 : 67 : string = "CASCADE";
2328 : 67 : break;
2329 : 17 : case FKCONSTR_ACTION_SETNULL:
2330 : 17 : string = "SET NULL";
2331 : 17 : break;
8828 bruce@momjian.us 2332 :UBC 0 : case FKCONSTR_ACTION_SETDEFAULT:
2333 : 0 : string = "SET DEFAULT";
2334 : 0 : break;
2335 : 0 : default:
8502 tgl@sss.pgh.pa.us 2336 [ # # ]: 0 : elog(ERROR, "unrecognized confupdtype: %d",
2337 : : conForm->confupdtype);
2338 : : string = NULL; /* keep compiler quiet */
2339 : : break;
2340 : : }
8676 tgl@sss.pgh.pa.us 2341 [ + + ]:CBC 463 : if (string)
bruce@momjian.us 2342 : 84 : appendStringInfo(&buf, " ON UPDATE %s", string);
2343 : :
8828 2344 [ + - + + : 463 : switch (conForm->confdeltype)
+ - ]
2345 : : {
2346 : 380 : case FKCONSTR_ACTION_NOACTION:
8494 2347 : 380 : string = NULL; /* suppress default */
8828 2348 : 380 : break;
8828 bruce@momjian.us 2349 :UBC 0 : case FKCONSTR_ACTION_RESTRICT:
2350 : 0 : string = "RESTRICT";
2351 : 0 : break;
8828 bruce@momjian.us 2352 :CBC 67 : case FKCONSTR_ACTION_CASCADE:
2353 : 67 : string = "CASCADE";
2354 : 67 : break;
2355 : 12 : case FKCONSTR_ACTION_SETNULL:
2356 : 12 : string = "SET NULL";
2357 : 12 : break;
2358 : 4 : case FKCONSTR_ACTION_SETDEFAULT:
2359 : 4 : string = "SET DEFAULT";
2360 : 4 : break;
8828 bruce@momjian.us 2361 :UBC 0 : default:
8502 tgl@sss.pgh.pa.us 2362 [ # # ]: 0 : elog(ERROR, "unrecognized confdeltype: %d",
2363 : : conForm->confdeltype);
2364 : : string = NULL; /* keep compiler quiet */
2365 : : break;
2366 : : }
8676 tgl@sss.pgh.pa.us 2367 [ + + ]:CBC 463 : if (string)
bruce@momjian.us 2368 : 83 : appendStringInfo(&buf, " ON DELETE %s", string);
2369 : :
2370 : : /*
2371 : : * Add columns specified to SET NULL or SET DEFAULT if
2372 : : * provided.
2373 : : */
1793 peter@eisentraut.org 2374 : 463 : val = SysCacheGetAttr(CONSTROID, tup,
2375 : : Anum_pg_constraint_confdelsetcols, &isnull);
2376 [ + + ]: 463 : if (!isnull)
2377 : : {
1521 drowley@postgresql.o 2378 : 8 : appendStringInfoString(&buf, " (");
779 peter@eisentraut.org 2379 : 8 : decompile_column_index_array(val, conForm->conrelid, false, &buf);
1521 drowley@postgresql.o 2380 : 8 : appendStringInfoChar(&buf, ')');
2381 : : }
2382 : :
8828 bruce@momjian.us 2383 : 463 : break;
2384 : : }
8666 2385 : 2187 : case CONSTRAINT_PRIMARY:
2386 : : case CONSTRAINT_UNIQUE:
2387 : : {
2388 : : Datum val;
2389 : : Oid indexId;
2390 : : int keyatts;
2391 : : HeapTuple indtup;
2392 : :
2393 : : /* Start off the constraint definition */
2394 [ + + ]: 2187 : if (conForm->contype == CONSTRAINT_PRIMARY)
1736 peter@eisentraut.org 2395 : 1765 : appendStringInfoString(&buf, "PRIMARY KEY ");
2396 : : else
2397 : 422 : appendStringInfoString(&buf, "UNIQUE ");
2398 : :
2399 : 2187 : indexId = conForm->conindid;
2400 : :
2401 : 2187 : indtup = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexId));
2402 [ - + ]: 2187 : if (!HeapTupleIsValid(indtup))
1736 peter@eisentraut.org 2403 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for index %u", indexId);
1736 peter@eisentraut.org 2404 [ + + ]:CBC 2187 : if (conForm->contype == CONSTRAINT_UNIQUE &&
2405 [ - + ]: 422 : ((Form_pg_index) GETSTRUCT(indtup))->indnullsnotdistinct)
1736 peter@eisentraut.org 2406 :UBC 0 : appendStringInfoString(&buf, "NULLS NOT DISTINCT ");
2407 : :
1521 drowley@postgresql.o 2408 :CBC 2187 : appendStringInfoChar(&buf, '(');
2409 : :
2410 : : /* Fetch and build target column list */
1321 dgustafsson@postgres 2411 : 2187 : val = SysCacheGetAttrNotNull(CONSTROID, tup,
2412 : : Anum_pg_constraint_conkey);
2413 : :
779 peter@eisentraut.org 2414 : 2187 : keyatts = decompile_column_index_array(val, conForm->conrelid, false, &buf);
2415 [ + + ]: 2187 : if (conForm->conperiod)
2416 : 221 : appendStringInfoString(&buf, " WITHOUT OVERLAPS");
2417 : :
4753 rhaas@postgresql.org 2418 : 2187 : appendStringInfoChar(&buf, ')');
2419 : :
2420 : : /* Build including column list (from pg_index.indkeys) */
1321 dgustafsson@postgres 2421 : 2187 : val = SysCacheGetAttrNotNull(INDEXRELID, indtup,
2422 : : Anum_pg_index_indnatts);
2985 alvherre@alvh.no-ip. 2423 [ + + ]: 2187 : if (DatumGetInt32(val) > keyatts)
2424 : : {
2425 : : Datum cols;
2426 : : Datum *keys;
2427 : : int nKeys;
2428 : : int j;
2429 : :
3134 teodor@sigaev.ru 2430 : 48 : appendStringInfoString(&buf, " INCLUDE (");
2431 : :
1321 dgustafsson@postgres 2432 : 48 : cols = SysCacheGetAttrNotNull(INDEXRELID, indtup,
2433 : : Anum_pg_index_indkey);
2434 : :
1588 peter@eisentraut.org 2435 : 48 : deconstruct_array_builtin(DatumGetArrayTypeP(cols), INT2OID,
2436 : : &keys, NULL, &nKeys);
2437 : :
2985 alvherre@alvh.no-ip. 2438 [ + + ]: 144 : for (j = keyatts; j < nKeys; j++)
2439 : : {
2440 : : char *colName;
2441 : :
2442 : 96 : colName = get_attname(conForm->conrelid,
2443 : 96 : DatumGetInt16(keys[j]), false);
2444 [ + + ]: 96 : if (j > keyatts)
2445 : 48 : appendStringInfoString(&buf, ", ");
2446 : 96 : appendStringInfoString(&buf, quote_identifier(colName));
2447 : : }
2448 : :
3134 teodor@sigaev.ru 2449 : 48 : appendStringInfoChar(&buf, ')');
2450 : : }
2985 alvherre@alvh.no-ip. 2451 : 2187 : ReleaseSysCache(indtup);
2452 : :
2453 : : /* XXX why do we only print these bits if fullCommand? */
6963 tgl@sss.pgh.pa.us 2454 [ + + + - ]: 2187 : if (fullCommand && OidIsValid(indexId))
2455 : : {
2456 : 136 : char *options = flatten_reloptions(indexId);
2457 : : Oid tblspc;
2458 : :
7431 bruce@momjian.us 2459 [ - + ]: 136 : if (options)
2460 : : {
7431 bruce@momjian.us 2461 :UBC 0 : appendStringInfo(&buf, " WITH (%s)", options);
2462 : 0 : pfree(options);
2463 : : }
2464 : :
2465 : : /*
2466 : : * Print the tablespace, unless it's the database default.
2467 : : * This is to help ALTER TABLE usage of this facility,
2468 : : * which needs this behavior to recreate exact catalog
2469 : : * state.
2470 : : */
6963 tgl@sss.pgh.pa.us 2471 :CBC 136 : tblspc = get_rel_tablespace(indexId);
2472 [ + + ]: 136 : if (OidIsValid(tblspc))
2473 : 16 : appendStringInfo(&buf, " USING INDEX TABLESPACE %s",
3424 2474 : 16 : quote_identifier(get_tablespace_name(tblspc)));
2475 : : }
2476 : :
8666 bruce@momjian.us 2477 : 2187 : break;
2478 : : }
2479 : 1319 : case CONSTRAINT_CHECK:
2480 : : {
2481 : : Datum val;
2482 : : char *conbin;
2483 : : char *consrc;
2484 : : Node *expr;
2485 : : List *context;
2486 : :
2487 : : /* Fetch constraint expression in parsetree form */
1321 dgustafsson@postgres 2488 : 1319 : val = SysCacheGetAttrNotNull(CONSTROID, tup,
2489 : : Anum_pg_constraint_conbin);
2490 : :
6799 tgl@sss.pgh.pa.us 2491 : 1319 : conbin = TextDatumGetCString(val);
8534 bruce@momjian.us 2492 : 1319 : expr = stringToNode(conbin);
2493 : :
2494 : : /* Set up deparsing context for Var nodes in constraint */
2495 [ + + ]: 1319 : if (conForm->conrelid != InvalidOid)
2496 : : {
2497 : : /* relation constraint */
5847 tgl@sss.pgh.pa.us 2498 : 1172 : context = deparse_context_for(get_relation_name(conForm->conrelid),
2499 : : conForm->conrelid);
2500 : : }
2501 : : else
2502 : : {
2503 : : /* domain constraint --- can't have Vars */
8433 2504 : 147 : context = NIL;
2505 : : }
2506 : :
8499 2507 : 1319 : consrc = deparse_expression_pretty(expr, context, false, false,
2508 : : prettyFlags, 0);
2509 : :
2510 : : /*
2511 : : * Now emit the constraint definition, adding NO INHERIT if
2512 : : * necessary.
2513 : : *
2514 : : * There are cases where the constraint expression will be
2515 : : * fully parenthesized and we don't need the outer parens ...
2516 : : * but there are other cases where we do need 'em. Be
2517 : : * conservative for now.
2518 : : *
2519 : : * Note that simply checking for leading '(' and trailing ')'
2520 : : * would NOT be good enough, consider "(x > 0) AND (y > 0)".
2521 : : */
5217 alvherre@alvh.no-ip. 2522 : 1319 : appendStringInfo(&buf, "CHECK (%s)%s",
2523 : : consrc,
2524 [ + + ]: 1319 : conForm->connoinherit ? " NO INHERIT" : "");
8666 bruce@momjian.us 2525 : 1319 : break;
2526 : : }
727 alvherre@alvh.no-ip. 2527 : 292 : case CONSTRAINT_NOTNULL:
2528 : : {
2529 [ + + ]: 292 : if (conForm->conrelid)
2530 : : {
2531 : : AttrNumber attnum;
2532 : :
2533 : 232 : attnum = extractNotNullColumn(tup);
2534 : :
2535 : 232 : appendStringInfo(&buf, "NOT NULL %s",
2536 : 232 : quote_identifier(get_attname(conForm->conrelid,
2537 : : attnum, false)));
2538 [ - + ]: 232 : if (((Form_pg_constraint) GETSTRUCT(tup))->connoinherit)
727 alvherre@alvh.no-ip. 2539 :UBC 0 : appendStringInfoString(&buf, " NO INHERIT");
2540 : : }
727 alvherre@alvh.no-ip. 2541 [ + - ]:CBC 60 : else if (conForm->contypid)
2542 : : {
2543 : : /* conkey is null for domain not-null constraints */
2544 : 60 : appendStringInfoString(&buf, "NOT NULL");
2545 : : }
2546 : 292 : break;
2547 : : }
2548 : :
6136 tgl@sss.pgh.pa.us 2549 :UBC 0 : case CONSTRAINT_TRIGGER:
2550 : :
2551 : : /*
2552 : : * There isn't an ALTER TABLE syntax for creating a user-defined
2553 : : * constraint trigger, but it seems better to print something than
2554 : : * throw an error; if we throw error then this function couldn't
2555 : : * safely be applied to all rows of pg_constraint.
2556 : : */
4753 rhaas@postgresql.org 2557 : 0 : appendStringInfoString(&buf, "TRIGGER");
6136 tgl@sss.pgh.pa.us 2558 : 0 : break;
6177 tgl@sss.pgh.pa.us 2559 :CBC 117 : case CONSTRAINT_EXCLUSION:
2560 : : {
6096 bruce@momjian.us 2561 : 117 : Oid indexOid = conForm->conindid;
2562 : : Datum val;
2563 : : Datum *elems;
2564 : : int nElems;
2565 : : int i;
2566 : : Oid *operators;
2567 : :
2568 : : /* Extract operator OIDs from the pg_constraint tuple */
1321 dgustafsson@postgres 2569 : 117 : val = SysCacheGetAttrNotNull(CONSTROID, tup,
2570 : : Anum_pg_constraint_conexclop);
2571 : :
1588 peter@eisentraut.org 2572 : 117 : deconstruct_array_builtin(DatumGetArrayTypeP(val), OIDOID,
2573 : : &elems, NULL, &nElems);
2574 : :
80 michael@paquier.xyz 2575 :GNC 117 : operators = palloc_array(Oid, nElems);
6177 tgl@sss.pgh.pa.us 2576 [ + + ]:CBC 336 : for (i = 0; i < nElems; i++)
2577 : 219 : operators[i] = DatumGetObjectId(elems[i]);
2578 : :
2579 : : /* pg_get_indexdef_worker does the rest */
2580 : : /* suppress tablespace because pg_dump wants it that way */
2581 : 117 : appendStringInfoString(&buf,
2582 : 117 : pg_get_indexdef_worker(indexOid,
2583 : : 0,
2584 : : operators,
2585 : : false,
2586 : : false,
2587 : : false,
2588 : : false,
2589 : : prettyFlags,
2590 : : false));
2591 : 117 : break;
2592 : : }
8847 tgl@sss.pgh.pa.us 2593 :UBC 0 : default:
8452 peter_e@gmx.net 2594 [ # # ]: 0 : elog(ERROR, "invalid constraint type \"%c\"", conForm->contype);
2595 : : break;
2596 : : }
2597 : :
6308 tgl@sss.pgh.pa.us 2598 [ + + ]:CBC 4378 : if (conForm->condeferrable)
4753 rhaas@postgresql.org 2599 : 90 : appendStringInfoString(&buf, " DEFERRABLE");
6308 tgl@sss.pgh.pa.us 2600 [ + + ]: 4378 : if (conForm->condeferred)
4753 rhaas@postgresql.org 2601 : 39 : appendStringInfoString(&buf, " INITIALLY DEFERRED");
2602 : :
2603 : : /* Validated status is irrelevant when the constraint is NOT ENFORCED. */
663 peter@eisentraut.org 2604 [ + + ]: 4378 : if (!conForm->conenforced)
2605 : 57 : appendStringInfoString(&buf, " NOT ENFORCED");
2606 [ + + ]: 4321 : else if (!conForm->convalidated)
5635 alvherre@alvh.no-ip. 2607 : 130 : appendStringInfoString(&buf, " NOT VALID");
2608 : :
2609 : : /* Cleanup */
4596 simon@2ndQuadrant.co 2610 : 4378 : systable_endscan(scandesc);
2845 andres@anarazel.de 2611 : 4378 : table_close(relation, AccessShareLock);
2612 : :
8219 tgl@sss.pgh.pa.us 2613 : 4378 : return buf.data;
2614 : : }
2615 : :
2616 : :
2617 : : /*
2618 : : * Convert an int16[] Datum into a comma-separated list of column names
2619 : : * for the indicated relation; append the list to buf. Returns the number
2620 : : * of keys.
2621 : : */
2622 : : static int
8847 2623 : 3121 : decompile_column_index_array(Datum column_index_array, Oid relId,
2624 : : bool withPeriod, StringInfo buf)
2625 : : {
2626 : : Datum *keys;
2627 : : int nKeys;
2628 : : int j;
2629 : :
2630 : : /* Extract data from array of int16 */
1588 peter@eisentraut.org 2631 : 3121 : deconstruct_array_builtin(DatumGetArrayTypeP(column_index_array), INT2OID,
2632 : : &keys, NULL, &nKeys);
2633 : :
8847 tgl@sss.pgh.pa.us 2634 [ + + ]: 7531 : for (j = 0; j < nKeys; j++)
2635 : : {
2636 : : char *colName;
2637 : :
3188 alvherre@alvh.no-ip. 2638 : 4410 : colName = get_attname(relId, DatumGetInt16(keys[j]), false);
2639 : :
8847 tgl@sss.pgh.pa.us 2640 [ + + ]: 4410 : if (j == 0)
8314 neilc@samurai.com 2641 : 3121 : appendStringInfoString(buf, quote_identifier(colName));
2642 : : else
779 peter@eisentraut.org 2643 [ + + ]: 1423 : appendStringInfo(buf, ", %s%s",
2644 [ + + ]: 134 : (withPeriod && j == nKeys - 1) ? "PERIOD " : "",
2645 : : quote_identifier(colName));
2646 : : }
2647 : :
2985 alvherre@alvh.no-ip. 2648 : 3121 : return nKeys;
2649 : : }
2650 : :
2651 : :
2652 : : /* ----------
2653 : : * pg_get_expr - Decompile an expression tree
2654 : : *
2655 : : * Input: an expression tree in nodeToString form, and a relation OID
2656 : : *
2657 : : * Output: reverse-listed expression
2658 : : *
2659 : : * Currently, the expression can only refer to a single relation, namely
2660 : : * the one specified by the second parameter. This is sufficient for
2661 : : * partial indexes, column default expressions, etc. We also support
2662 : : * Var-free expressions, for which the OID can be InvalidOid.
2663 : : *
2664 : : * If the OID is nonzero but not actually valid, don't throw an error,
2665 : : * just return NULL. This is a bit questionable, but it's what we've
2666 : : * done historically, and it can help avoid unwanted failures when
2667 : : * examining catalog entries for just-deleted relations.
2668 : : *
2669 : : * We expect this function to work, or throw a reasonably clean error,
2670 : : * for any node tree that can appear in a catalog pg_node_tree column.
2671 : : * Query trees, such as those appearing in pg_rewrite.ev_action, are
2672 : : * not supported. Nor are expressions in more than one relation, which
2673 : : * can appear in places like pg_rewrite.ev_qual.
2674 : : * ----------
2675 : : */
2676 : : Datum
9243 tgl@sss.pgh.pa.us 2677 : 5221 : pg_get_expr(PG_FUNCTION_ARGS)
2678 : : {
3525 noah@leadboat.com 2679 : 5221 : text *expr = PG_GETARG_TEXT_PP(0);
8494 bruce@momjian.us 2680 : 5221 : Oid relid = PG_GETARG_OID(1);
2681 : : text *result;
2682 : : int prettyFlags;
2683 : :
5023 tgl@sss.pgh.pa.us 2684 : 5221 : prettyFlags = PRETTYFLAG_INDENT;
2685 : :
1000 2686 : 5221 : result = pg_get_expr_worker(expr, relid, prettyFlags);
2687 [ + - ]: 5221 : if (result)
2688 : 5221 : PG_RETURN_TEXT_P(result);
2689 : : else
1000 tgl@sss.pgh.pa.us 2690 :UBC 0 : PG_RETURN_NULL();
2691 : : }
2692 : :
2693 : : Datum
8499 tgl@sss.pgh.pa.us 2694 :CBC 496 : pg_get_expr_ext(PG_FUNCTION_ARGS)
2695 : : {
3525 noah@leadboat.com 2696 : 496 : text *expr = PG_GETARG_TEXT_PP(0);
8494 bruce@momjian.us 2697 : 496 : Oid relid = PG_GETARG_OID(1);
8499 tgl@sss.pgh.pa.us 2698 : 496 : bool pretty = PG_GETARG_BOOL(2);
2699 : : text *result;
2700 : : int prettyFlags;
2701 : :
1683 2702 [ + - ]: 496 : prettyFlags = GET_PRETTY_FLAGS(pretty);
2703 : :
1000 2704 : 496 : result = pg_get_expr_worker(expr, relid, prettyFlags);
2705 [ + - ]: 496 : if (result)
2706 : 496 : PG_RETURN_TEXT_P(result);
2707 : : else
1000 tgl@sss.pgh.pa.us 2708 :UBC 0 : PG_RETURN_NULL();
2709 : : }
2710 : :
2711 : : static text *
1000 tgl@sss.pgh.pa.us 2712 :CBC 5717 : pg_get_expr_worker(text *expr, Oid relid, int prettyFlags)
2713 : : {
2714 : : Node *node;
2715 : : Node *tst;
2716 : : Relids relids;
2717 : : List *context;
2718 : : char *exprstr;
2719 : 5717 : Relation rel = NULL;
2720 : : char *str;
2721 : :
2722 : : /* Convert input pg_node_tree (really TEXT) object to C string */
6799 2723 : 5717 : exprstr = text_to_cstring(expr);
2724 : :
2725 : : /* Convert expression to node tree */
9243 2726 : 5717 : node = (Node *) stringToNode(exprstr);
2727 : :
6372 2728 : 5717 : pfree(exprstr);
2729 : :
2730 : : /*
2731 : : * Throw error if the input is a querytree rather than an expression tree.
2732 : : * While we could support queries here, there seems no very good reason
2733 : : * to. In most such catalog columns, we'll see a List of Query nodes, or
2734 : : * even nested Lists, so drill down to a non-List node before checking.
2735 : : */
1761 2736 : 5717 : tst = node;
2737 [ + - - + ]: 5717 : while (tst && IsA(tst, List))
1761 tgl@sss.pgh.pa.us 2738 :UBC 0 : tst = linitial((List *) tst);
1761 tgl@sss.pgh.pa.us 2739 [ + - - + ]:CBC 5717 : if (tst && IsA(tst, Query))
1761 tgl@sss.pgh.pa.us 2740 [ # # ]:UBC 0 : ereport(ERROR,
2741 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
2742 : : errmsg("input is a query, not an expression")));
2743 : :
2744 : : /*
2745 : : * Throw error if the expression contains Vars we won't be able to
2746 : : * deparse.
2747 : : */
1761 tgl@sss.pgh.pa.us 2748 :CBC 5717 : relids = pull_varnos(NULL, node);
2749 [ + + ]: 5717 : if (OidIsValid(relid))
2750 : : {
2751 [ - + ]: 5653 : if (!bms_is_subset(relids, bms_make_singleton(1)))
1761 tgl@sss.pgh.pa.us 2752 [ # # ]:UBC 0 : ereport(ERROR,
2753 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
2754 : : errmsg("expression contains variables of more than one relation")));
2755 : : }
2756 : : else
2757 : : {
1761 tgl@sss.pgh.pa.us 2758 [ - + ]:CBC 64 : if (!bms_is_empty(relids))
1761 tgl@sss.pgh.pa.us 2759 [ # # ]:UBC 0 : ereport(ERROR,
2760 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
2761 : : errmsg("expression contains variables")));
2762 : : }
2763 : :
2764 : : /*
2765 : : * Prepare deparse context if needed. If we are deparsing with a relid,
2766 : : * we need to transiently open and lock the rel, to make sure it won't go
2767 : : * away underneath us. (set_relation_column_names would lock it anyway,
2768 : : * so this isn't really introducing any new behavior.)
2769 : : */
6372 tgl@sss.pgh.pa.us 2770 [ + + ]:CBC 5717 : if (OidIsValid(relid))
2771 : : {
1000 2772 : 5653 : rel = try_relation_open(relid, AccessShareLock);
2773 [ - + ]: 5653 : if (rel == NULL)
1000 tgl@sss.pgh.pa.us 2774 :UBC 0 : return NULL;
1000 tgl@sss.pgh.pa.us 2775 :CBC 5653 : context = deparse_context_for(RelationGetRelationName(rel), relid);
2776 : : }
2777 : : else
6372 2778 : 64 : context = NIL;
2779 : :
2780 : : /* Deparse */
8499 2781 : 5717 : str = deparse_expression_pretty(node, context, false, false,
2782 : : prettyFlags, 0);
2783 : :
1000 2784 [ + + ]: 5717 : if (rel != NULL)
2785 : 5653 : relation_close(rel, AccessShareLock);
2786 : :
6372 2787 : 5717 : return string_to_text(str);
2788 : : }
2789 : :
2790 : :
2791 : : /* ----------
2792 : : * pg_get_userbyid - Get a user name by roleid and
2793 : : * fallback to 'unknown (OID=n)'
2794 : : * ----------
2795 : : */
2796 : : Datum
9641 2797 : 1215 : pg_get_userbyid(PG_FUNCTION_ARGS)
2798 : : {
7800 2799 : 1215 : Oid roleid = PG_GETARG_OID(0);
2800 : : Name result;
2801 : : HeapTuple roletup;
2802 : : Form_pg_authid role_rec;
2803 : :
2804 : : /*
2805 : : * Allocate space for the result
2806 : : */
9641 2807 : 1215 : result = (Name) palloc(NAMEDATALEN);
9860 bruce@momjian.us 2808 : 1215 : memset(NameStr(*result), 0, NAMEDATALEN);
2809 : :
2810 : : /*
2811 : : * Get the pg_authid entry and print the result
2812 : : */
6108 rhaas@postgresql.org 2813 : 1215 : roletup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
7800 tgl@sss.pgh.pa.us 2814 [ + - ]: 1215 : if (HeapTupleIsValid(roletup))
2815 : : {
2816 : 1215 : role_rec = (Form_pg_authid) GETSTRUCT(roletup);
2278 peter@eisentraut.org 2817 : 1215 : *result = role_rec->rolname;
7800 tgl@sss.pgh.pa.us 2818 : 1215 : ReleaseSysCache(roletup);
2819 : : }
2820 : : else
7800 tgl@sss.pgh.pa.us 2821 :UBC 0 : sprintf(NameStr(*result), "unknown (OID=%u)", roleid);
2822 : :
9641 tgl@sss.pgh.pa.us 2823 :CBC 1215 : PG_RETURN_NAME(result);
2824 : : }
2825 : :
2826 : :
2827 : : /*
2828 : : * pg_get_serial_sequence
2829 : : * Get the name of the sequence used by an identity or serial column,
2830 : : * formatted suitably for passing to setval, nextval or currval.
2831 : : * First parameter is not treated as double-quoted, second parameter
2832 : : * is --- see documentation for reason.
2833 : : */
2834 : : Datum
8168 2835 : 8 : pg_get_serial_sequence(PG_FUNCTION_ARGS)
2836 : : {
3525 noah@leadboat.com 2837 : 8 : text *tablename = PG_GETARG_TEXT_PP(0);
6799 tgl@sss.pgh.pa.us 2838 : 8 : text *columnname = PG_GETARG_TEXT_PP(1);
2839 : : RangeVar *tablerv;
2840 : : Oid tableOid;
2841 : : char *column;
2842 : : AttrNumber attnum;
8103 bruce@momjian.us 2843 : 8 : Oid sequenceId = InvalidOid;
2844 : : Relation depRel;
2845 : : ScanKeyData key[3];
2846 : : SysScanDesc scan;
2847 : : HeapTuple tup;
2848 : :
2849 : : /* Look up table name. Can't lock it - we might not have privileges. */
7832 neilc@samurai.com 2850 : 8 : tablerv = makeRangeVarFromNameList(textToQualifiedNameList(tablename));
5454 rhaas@postgresql.org 2851 : 8 : tableOid = RangeVarGetRelid(tablerv, NoLock, false);
2852 : :
2853 : : /* Get the number of the column */
6799 tgl@sss.pgh.pa.us 2854 : 8 : column = text_to_cstring(columnname);
2855 : :
8168 2856 : 8 : attnum = get_attnum(tableOid, column);
2857 [ - + ]: 8 : if (attnum == InvalidAttrNumber)
8168 tgl@sss.pgh.pa.us 2858 [ # # ]:UBC 0 : ereport(ERROR,
2859 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
2860 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
2861 : : column, tablerv->relname)));
2862 : :
2863 : : /* Search the dependency table for the dependent sequence */
2845 andres@anarazel.de 2864 :CBC 8 : depRel = table_open(DependRelationId, AccessShareLock);
2865 : :
8168 tgl@sss.pgh.pa.us 2866 : 8 : ScanKeyInit(&key[0],
2867 : : Anum_pg_depend_refclassid,
2868 : : BTEqualStrategyNumber, F_OIDEQ,
2869 : : ObjectIdGetDatum(RelationRelationId));
2870 : 8 : ScanKeyInit(&key[1],
2871 : : Anum_pg_depend_refobjid,
2872 : : BTEqualStrategyNumber, F_OIDEQ,
2873 : : ObjectIdGetDatum(tableOid));
2874 : 8 : ScanKeyInit(&key[2],
2875 : : Anum_pg_depend_refobjsubid,
2876 : : BTEqualStrategyNumber, F_INT4EQ,
2877 : : Int32GetDatum(attnum));
2878 : :
7875 2879 : 8 : scan = systable_beginscan(depRel, DependReferenceIndexId, true,
2880 : : NULL, 3, key);
2881 : :
8168 2882 [ + - ]: 18 : while (HeapTupleIsValid(tup = systable_getnext(scan)))
2883 : : {
8103 bruce@momjian.us 2884 : 18 : Form_pg_depend deprec = (Form_pg_depend) GETSTRUCT(tup);
2885 : :
2886 : : /*
2887 : : * Look for an auto dependency (serial column) or internal dependency
2888 : : * (identity column) of a sequence on a column. (We need the relkind
2889 : : * test because indexes can also have auto dependencies on columns.)
2890 : : */
7875 tgl@sss.pgh.pa.us 2891 [ + + ]: 18 : if (deprec->classid == RelationRelationId &&
8168 2892 [ + - ]: 8 : deprec->objsubid == 0 &&
3338 peter_e@gmx.net 2893 [ + + ]: 8 : (deprec->deptype == DEPENDENCY_AUTO ||
2894 [ + - + - ]: 12 : deprec->deptype == DEPENDENCY_INTERNAL) &&
7300 tgl@sss.pgh.pa.us 2895 : 8 : get_rel_relkind(deprec->objid) == RELKIND_SEQUENCE)
2896 : : {
8168 2897 : 8 : sequenceId = deprec->objid;
2898 : 8 : break;
2899 : : }
2900 : : }
2901 : :
2902 : 8 : systable_endscan(scan);
2845 andres@anarazel.de 2903 : 8 : table_close(depRel, AccessShareLock);
2904 : :
8168 tgl@sss.pgh.pa.us 2905 [ + - ]: 8 : if (OidIsValid(sequenceId))
2906 : : {
2907 : : char *result;
2908 : :
4003 2909 : 8 : result = generate_qualified_relation_name(sequenceId);
2910 : :
8168 2911 : 8 : PG_RETURN_TEXT_P(string_to_text(result));
2912 : : }
2913 : :
8168 tgl@sss.pgh.pa.us 2914 :UBC 0 : PG_RETURN_NULL();
2915 : : }
2916 : :
2917 : :
2918 : : /*
2919 : : * pg_get_functiondef
2920 : : * Returns the complete "CREATE OR REPLACE FUNCTION ..." statement for
2921 : : * the specified function.
2922 : : *
2923 : : * Note: if you change the output format of this function, be careful not
2924 : : * to break psql's rules (in \ef and \sf) for identifying the start of the
2925 : : * function body. To wit: the function body starts on a line that begins with
2926 : : * "AS ", "BEGIN ", or "RETURN ", and no preceding line will look like that.
2927 : : */
2928 : : Datum
6634 tgl@sss.pgh.pa.us 2929 :CBC 115 : pg_get_functiondef(PG_FUNCTION_ARGS)
2930 : : {
2931 : 115 : Oid funcid = PG_GETARG_OID(0);
2932 : : StringInfoData buf;
2933 : : StringInfoData dq;
2934 : : HeapTuple proctup;
2935 : : Form_pg_proc proc;
2936 : : bool isfunction;
2937 : : Datum tmp;
2938 : : bool isnull;
2939 : : const char *prosrc;
2940 : : const char *name;
2941 : : const char *nsp;
2942 : : float4 procost;
2943 : : int oldlen;
2944 : :
2945 : 115 : initStringInfo(&buf);
2946 : :
2947 : : /* Look up the function */
6108 rhaas@postgresql.org 2948 : 115 : proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
6634 tgl@sss.pgh.pa.us 2949 [ + + ]: 115 : if (!HeapTupleIsValid(proctup))
3754 rhaas@postgresql.org 2950 : 4 : PG_RETURN_NULL();
2951 : :
6634 tgl@sss.pgh.pa.us 2952 : 111 : proc = (Form_pg_proc) GETSTRUCT(proctup);
2953 : 111 : name = NameStr(proc->proname);
2954 : :
3170 peter_e@gmx.net 2955 [ - + ]: 111 : if (proc->prokind == PROKIND_AGGREGATE)
6634 tgl@sss.pgh.pa.us 2956 [ # # ]:UBC 0 : ereport(ERROR,
2957 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2958 : : errmsg("\"%s\" is an aggregate function", name)));
2959 : :
3170 peter_e@gmx.net 2960 :CBC 111 : isfunction = (proc->prokind != PROKIND_PROCEDURE);
2961 : :
2962 : : /*
2963 : : * We always qualify the function name, to ensure the right function gets
2964 : : * replaced.
2965 : : */
1927 tgl@sss.pgh.pa.us 2966 : 111 : nsp = get_namespace_name_or_temp(proc->pronamespace);
3187 peter_e@gmx.net 2967 [ + + ]: 111 : appendStringInfo(&buf, "CREATE OR REPLACE %s %s(",
2968 : : isfunction ? "FUNCTION" : "PROCEDURE",
2969 : : quote_qualified_identifier(nsp, name));
6545 2970 : 111 : (void) print_function_arguments(&buf, proctup, false, true);
3187 2971 : 111 : appendStringInfoString(&buf, ")\n");
2972 [ + + ]: 111 : if (isfunction)
2973 : : {
2974 : 98 : appendStringInfoString(&buf, " RETURNS ");
2975 : 98 : print_function_rettype(&buf, proctup);
2976 : 98 : appendStringInfoChar(&buf, '\n');
2977 : : }
2978 : :
4211 2979 : 111 : print_function_trftypes(&buf, proctup);
2980 : :
3187 2981 : 111 : appendStringInfo(&buf, " LANGUAGE %s\n",
3424 tgl@sss.pgh.pa.us 2982 : 111 : quote_identifier(get_language_name(proc->prolang, false)));
2983 : :
2984 : : /* Emit some miscellaneous options on one line */
6634 2985 : 111 : oldlen = buf.len;
2986 : :
3170 peter_e@gmx.net 2987 [ - + ]: 111 : if (proc->prokind == PROKIND_WINDOW)
6518 tgl@sss.pgh.pa.us 2988 :UBC 0 : appendStringInfoString(&buf, " WINDOW");
6634 tgl@sss.pgh.pa.us 2989 [ + + + - ]:CBC 111 : switch (proc->provolatile)
2990 : : {
2991 : 8 : case PROVOLATILE_IMMUTABLE:
2992 : 8 : appendStringInfoString(&buf, " IMMUTABLE");
2993 : 8 : break;
2994 : 20 : case PROVOLATILE_STABLE:
2995 : 20 : appendStringInfoString(&buf, " STABLE");
2996 : 20 : break;
2997 : 83 : case PROVOLATILE_VOLATILE:
2998 : 83 : break;
2999 : : }
3000 : :
3845 rhaas@postgresql.org 3001 [ + - + - ]: 111 : switch (proc->proparallel)
3002 : : {
3003 : 17 : case PROPARALLEL_SAFE:
3004 : 17 : appendStringInfoString(&buf, " PARALLEL SAFE");
3005 : 17 : break;
3845 rhaas@postgresql.org 3006 :UBC 0 : case PROPARALLEL_RESTRICTED:
3007 : 0 : appendStringInfoString(&buf, " PARALLEL RESTRICTED");
3008 : 0 : break;
3845 rhaas@postgresql.org 3009 :CBC 94 : case PROPARALLEL_UNSAFE:
3010 : 94 : break;
3011 : : }
3012 : :
6634 tgl@sss.pgh.pa.us 3013 [ + + ]: 111 : if (proc->proisstrict)
3014 : 32 : appendStringInfoString(&buf, " STRICT");
3015 [ + + ]: 111 : if (proc->prosecdef)
3016 : 4 : appendStringInfoString(&buf, " SECURITY DEFINER");
4179 3017 [ - + ]: 111 : if (proc->proleakproof)
4179 tgl@sss.pgh.pa.us 3018 :UBC 0 : appendStringInfoString(&buf, " LEAKPROOF");
3019 : :
3020 : : /* This code for the default cost and rows should match functioncmds.c */
6634 tgl@sss.pgh.pa.us 3021 [ + - ]:CBC 111 : if (proc->prolang == INTERNALlanguageId ||
3022 [ + + ]: 111 : proc->prolang == ClanguageId)
3023 : 5 : procost = 1;
3024 : : else
3025 : 106 : procost = 100;
3026 [ + + ]: 111 : if (proc->procost != procost)
3027 : 4 : appendStringInfo(&buf, " COST %g", proc->procost);
3028 : :
3029 [ + + - + ]: 111 : if (proc->prorows > 0 && proc->prorows != 1000)
6634 tgl@sss.pgh.pa.us 3030 :UBC 0 : appendStringInfo(&buf, " ROWS %g", proc->prorows);
3031 : :
2826 tgl@sss.pgh.pa.us 3032 [ - + ]:CBC 111 : if (proc->prosupport)
3033 : : {
3034 : : Oid argtypes[1];
3035 : :
3036 : : /*
3037 : : * We should qualify the support function's name if it wouldn't be
3038 : : * resolved by lookup in the current search path.
3039 : : */
2826 tgl@sss.pgh.pa.us 3040 :UBC 0 : argtypes[0] = INTERNALOID;
3041 : 0 : appendStringInfo(&buf, " SUPPORT %s",
3042 : : generate_function_name(proc->prosupport, 1,
3043 : : NIL, argtypes,
3044 : : false, NULL, false));
3045 : : }
3046 : :
6634 tgl@sss.pgh.pa.us 3047 [ + + ]:CBC 111 : if (oldlen != buf.len)
3048 : 41 : appendStringInfoChar(&buf, '\n');
3049 : :
3050 : : /* Emit any proconfig options, one per line */
3051 : 111 : tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_proconfig, &isnull);
3052 [ + + ]: 111 : if (!isnull)
3053 : : {
6356 bruce@momjian.us 3054 : 4 : ArrayType *a = DatumGetArrayTypeP(tmp);
3055 : : int i;
3056 : :
6634 tgl@sss.pgh.pa.us 3057 [ - + ]: 4 : Assert(ARR_ELEMTYPE(a) == TEXTOID);
3058 [ - + ]: 4 : Assert(ARR_NDIM(a) == 1);
3059 [ - + ]: 4 : Assert(ARR_LBOUND(a)[0] == 1);
3060 : :
3061 [ + + ]: 28 : for (i = 1; i <= ARR_DIMS(a)[0]; i++)
3062 : : {
3063 : : Datum d;
3064 : :
3065 : 24 : d = array_ref(a, 1, &i,
3066 : : -1 /* varlenarray */ ,
3067 : : -1 /* TEXT's typlen */ ,
3068 : : false /* TEXT's typbyval */ ,
3069 : : TYPALIGN_INT /* TEXT's typalign */ ,
3070 : : &isnull);
3071 [ + - ]: 24 : if (!isnull)
3072 : : {
3073 : 24 : char *configitem = TextDatumGetCString(d);
3074 : : char *pos;
3075 : :
3076 : 24 : pos = strchr(configitem, '=');
3077 [ - + ]: 24 : if (pos == NULL)
6634 tgl@sss.pgh.pa.us 3078 :UBC 0 : continue;
6634 tgl@sss.pgh.pa.us 3079 :CBC 24 : *pos++ = '\0';
3080 : :
3081 : 24 : appendStringInfo(&buf, " SET %s TO ",
3082 : : quote_identifier(configitem));
3083 : :
3084 : : /*
3085 : : * Variables that are marked GUC_LIST_QUOTE were already fully
3086 : : * quoted by flatten_set_variable_args() before they were put
3087 : : * into the proconfig array. However, because the quoting
3088 : : * rules used there aren't exactly like SQL's, we have to
3089 : : * break the list value apart and then quote the elements as
3090 : : * string literals. (The elements may be double-quoted as-is,
3091 : : * but we can't just feed them to the SQL parser; it would do
3092 : : * the wrong thing with elements that are zero-length or
3093 : : * longer than NAMEDATALEN.) Also, we need a special case for
3094 : : * empty lists.
3095 : : *
3096 : : * Variables that are not so marked should just be emitted as
3097 : : * simple string literals. If the variable is not known to
3098 : : * guc.c, we'll do that; this makes it unsafe to use
3099 : : * GUC_LIST_QUOTE for extension variables.
3100 : : */
3151 3101 [ + + ]: 24 : if (GetConfigOptionFlags(configitem, true) & GUC_LIST_QUOTE)
3102 : : {
3103 : : List *namelist;
3104 : : ListCell *lc;
3105 : :
3106 : : /* Parse string into list of identifiers */
3019 3107 [ - + ]: 12 : if (!SplitGUCList(pos, ',', &namelist))
3108 : : {
3109 : : /* this shouldn't fail really */
3019 tgl@sss.pgh.pa.us 3110 [ # # ]:UBC 0 : elog(ERROR, "invalid list syntax in proconfig item");
3111 : : }
3112 : : /* Special case: represent an empty list as NULL */
366 tgl@sss.pgh.pa.us 3113 [ + + ]:CBC 12 : if (namelist == NIL)
3114 : 4 : appendStringInfoString(&buf, "NULL");
3019 3115 [ + + + + : 32 : foreach(lc, namelist)
+ + ]
3116 : : {
3117 : 20 : char *curname = (char *) lfirst(lc);
3118 : :
3119 : 20 : simple_quote_literal(&buf, curname);
2670 3120 [ + + ]: 20 : if (lnext(namelist, lc))
3019 3121 : 12 : appendStringInfoString(&buf, ", ");
3122 : : }
3123 : : }
3124 : : else
6634 3125 : 12 : simple_quote_literal(&buf, pos);
3126 : 24 : appendStringInfoChar(&buf, '\n');
3127 : : }
3128 : : }
3129 : : }
3130 : :
3131 : : /* And finally the function definition ... */
1892 3132 : 111 : (void) SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prosqlbody, &isnull);
2038 peter@eisentraut.org 3133 [ + + + + ]: 111 : if (proc->prolang == SQLlanguageId && !isnull)
3134 : : {
3135 : 79 : print_function_sqlbody(&buf, proctup);
3136 : : }
3137 : : else
3138 : : {
2003 tgl@sss.pgh.pa.us 3139 : 32 : appendStringInfoString(&buf, "AS ");
3140 : :
3141 : 32 : tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_probin, &isnull);
3142 [ + + ]: 32 : if (!isnull)
3143 : : {
3144 : 5 : simple_quote_literal(&buf, TextDatumGetCString(tmp));
3145 : 5 : appendStringInfoString(&buf, ", "); /* assume prosrc isn't null */
3146 : : }
3147 : :
1321 dgustafsson@postgres 3148 : 32 : tmp = SysCacheGetAttrNotNull(PROCOID, proctup, Anum_pg_proc_prosrc);
2003 tgl@sss.pgh.pa.us 3149 : 32 : prosrc = TextDatumGetCString(tmp);
3150 : :
3151 : : /*
3152 : : * We always use dollar quoting. Figure out a suitable delimiter.
3153 : : *
3154 : : * Since the user is likely to be editing the function body string, we
3155 : : * shouldn't use a short delimiter that he might easily create a
3156 : : * conflict with. Hence prefer "$function$"/"$procedure$", but extend
3157 : : * if needed.
3158 : : */
3159 : 32 : initStringInfo(&dq);
3160 : 32 : appendStringInfoChar(&dq, '$');
3161 [ + + ]: 32 : appendStringInfoString(&dq, (isfunction ? "function" : "procedure"));
3162 [ - + ]: 32 : while (strstr(prosrc, dq.data) != NULL)
2003 tgl@sss.pgh.pa.us 3163 :UBC 0 : appendStringInfoChar(&dq, 'x');
2003 tgl@sss.pgh.pa.us 3164 :CBC 32 : appendStringInfoChar(&dq, '$');
3165 : :
3166 : 32 : appendBinaryStringInfo(&buf, dq.data, dq.len);
3167 : 32 : appendStringInfoString(&buf, prosrc);
3168 : 32 : appendBinaryStringInfo(&buf, dq.data, dq.len);
3169 : : }
3170 : :
4753 rhaas@postgresql.org 3171 : 111 : appendStringInfoChar(&buf, '\n');
3172 : :
6634 tgl@sss.pgh.pa.us 3173 : 111 : ReleaseSysCache(proctup);
3174 : :
3175 : 111 : PG_RETURN_TEXT_P(string_to_text(buf.data));
3176 : : }
3177 : :
3178 : : /*
3179 : : * pg_get_function_arguments
3180 : : * Get a nicely-formatted list of arguments for a function.
3181 : : * This is everything that would go between the parentheses in
3182 : : * CREATE FUNCTION.
3183 : : */
3184 : : Datum
6684 3185 : 2452 : pg_get_function_arguments(PG_FUNCTION_ARGS)
3186 : : {
3187 : 2452 : Oid funcid = PG_GETARG_OID(0);
3188 : : StringInfoData buf;
3189 : : HeapTuple proctup;
3190 : :
6108 rhaas@postgresql.org 3191 : 2452 : proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
6684 tgl@sss.pgh.pa.us 3192 [ + + ]: 2452 : if (!HeapTupleIsValid(proctup))
3751 rhaas@postgresql.org 3193 : 4 : PG_RETURN_NULL();
3194 : :
3195 : 2448 : initStringInfo(&buf);
3196 : :
6545 peter_e@gmx.net 3197 : 2448 : (void) print_function_arguments(&buf, proctup, false, true);
3198 : :
6684 tgl@sss.pgh.pa.us 3199 : 2448 : ReleaseSysCache(proctup);
3200 : :
3201 : 2448 : PG_RETURN_TEXT_P(string_to_text(buf.data));
3202 : : }
3203 : :
3204 : : /*
3205 : : * pg_get_function_identity_arguments
3206 : : * Get a formatted list of arguments for a function.
3207 : : * This is everything that would go between the parentheses in
3208 : : * ALTER FUNCTION, etc. In particular, don't print defaults.
3209 : : */
3210 : : Datum
6545 peter_e@gmx.net 3211 : 2098 : pg_get_function_identity_arguments(PG_FUNCTION_ARGS)
3212 : : {
3213 : 2098 : Oid funcid = PG_GETARG_OID(0);
3214 : : StringInfoData buf;
3215 : : HeapTuple proctup;
3216 : :
6108 rhaas@postgresql.org 3217 : 2098 : proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
6545 peter_e@gmx.net 3218 [ + + ]: 2098 : if (!HeapTupleIsValid(proctup))
3751 rhaas@postgresql.org 3219 : 4 : PG_RETURN_NULL();
3220 : :
3221 : 2094 : initStringInfo(&buf);
3222 : :
6545 peter_e@gmx.net 3223 : 2094 : (void) print_function_arguments(&buf, proctup, false, false);
3224 : :
3225 : 2094 : ReleaseSysCache(proctup);
3226 : :
3227 : 2094 : PG_RETURN_TEXT_P(string_to_text(buf.data));
3228 : : }
3229 : :
3230 : : /*
3231 : : * pg_get_function_result
3232 : : * Get a nicely-formatted version of the result type of a function.
3233 : : * This is what would appear after RETURNS in CREATE FUNCTION.
3234 : : */
3235 : : Datum
6684 tgl@sss.pgh.pa.us 3236 : 2156 : pg_get_function_result(PG_FUNCTION_ARGS)
3237 : : {
3238 : 2156 : Oid funcid = PG_GETARG_OID(0);
3239 : : StringInfoData buf;
3240 : : HeapTuple proctup;
3241 : :
6108 rhaas@postgresql.org 3242 : 2156 : proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
6684 tgl@sss.pgh.pa.us 3243 [ + + ]: 2156 : if (!HeapTupleIsValid(proctup))
3751 rhaas@postgresql.org 3244 : 4 : PG_RETURN_NULL();
3245 : :
3170 peter_e@gmx.net 3246 [ + + ]: 2152 : if (((Form_pg_proc) GETSTRUCT(proctup))->prokind == PROKIND_PROCEDURE)
3247 : : {
3262 3248 : 130 : ReleaseSysCache(proctup);
3249 : 130 : PG_RETURN_NULL();
3250 : : }
3251 : :
3751 rhaas@postgresql.org 3252 : 2022 : initStringInfo(&buf);
3253 : :
6634 tgl@sss.pgh.pa.us 3254 : 2022 : print_function_rettype(&buf, proctup);
3255 : :
3256 : 2022 : ReleaseSysCache(proctup);
3257 : :
3258 : 2022 : PG_RETURN_TEXT_P(string_to_text(buf.data));
3259 : : }
3260 : :
3261 : : /*
3262 : : * Guts of pg_get_function_result: append the function's return type
3263 : : * to the specified buffer.
3264 : : */
3265 : : static void
3266 : 2120 : print_function_rettype(StringInfo buf, HeapTuple proctup)
3267 : : {
3268 : 2120 : Form_pg_proc proc = (Form_pg_proc) GETSTRUCT(proctup);
3269 : 2120 : int ntabargs = 0;
3270 : : StringInfoData rbuf;
3271 : :
3272 : 2120 : initStringInfo(&rbuf);
3273 : :
3274 [ + + ]: 2120 : if (proc->proretset)
3275 : : {
3276 : : /* It might be a table function; try to print the arguments */
3277 : 209 : appendStringInfoString(&rbuf, "TABLE(");
6531 3278 : 209 : ntabargs = print_function_arguments(&rbuf, proctup, true, false);
6684 3279 [ + + ]: 209 : if (ntabargs > 0)
4196 peter_e@gmx.net 3280 : 39 : appendStringInfoChar(&rbuf, ')');
3281 : : else
6634 tgl@sss.pgh.pa.us 3282 : 170 : resetStringInfo(&rbuf);
3283 : : }
3284 : :
6684 3285 [ + + ]: 2120 : if (ntabargs == 0)
3286 : : {
3287 : : /* Not a table function, so do the normal thing */
6634 3288 [ + + ]: 2081 : if (proc->proretset)
3289 : 170 : appendStringInfoString(&rbuf, "SETOF ");
3290 : 2081 : appendStringInfoString(&rbuf, format_type_be(proc->prorettype));
3291 : : }
3292 : :
2662 drowley@postgresql.o 3293 : 2120 : appendBinaryStringInfo(buf, rbuf.data, rbuf.len);
6684 tgl@sss.pgh.pa.us 3294 : 2120 : }
3295 : :
3296 : : /*
3297 : : * Common code for pg_get_function_arguments and pg_get_function_result:
3298 : : * append the desired subset of arguments to buf. We print only TABLE
3299 : : * arguments when print_table_args is true, and all the others when it's false.
3300 : : * We print argument defaults only if print_defaults is true.
3301 : : * Function return value is the number of arguments printed.
3302 : : */
3303 : : static int
3304 : 4862 : print_function_arguments(StringInfo buf, HeapTuple proctup,
3305 : : bool print_table_args, bool print_defaults)
3306 : : {
6531 3307 : 4862 : Form_pg_proc proc = (Form_pg_proc) GETSTRUCT(proctup);
3308 : : int numargs;
3309 : : Oid *argtypes;
3310 : : char **argnames;
3311 : : char *argmodes;
4700 3312 : 4862 : int insertorderbyat = -1;
3313 : : int argsprinted;
3314 : : int inputargno;
3315 : : int nlackdefaults;
2670 3316 : 4862 : List *argdefaults = NIL;
6531 3317 : 4862 : ListCell *nextargdefault = NULL;
3318 : : int i;
3319 : :
6684 3320 : 4862 : numargs = get_func_arg_info(proctup,
3321 : : &argtypes, &argnames, &argmodes);
3322 : :
6531 3323 : 4862 : nlackdefaults = numargs;
3324 [ + + + + ]: 4862 : if (print_defaults && proc->pronargdefaults > 0)
3325 : : {
3326 : : Datum proargdefaults;
3327 : : bool isnull;
3328 : :
3329 : 21 : proargdefaults = SysCacheGetAttr(PROCOID, proctup,
3330 : : Anum_pg_proc_proargdefaults,
3331 : : &isnull);
3332 [ + - ]: 21 : if (!isnull)
3333 : : {
3334 : : char *str;
3335 : :
3336 : 21 : str = TextDatumGetCString(proargdefaults);
3544 peter_e@gmx.net 3337 : 21 : argdefaults = castNode(List, stringToNode(str));
6531 tgl@sss.pgh.pa.us 3338 : 21 : pfree(str);
3339 : 21 : nextargdefault = list_head(argdefaults);
3340 : : /* nlackdefaults counts only *input* arguments lacking defaults */
3341 : 21 : nlackdefaults = proc->pronargs - list_length(argdefaults);
3342 : : }
3343 : : }
3344 : :
3345 : : /* Check for special treatment of ordered-set aggregates */
3170 peter_e@gmx.net 3346 [ + + ]: 4862 : if (proc->prokind == PROKIND_AGGREGATE)
3347 : : {
3348 : : HeapTuple aggtup;
3349 : : Form_pg_aggregate agg;
3350 : :
1204 michael@paquier.xyz 3351 : 594 : aggtup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(proc->oid));
4700 tgl@sss.pgh.pa.us 3352 [ - + ]: 594 : if (!HeapTupleIsValid(aggtup))
4700 tgl@sss.pgh.pa.us 3353 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for aggregate %u",
3354 : : proc->oid);
4700 tgl@sss.pgh.pa.us 3355 :CBC 594 : agg = (Form_pg_aggregate) GETSTRUCT(aggtup);
3356 [ + + ]: 594 : if (AGGKIND_IS_ORDERED_SET(agg->aggkind))
3357 : 28 : insertorderbyat = agg->aggnumdirectargs;
3358 : 594 : ReleaseSysCache(aggtup);
3359 : : }
3360 : :
6684 3361 : 4862 : argsprinted = 0;
6531 3362 : 4862 : inputargno = 0;
6684 3363 [ + + ]: 9860 : for (i = 0; i < numargs; i++)
3364 : : {
6356 bruce@momjian.us 3365 : 4998 : Oid argtype = argtypes[i];
3366 [ + + ]: 4998 : char *argname = argnames ? argnames[i] : NULL;
3367 [ + + ]: 4998 : char argmode = argmodes ? argmodes[i] : PROARGMODE_IN;
3368 : : const char *modename;
3369 : : bool isinput;
3370 : :
6684 tgl@sss.pgh.pa.us 3371 [ + + + + : 4998 : switch (argmode)
+ - ]
3372 : : {
3373 : 4093 : case PROARGMODE_IN:
3374 : :
3375 : : /*
3376 : : * For procedures, explicitly mark all argument modes, so as
3377 : : * to avoid ambiguity with the SQL syntax for DROP PROCEDURE.
3378 : : */
1974 3379 [ + + ]: 4093 : if (proc->prokind == PROKIND_PROCEDURE)
3380 : 287 : modename = "IN ";
3381 : : else
3382 : 3806 : modename = "";
6531 3383 : 4093 : isinput = true;
6684 3384 : 4093 : break;
3385 : 50 : case PROARGMODE_INOUT:
3386 : 50 : modename = "INOUT ";
6531 3387 : 50 : isinput = true;
6684 3388 : 50 : break;
3389 : 517 : case PROARGMODE_OUT:
3390 : 517 : modename = "OUT ";
6531 3391 : 517 : isinput = false;
6684 3392 : 517 : break;
3393 : 92 : case PROARGMODE_VARIADIC:
3394 : 92 : modename = "VARIADIC ";
6531 3395 : 92 : isinput = true;
6684 3396 : 92 : break;
3397 : 246 : case PROARGMODE_TABLE:
3398 : 246 : modename = "";
6531 3399 : 246 : isinput = false;
6684 3400 : 246 : break;
6684 tgl@sss.pgh.pa.us 3401 :UBC 0 : default:
3402 [ # # ]: 0 : elog(ERROR, "invalid parameter mode '%c'", argmode);
3403 : : modename = NULL; /* keep compiler quiet */
3404 : : isinput = false;
3405 : : break;
3406 : : }
6531 tgl@sss.pgh.pa.us 3407 [ + + ]:CBC 4998 : if (isinput)
3408 : 4235 : inputargno++; /* this is a 1-based counter */
3409 : :
3410 [ + + ]: 4998 : if (print_table_args != (argmode == PROARGMODE_TABLE))
3411 : 416 : continue;
3412 : :
4700 3413 [ + + ]: 4582 : if (argsprinted == insertorderbyat)
3414 : : {
3415 [ + - ]: 28 : if (argsprinted)
3416 : 28 : appendStringInfoChar(buf, ' ');
3417 : 28 : appendStringInfoString(buf, "ORDER BY ");
3418 : : }
3419 [ + + ]: 4554 : else if (argsprinted)
6684 3420 : 1477 : appendStringInfoString(buf, ", ");
3421 : :
3422 : 4582 : appendStringInfoString(buf, modename);
6531 3423 [ + + + + ]: 4582 : if (argname && argname[0])
6358 3424 : 1623 : appendStringInfo(buf, "%s ", quote_identifier(argname));
6684 3425 : 4582 : appendStringInfoString(buf, format_type_be(argtype));
6531 3426 [ + + + + : 4582 : if (print_defaults && isinput && inputargno > nlackdefaults)
+ + ]
3427 : : {
3428 : : Node *expr;
3429 : :
3430 [ - + ]: 32 : Assert(nextargdefault != NULL);
3431 : 32 : expr = (Node *) lfirst(nextargdefault);
2670 3432 : 32 : nextargdefault = lnext(argdefaults, nextargdefault);
3433 : :
6531 3434 : 32 : appendStringInfo(buf, " DEFAULT %s",
3435 : : deparse_expression(expr, NIL, false, false));
3436 : : }
6684 3437 : 4582 : argsprinted++;
3438 : :
3439 : : /* nasty hack: print the last arg twice for variadic ordered-set agg */
4700 3440 [ + + + + ]: 4582 : if (argsprinted == insertorderbyat && i == numargs - 1)
3441 : : {
3442 : 14 : i--;
3443 : : /* aggs shouldn't have defaults anyway, but just to be sure ... */
3444 : 14 : print_defaults = false;
3445 : : }
3446 : : }
3447 : :
6684 3448 : 4862 : return argsprinted;
3449 : : }
3450 : :
3451 : : static bool
4727 peter_e@gmx.net 3452 : 64 : is_input_argument(int nth, const char *argmodes)
3453 : : {
3454 : : return (!argmodes
3455 [ + + ]: 28 : || argmodes[nth] == PROARGMODE_IN
3456 [ + - ]: 12 : || argmodes[nth] == PROARGMODE_INOUT
3457 [ + + - + ]: 92 : || argmodes[nth] == PROARGMODE_VARIADIC);
3458 : : }
3459 : :
3460 : : /*
3461 : : * Append used transformed types to specified buffer
3462 : : */
3463 : : static void
4211 3464 : 111 : print_function_trftypes(StringInfo buf, HeapTuple proctup)
3465 : : {
3466 : : Oid *trftypes;
3467 : : int ntypes;
3468 : :
3469 : 111 : ntypes = get_func_trftypes(proctup, &trftypes);
3470 [ + + ]: 111 : if (ntypes > 0)
3471 : : {
3472 : : int i;
3473 : :
2110 tgl@sss.pgh.pa.us 3474 : 3 : appendStringInfoString(buf, " TRANSFORM ");
4211 peter_e@gmx.net 3475 [ + + ]: 8 : for (i = 0; i < ntypes; i++)
3476 : : {
3477 [ + + ]: 5 : if (i != 0)
3478 : 2 : appendStringInfoString(buf, ", ");
4200 magnus@hagander.net 3479 : 5 : appendStringInfo(buf, "FOR TYPE %s", format_type_be(trftypes[i]));
3480 : : }
2110 tgl@sss.pgh.pa.us 3481 : 3 : appendStringInfoChar(buf, '\n');
3482 : : }
4211 peter_e@gmx.net 3483 : 111 : }
3484 : :
3485 : : /*
3486 : : * Get textual representation of a function argument's default value. The
3487 : : * second argument of this function is the argument number among all arguments
3488 : : * (i.e. proallargtypes, *not* proargtypes), starting with 1, because that's
3489 : : * how information_schema.sql uses it.
3490 : : */
3491 : : Datum
4727 3492 : 36 : pg_get_function_arg_default(PG_FUNCTION_ARGS)
3493 : : {
3494 : 36 : Oid funcid = PG_GETARG_OID(0);
3495 : 36 : int32 nth_arg = PG_GETARG_INT32(1);
3496 : : HeapTuple proctup;
3497 : : Form_pg_proc proc;
3498 : : int numargs;
3499 : : Oid *argtypes;
3500 : : char **argnames;
3501 : : char *argmodes;
3502 : : int i;
3503 : : List *argdefaults;
3504 : : Node *node;
3505 : : char *str;
3506 : : int nth_inputarg;
3507 : : Datum proargdefaults;
3508 : : bool isnull;
3509 : : int nth_default;
3510 : :
3511 : 36 : proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
3512 [ + + ]: 36 : if (!HeapTupleIsValid(proctup))
3751 rhaas@postgresql.org 3513 : 8 : PG_RETURN_NULL();
3514 : :
4727 peter_e@gmx.net 3515 : 28 : numargs = get_func_arg_info(proctup, &argtypes, &argnames, &argmodes);
3516 [ + - + - : 28 : if (nth_arg < 1 || nth_arg > numargs || !is_input_argument(nth_arg - 1, argmodes))
+ + ]
3517 : : {
3518 : 8 : ReleaseSysCache(proctup);
3519 : 8 : PG_RETURN_NULL();
3520 : : }
3521 : :
3522 : 20 : nth_inputarg = 0;
3523 [ + + ]: 56 : for (i = 0; i < nth_arg; i++)
3524 [ + + ]: 36 : if (is_input_argument(i, argmodes))
3525 : 32 : nth_inputarg++;
3526 : :
3527 : 20 : proargdefaults = SysCacheGetAttr(PROCOID, proctup,
3528 : : Anum_pg_proc_proargdefaults,
3529 : : &isnull);
3530 [ - + ]: 20 : if (isnull)
3531 : : {
4727 peter_e@gmx.net 3532 :UBC 0 : ReleaseSysCache(proctup);
3533 : 0 : PG_RETURN_NULL();
3534 : : }
3535 : :
4727 peter_e@gmx.net 3536 :CBC 20 : str = TextDatumGetCString(proargdefaults);
3544 3537 : 20 : argdefaults = castNode(List, stringToNode(str));
4727 3538 : 20 : pfree(str);
3539 : :
3540 : 20 : proc = (Form_pg_proc) GETSTRUCT(proctup);
3541 : :
3542 : : /*
3543 : : * Calculate index into proargdefaults: proargdefaults corresponds to the
3544 : : * last N input arguments, where N = pronargdefaults.
3545 : : */
3546 : 20 : nth_default = nth_inputarg - 1 - (proc->pronargs - proc->pronargdefaults);
3547 : :
3548 [ + + - + ]: 20 : if (nth_default < 0 || nth_default >= list_length(argdefaults))
3549 : : {
3550 : 4 : ReleaseSysCache(proctup);
3551 : 4 : PG_RETURN_NULL();
3552 : : }
3553 : 16 : node = list_nth(argdefaults, nth_default);
3554 : 16 : str = deparse_expression(node, NIL, false, false);
3555 : :
3556 : 16 : ReleaseSysCache(proctup);
3557 : :
3558 : 16 : PG_RETURN_TEXT_P(string_to_text(str));
3559 : : }
3560 : :
3561 : : static void
2038 peter@eisentraut.org 3562 : 129 : print_function_sqlbody(StringInfo buf, HeapTuple proctup)
3563 : : {
3564 : : int numargs;
3565 : : Oid *argtypes;
3566 : : char **argnames;
3567 : : char *argmodes;
3568 : 129 : deparse_namespace dpns = {0};
3569 : : Datum tmp;
3570 : : Node *n;
3571 : :
3572 : 129 : dpns.funcname = pstrdup(NameStr(((Form_pg_proc) GETSTRUCT(proctup))->proname));
3573 : 129 : numargs = get_func_arg_info(proctup,
3574 : : &argtypes, &argnames, &argmodes);
3575 : 129 : dpns.numargs = numargs;
3576 : 129 : dpns.argnames = argnames;
3577 : :
1321 dgustafsson@postgres 3578 : 129 : tmp = SysCacheGetAttrNotNull(PROCOID, proctup, Anum_pg_proc_prosqlbody);
2038 peter@eisentraut.org 3579 : 129 : n = stringToNode(TextDatumGetCString(tmp));
3580 : :
3581 [ + + ]: 129 : if (IsA(n, List))
3582 : : {
3583 : : List *stmts;
3584 : : ListCell *lc;
3585 : :
3586 : 102 : stmts = linitial(castNode(List, n));
3587 : :
3588 : 102 : appendStringInfoString(buf, "BEGIN ATOMIC\n");
3589 : :
3590 [ + + + + : 199 : foreach(lc, stmts)
+ + ]
3591 : : {
3592 : 97 : Query *query = lfirst_node(Query, lc);
3593 : :
3594 : : /* It seems advisable to get at least AccessShareLock on rels */
1892 tgl@sss.pgh.pa.us 3595 : 97 : AcquireRewriteLocks(query, false, false);
1629 3596 : 97 : get_query_def(query, buf, list_make1(&dpns), NULL, false,
3597 : : PRETTYFLAG_INDENT, WRAP_COLUMN_DEFAULT, 1);
2038 peter@eisentraut.org 3598 : 97 : appendStringInfoChar(buf, ';');
3599 : 97 : appendStringInfoChar(buf, '\n');
3600 : : }
3601 : :
3602 : 102 : appendStringInfoString(buf, "END");
3603 : : }
3604 : : else
3605 : : {
1892 tgl@sss.pgh.pa.us 3606 : 27 : Query *query = castNode(Query, n);
3607 : :
3608 : : /* It seems advisable to get at least AccessShareLock on rels */
3609 : 27 : AcquireRewriteLocks(query, false, false);
1629 3610 : 27 : get_query_def(query, buf, list_make1(&dpns), NULL, false,
3611 : : 0, WRAP_COLUMN_DEFAULT, 0);
3612 : : }
2038 peter@eisentraut.org 3613 : 129 : }
3614 : :
3615 : : Datum
3616 : 1807 : pg_get_function_sqlbody(PG_FUNCTION_ARGS)
3617 : : {
3618 : 1807 : Oid funcid = PG_GETARG_OID(0);
3619 : : StringInfoData buf;
3620 : : HeapTuple proctup;
3621 : : bool isnull;
3622 : :
3623 : 1807 : initStringInfo(&buf);
3624 : :
3625 : : /* Look up the function */
3626 : 1807 : proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
3627 [ - + ]: 1807 : if (!HeapTupleIsValid(proctup))
2038 peter@eisentraut.org 3628 :UBC 0 : PG_RETURN_NULL();
3629 : :
1892 tgl@sss.pgh.pa.us 3630 :CBC 1807 : (void) SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prosqlbody, &isnull);
2038 peter@eisentraut.org 3631 [ + + ]: 1807 : if (isnull)
3632 : : {
3633 : 1757 : ReleaseSysCache(proctup);
3634 : 1757 : PG_RETURN_NULL();
3635 : : }
3636 : :
3637 : 50 : print_function_sqlbody(&buf, proctup);
3638 : :
3639 : 50 : ReleaseSysCache(proctup);
3640 : :
1521 drowley@postgresql.o 3641 : 50 : PG_RETURN_TEXT_P(cstring_to_text_with_len(buf.data, buf.len));
3642 : : }
3643 : :
3644 : :
3645 : : /*
3646 : : * deparse_expression - General utility for deparsing expressions
3647 : : *
3648 : : * calls deparse_expression_pretty with all prettyPrinting disabled
3649 : : */
3650 : : char *
8499 tgl@sss.pgh.pa.us 3651 : 58951 : deparse_expression(Node *expr, List *dpcontext,
3652 : : bool forceprefix, bool showimplicit)
3653 : : {
8494 bruce@momjian.us 3654 : 58951 : return deparse_expression_pretty(expr, dpcontext, forceprefix,
3655 : : showimplicit, 0, 0);
3656 : : }
3657 : :
3658 : : /* ----------
3659 : : * deparse_expression_pretty - General utility for deparsing expressions
3660 : : *
3661 : : * expr is the node tree to be deparsed. It must be a transformed expression
3662 : : * tree (ie, not the raw output of gram.y).
3663 : : *
3664 : : * dpcontext is a list of deparse_namespace nodes representing the context
3665 : : * for interpreting Vars in the node tree. It can be NIL if no Vars are
3666 : : * expected.
3667 : : *
3668 : : * forceprefix is true to force all Vars to be prefixed with their table names.
3669 : : *
3670 : : * showimplicit is true to force all implicit casts to be shown explicitly.
3671 : : *
3672 : : * Tries to pretty up the output according to prettyFlags and startIndent.
3673 : : *
3674 : : * The result is a palloc'd string.
3675 : : * ----------
3676 : : */
3677 : : static char *
8499 tgl@sss.pgh.pa.us 3678 : 67457 : deparse_expression_pretty(Node *expr, List *dpcontext,
3679 : : bool forceprefix, bool showimplicit,
3680 : : int prettyFlags, int startIndent)
3681 : : {
3682 : : StringInfoData buf;
3683 : : deparse_context context;
3684 : :
9895 3685 : 67457 : initStringInfo(&buf);
3686 : 67457 : context.buf = &buf;
9395 3687 : 67457 : context.namespaces = dpcontext;
798 3688 : 67457 : context.resultDesc = NULL;
3689 : 67457 : context.targetList = NIL;
6521 3690 : 67457 : context.windowClause = NIL;
9395 3691 : 67457 : context.varprefix = forceprefix;
8499 3692 : 67457 : context.prettyFlags = prettyFlags;
5064 3693 : 67457 : context.wrapColumn = WRAP_COLUMN_DEFAULT;
8499 3694 : 67457 : context.indentLevel = startIndent;
798 3695 : 67457 : context.colNamesVisible = true;
3696 : 67457 : context.inGroupBy = false;
3697 : 67457 : context.varInOrderBy = false;
2521 3698 : 67457 : context.appendparents = NULL;
3699 : :
8813 3700 : 67457 : get_rule_expr(expr, &context, showimplicit);
3701 : :
9895 3702 : 67457 : return buf.data;
3703 : : }
3704 : :
3705 : : /* ----------
3706 : : * deparse_context_for - Build deparse context for a single relation
3707 : : *
3708 : : * Given the reference name (alias) and OID of a relation, build deparsing
3709 : : * context for an expression referencing only that relation (as varno 1,
3710 : : * varlevelsup 0). This is sufficient for many uses of deparse_expression.
3711 : : * ----------
3712 : : */
3713 : : List *
8994 3714 : 14686 : deparse_context_for(const char *aliasname, Oid relid)
3715 : : {
3716 : : deparse_namespace *dpns;
3717 : : RangeTblEntry *rte;
3718 : :
330 michael@paquier.xyz 3719 : 14686 : dpns = palloc0_object(deparse_namespace);
3720 : :
3721 : : /* Build a minimal RTE for the rel */
9395 tgl@sss.pgh.pa.us 3722 : 14686 : rte = makeNode(RangeTblEntry);
9004 3723 : 14686 : rte->rtekind = RTE_RELATION;
9395 3724 : 14686 : rte->relid = relid;
5735 3725 : 14686 : rte->relkind = RELKIND_RELATION; /* no need for exactness here */
2958 3726 : 14686 : rte->rellockmode = AccessShareLock;
5158 3727 : 14686 : rte->alias = makeAlias(aliasname, NIL);
3728 : 14686 : rte->eref = rte->alias;
5203 3729 : 14686 : rte->lateral = false;
9395 3730 : 14686 : rte->inh = false;
3731 : 14686 : rte->inFromCl = true;
3732 : :
3733 : : /* Build one-element rtable */
8194 neilc@samurai.com 3734 : 14686 : dpns->rtable = list_make1(rte);
2521 tgl@sss.pgh.pa.us 3735 : 14686 : dpns->subplans = NIL;
6604 3736 : 14686 : dpns->ctes = NIL;
2521 3737 : 14686 : dpns->appendrels = NULL;
5158 3738 : 14686 : set_rtable_names(dpns, NIL, NULL);
5057 3739 : 14686 : set_simple_column_names(dpns);
3740 : :
3741 : : /* Return a one-deep namespace stack */
8194 neilc@samurai.com 3742 : 14686 : return list_make1(dpns);
3743 : : }
3744 : :
3745 : : /*
3746 : : * deparse_context_for_plan_tree - Build deparse context for a Plan tree
3747 : : *
3748 : : * When deparsing an expression in a Plan tree, we use the plan's rangetable
3749 : : * to resolve names of simple Vars. The initialization of column names for
3750 : : * this is rather expensive if the rangetable is large, and it'll be the same
3751 : : * for every expression in the Plan tree; so we do it just once and re-use
3752 : : * the result of this function for each expression. (Note that the result
3753 : : * is not usable until set_deparse_context_plan() is applied to it.)
3754 : : *
3755 : : * In addition to the PlannedStmt, pass the per-RTE alias names
3756 : : * assigned by a previous call to select_rtable_names_for_explain.
3757 : : */
3758 : : List *
2521 tgl@sss.pgh.pa.us 3759 : 17279 : deparse_context_for_plan_tree(PlannedStmt *pstmt, List *rtable_names)
3760 : : {
3761 : : deparse_namespace *dpns;
3762 : :
330 michael@paquier.xyz 3763 : 17279 : dpns = palloc0_object(deparse_namespace);
3764 : :
3765 : : /* Initialize fields that stay the same across the whole plan tree */
2521 tgl@sss.pgh.pa.us 3766 : 17279 : dpns->rtable = pstmt->rtable;
4312 3767 : 17279 : dpns->rtable_names = rtable_names;
2521 3768 : 17279 : dpns->subplans = pstmt->subplans;
4312 3769 : 17279 : dpns->ctes = NIL;
2521 3770 [ + + ]: 17279 : if (pstmt->appendRelations)
3771 : : {
3772 : : /* Set up the array, indexed by child relid */
3773 : 2782 : int ntables = list_length(dpns->rtable);
3774 : : ListCell *lc;
3775 : :
80 michael@paquier.xyz 3776 :GNC 2782 : dpns->appendrels = palloc0_array(AppendRelInfo *, ntables + 1);
2521 tgl@sss.pgh.pa.us 3777 [ + - + + :CBC 15138 : foreach(lc, pstmt->appendRelations)
+ + ]
3778 : : {
3779 : 12356 : AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
3780 : 12356 : Index crelid = appinfo->child_relid;
3781 : :
3782 [ + - - + ]: 12356 : Assert(crelid > 0 && crelid <= ntables);
3783 [ - + ]: 12356 : Assert(dpns->appendrels[crelid] == NULL);
3784 : 12356 : dpns->appendrels[crelid] = appinfo;
3785 : : }
3786 : : }
3787 : : else
3788 : 14497 : dpns->appendrels = NULL; /* don't need it */
3789 : :
3790 : : /*
3791 : : * Set up column name aliases, ignoring any join RTEs; they don't matter
3792 : : * because plan trees don't contain any join alias Vars.
3793 : : */
4312 3794 : 17279 : set_simple_column_names(dpns);
3795 : :
3796 : : /* Return a one-deep namespace stack */
3797 : 17279 : return list_make1(dpns);
3798 : : }
3799 : :
3800 : : /*
3801 : : * set_deparse_context_plan - Specify Plan node containing expression
3802 : : *
3803 : : * When deparsing an expression in a Plan tree, we might have to resolve
3804 : : * OUTER_VAR, INNER_VAR, or INDEX_VAR references. To do this, the caller must
3805 : : * provide the parent Plan node. Then OUTER_VAR and INNER_VAR references
3806 : : * can be resolved by drilling down into the left and right child plans.
3807 : : * Similarly, INDEX_VAR references can be resolved by reference to the
3808 : : * indextlist given in a parent IndexOnlyScan node, or to the scan tlist in
3809 : : * ForeignScan and CustomScan nodes. (Note that we don't currently support
3810 : : * deparsing of indexquals in regular IndexScan or BitmapIndexScan nodes;
3811 : : * for those, we can only deparse the indexqualorig fields, which won't
3812 : : * contain INDEX_VAR Vars.)
3813 : : *
3814 : : * The ancestors list is a list of the Plan's parent Plan and SubPlan nodes,
3815 : : * the most-closely-nested first. This is needed to resolve PARAM_EXEC
3816 : : * Params. Note we assume that all the Plan nodes share the same rtable.
3817 : : *
3818 : : * For a ModifyTable plan, we might also need to resolve references to OLD/NEW
3819 : : * variables in the RETURNING list, so we copy the alias names of the OLD and
3820 : : * NEW rows from the ModifyTable plan node.
3821 : : *
3822 : : * Once this function has been called, deparse_expression() can be called on
3823 : : * subsidiary expression(s) of the specified Plan node. To deparse
3824 : : * expressions of a different Plan node in the same Plan tree, re-call this
3825 : : * function to identify the new parent Plan node.
3826 : : *
3827 : : * The result is the same List passed in; this is a notational convenience.
3828 : : */
3829 : : List *
2521 3830 : 42930 : set_deparse_context_plan(List *dpcontext, Plan *plan, List *ancestors)
3831 : : {
3832 : : deparse_namespace *dpns;
3833 : :
3834 : : /* Should always have one-entry namespace list for Plan deparsing */
4312 3835 [ - + ]: 42930 : Assert(list_length(dpcontext) == 1);
3836 : 42930 : dpns = (deparse_namespace *) linitial(dpcontext);
3837 : :
3838 : : /* Set our attention on the specific plan node passed in */
5959 3839 : 42930 : dpns->ancestors = ancestors;
1851 3840 : 42930 : set_deparse_plan(dpns, plan);
3841 : :
4312 3842 : 42930 : return dpcontext;
3843 : : }
3844 : :
3845 : : /*
3846 : : * select_rtable_names_for_explain - Select RTE aliases for EXPLAIN
3847 : : *
3848 : : * Determine the relation aliases we'll use during an EXPLAIN operation.
3849 : : * This is just a frontend to set_rtable_names. We have to expose the aliases
3850 : : * to EXPLAIN because EXPLAIN needs to know the right alias names to print.
3851 : : */
3852 : : List *
5158 3853 : 17279 : select_rtable_names_for_explain(List *rtable, Bitmapset *rels_used)
3854 : : {
3855 : : deparse_namespace dpns;
3856 : :
3857 : 17279 : memset(&dpns, 0, sizeof(dpns));
3858 : 17279 : dpns.rtable = rtable;
2521 3859 : 17279 : dpns.subplans = NIL;
5158 3860 : 17279 : dpns.ctes = NIL;
2521 3861 : 17279 : dpns.appendrels = NULL;
5158 3862 : 17279 : set_rtable_names(&dpns, NIL, rels_used);
3863 : : /* We needn't bother computing column aliases yet */
3864 : :
3865 : 17279 : return dpns.rtable_names;
3866 : : }
3867 : :
3868 : : /*
3869 : : * set_rtable_names: select RTE aliases to be used in printing a query
3870 : : *
3871 : : * We fill in dpns->rtable_names with a list of names that is one-for-one with
3872 : : * the already-filled dpns->rtable list. Each RTE name is unique among those
3873 : : * in the new namespace plus any ancestor namespaces listed in
3874 : : * parent_namespaces.
3875 : : *
3876 : : * If rels_used isn't NULL, only RTE indexes listed in it are given aliases.
3877 : : *
3878 : : * Note that this function is only concerned with relation names, not column
3879 : : * names.
3880 : : */
3881 : : static void
3882 : 35667 : set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
3883 : : Bitmapset *rels_used)
3884 : : {
3885 : : HASHCTL hash_ctl;
3886 : : HTAB *names_hash;
3887 : : NameHashEntry *hentry;
3888 : : bool found;
3889 : : int rtindex;
3890 : : ListCell *lc;
3891 : :
3892 : 35667 : dpns->rtable_names = NIL;
3893 : : /* nothing more to do if empty rtable */
4007 3894 [ + + ]: 35667 : if (dpns->rtable == NIL)
3895 : 368 : return;
3896 : :
3897 : : /*
3898 : : * We use a hash table to hold known names, so that this process is O(N)
3899 : : * not O(N^2) for N names.
3900 : : */
3901 : 35299 : hash_ctl.keysize = NAMEDATALEN;
3902 : 35299 : hash_ctl.entrysize = sizeof(NameHashEntry);
3903 : 35299 : hash_ctl.hcxt = CurrentMemoryContext;
3904 : 35299 : names_hash = hash_create("set_rtable_names names",
3905 : 35299 : list_length(dpns->rtable),
3906 : : &hash_ctl,
3907 : : HASH_ELEM | HASH_STRINGS | HASH_CONTEXT);
3908 : :
3909 : : /* Preload the hash table with names appearing in parent_namespaces */
3910 [ + + + + : 36422 : foreach(lc, parent_namespaces)
+ + ]
3911 : : {
3912 : 1123 : deparse_namespace *olddpns = (deparse_namespace *) lfirst(lc);
3913 : : ListCell *lc2;
3914 : :
3915 [ + + + + : 3899 : foreach(lc2, olddpns->rtable_names)
+ + ]
3916 : : {
3917 : 2776 : char *oldname = (char *) lfirst(lc2);
3918 : :
3919 [ + + ]: 2776 : if (oldname == NULL)
3920 : 201 : continue;
3921 : 2575 : hentry = (NameHashEntry *) hash_search(names_hash,
3922 : : oldname,
3923 : : HASH_ENTER,
3924 : : &found);
3925 : : /* we do not complain about duplicate names in parent namespaces */
3926 : 2575 : hentry->counter = 0;
3927 : : }
3928 : : }
3929 : :
3930 : : /* Now we can scan the rtable */
3931 : 35299 : rtindex = 1;
5158 3932 [ + - + + : 106581 : foreach(lc, dpns->rtable)
+ + ]
3933 : : {
3934 : 71282 : RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
3935 : : char *refname;
3936 : :
3937 : : /* Just in case this takes an unreasonable amount of time ... */
4007 3938 [ + + ]: 71282 : CHECK_FOR_INTERRUPTS();
3939 : :
5158 3940 [ + + + + ]: 71282 : if (rels_used && !bms_is_member(rtindex, rels_used))
3941 : : {
3942 : : /* Ignore unreferenced RTE */
3943 : 14089 : refname = NULL;
3944 : : }
3945 [ + + ]: 57193 : else if (rte->alias)
3946 : : {
3947 : : /* If RTE has a user-defined alias, prefer that */
3948 : 36882 : refname = rte->alias->aliasname;
3949 : : }
3950 [ + + ]: 20311 : else if (rte->rtekind == RTE_RELATION)
3951 : : {
3952 : : /* Use the current actual name of the relation */
3953 : 15344 : refname = get_rel_name(rte->relid);
3954 : : }
3955 [ + + ]: 4967 : else if (rte->rtekind == RTE_JOIN)
3956 : : {
3957 : : /* Unnamed join has no refname */
3958 : 1181 : refname = NULL;
3959 : : }
3960 : : else
3961 : : {
3962 : : /* Otherwise use whatever the parser assigned */
3963 : 3786 : refname = rte->eref->aliasname;
3964 : : }
3965 : :
3966 : : /*
3967 : : * If the selected name isn't unique, append digits to make it so, and
3968 : : * make a new hash entry for it once we've got a unique name. For a
3969 : : * very long input name, we might have to truncate to stay within
3970 : : * NAMEDATALEN.
3971 : : */
4007 3972 [ + + ]: 71282 : if (refname)
3973 : : {
3974 : 56012 : hentry = (NameHashEntry *) hash_search(names_hash,
3975 : : refname,
3976 : : HASH_ENTER,
3977 : : &found);
3978 [ + + ]: 56012 : if (found)
3979 : : {
3980 : : /* Name already in use, must choose a new one */
3981 : 10647 : int refnamelen = strlen(refname);
3982 : 10647 : char *modname = (char *) palloc(refnamelen + 16);
3983 : : NameHashEntry *hentry2;
3984 : :
3985 : : do
3986 : : {
3987 : 10651 : hentry->counter++;
3988 : : for (;;)
3989 : : {
3990 : 10659 : memcpy(modname, refname, refnamelen);
3991 : 10659 : sprintf(modname + refnamelen, "_%d", hentry->counter);
3992 [ + + ]: 10659 : if (strlen(modname) < NAMEDATALEN)
3993 : 10651 : break;
3994 : : /* drop chars from refname to keep all the digits */
3995 : 8 : refnamelen = pg_mbcliplen(refname, refnamelen,
3996 : : refnamelen - 1);
3997 : : }
3998 : 10651 : hentry2 = (NameHashEntry *) hash_search(names_hash,
3999 : : modname,
4000 : : HASH_ENTER,
4001 : : &found);
4002 [ + + ]: 10651 : } while (found);
4003 : 10647 : hentry2->counter = 0; /* init new hash entry */
4004 : 10647 : refname = modname;
4005 : : }
4006 : : else
4007 : : {
4008 : : /* Name not previously used, need only initialize hentry */
4009 : 45365 : hentry->counter = 0;
4010 : : }
4011 : : }
4012 : :
5158 4013 : 71282 : dpns->rtable_names = lappend(dpns->rtable_names, refname);
4014 : 71282 : rtindex++;
4015 : : }
4016 : :
4007 4017 : 35299 : hash_destroy(names_hash);
4018 : : }
4019 : :
4020 : : /*
4021 : : * set_deparse_for_query: set up deparse_namespace for deparsing a Query tree
4022 : : *
4023 : : * For convenience, this is defined to initialize the deparse_namespace struct
4024 : : * from scratch.
4025 : : */
4026 : : static void
5057 4027 : 3614 : set_deparse_for_query(deparse_namespace *dpns, Query *query,
4028 : : List *parent_namespaces)
4029 : : {
4030 : : ListCell *lc;
4031 : : ListCell *lc2;
4032 : :
4033 : : /* Initialize *dpns and fill rtable/ctes links */
4034 : 3614 : memset(dpns, 0, sizeof(deparse_namespace));
4035 : 3614 : dpns->rtable = query->rtable;
2521 4036 : 3614 : dpns->subplans = NIL;
5057 4037 : 3614 : dpns->ctes = query->cteList;
2521 4038 : 3614 : dpns->appendrels = NULL;
658 dean.a.rasheed@gmail 4039 : 3614 : dpns->ret_old_alias = query->returningOldAlias;
4040 : 3614 : dpns->ret_new_alias = query->returningNewAlias;
4041 : :
4042 : : /* Assign a unique relation alias to each RTE */
5057 tgl@sss.pgh.pa.us 4043 : 3614 : set_rtable_names(dpns, parent_namespaces, NULL);
4044 : :
4045 : : /* Initialize dpns->rtable_columns to contain zeroed structs */
4046 : 3614 : dpns->rtable_columns = NIL;
4047 [ + + ]: 10016 : while (list_length(dpns->rtable_columns) < list_length(dpns->rtable))
4048 : 6402 : dpns->rtable_columns = lappend(dpns->rtable_columns,
4049 : : palloc0_object(deparse_columns));
4050 : :
4051 : : /* If it's a utility query, it won't have a jointree */
4921 4052 [ + + ]: 3614 : if (query->jointree)
4053 : : {
4054 : : /* Detect whether global uniqueness of USING names is needed */
4055 : 3605 : dpns->unique_using =
4853 4056 : 3605 : has_dangerous_join_using(dpns, (Node *) query->jointree);
4057 : :
4058 : : /*
4059 : : * Select names for columns merged by USING, via a recursive pass over
4060 : : * the query jointree.
4061 : : */
4571 4062 : 3605 : set_using_names(dpns, (Node *) query->jointree, NIL);
4063 : : }
4064 : :
4065 : : /*
4066 : : * Now assign remaining column aliases for each RTE. We do this in a
4067 : : * linear scan of the rtable, so as to process RTEs whether or not they
4068 : : * are in the jointree (we mustn't miss NEW.*, INSERT target relations,
4069 : : * etc). JOIN RTEs must be processed after their children, but this is
4070 : : * okay because they appear later in the rtable list than their children
4071 : : * (cf Asserts in identify_join_columns()).
4072 : : */
5057 4073 [ + + + + : 10016 : forboth(lc, dpns->rtable, lc2, dpns->rtable_columns)
+ + + + +
+ + - +
+ ]
4074 : : {
4075 : 6402 : RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
4076 : 6402 : deparse_columns *colinfo = (deparse_columns *) lfirst(lc2);
4077 : :
4078 [ + + ]: 6402 : if (rte->rtekind == RTE_JOIN)
4079 : 940 : set_join_column_names(dpns, rte, colinfo);
4080 : : else
4081 : 5462 : set_relation_column_names(dpns, rte, colinfo);
4082 : : }
4083 : 3614 : }
4084 : :
4085 : : /*
4086 : : * set_simple_column_names: fill in column aliases for non-query situations
4087 : : *
4088 : : * This handles EXPLAIN and cases where we only have relation RTEs. Without
4089 : : * a join tree, we can't do anything smart about join RTEs, but we don't
4090 : : * need to, because EXPLAIN should never see join alias Vars anyway.
4091 : : * If we find a join RTE we'll just skip it, leaving its deparse_columns
4092 : : * struct all-zero. If somehow we try to deparse a join alias Var, we'll
4093 : : * error out cleanly because the struct's num_cols will be zero.
4094 : : */
4095 : : static void
4096 : 32053 : set_simple_column_names(deparse_namespace *dpns)
4097 : : {
4098 : : ListCell *lc;
4099 : : ListCell *lc2;
4100 : :
4101 : : /* Initialize dpns->rtable_columns to contain zeroed structs */
4102 : 32053 : dpns->rtable_columns = NIL;
4103 [ + + ]: 96933 : while (list_length(dpns->rtable_columns) < list_length(dpns->rtable))
4104 : 64880 : dpns->rtable_columns = lappend(dpns->rtable_columns,
4105 : : palloc0_object(deparse_columns));
4106 : :
4107 : : /* Assign unique column aliases within each non-join RTE */
4108 [ + - + + : 96933 : forboth(lc, dpns->rtable, lc2, dpns->rtable_columns)
+ - + + +
+ + - +
+ ]
4109 : : {
4110 : 64880 : RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
4111 : 64880 : deparse_columns *colinfo = (deparse_columns *) lfirst(lc2);
4112 : :
688 4113 [ + + ]: 64880 : if (rte->rtekind != RTE_JOIN)
4114 : 59931 : set_relation_column_names(dpns, rte, colinfo);
4115 : : }
5057 4116 : 32053 : }
4117 : :
4118 : : /*
4119 : : * has_dangerous_join_using: search jointree for unnamed JOIN USING
4120 : : *
4121 : : * Merged columns of a JOIN USING may act differently from either of the input
4122 : : * columns, either because they are merged with COALESCE (in a FULL JOIN) or
4123 : : * because an implicit coercion of the underlying input column is required.
4124 : : * In such a case the column must be referenced as a column of the JOIN not as
4125 : : * a column of either input. And this is problematic if the join is unnamed
4126 : : * (alias-less): we cannot qualify the column's name with an RTE name, since
4127 : : * there is none. (Forcibly assigning an alias to the join is not a solution,
4128 : : * since that will prevent legal references to tables below the join.)
4129 : : * To ensure that every column in the query is unambiguously referenceable,
4130 : : * we must assign such merged columns names that are globally unique across
4131 : : * the whole query, aliasing other columns out of the way as necessary.
4132 : : *
4133 : : * Because the ensuing re-aliasing is fairly damaging to the readability of
4134 : : * the query, we don't do this unless we have to. So, we must pre-scan
4135 : : * the join tree to see if we have to, before starting set_using_names().
4136 : : */
4137 : : static bool
4853 4138 : 8538 : has_dangerous_join_using(deparse_namespace *dpns, Node *jtnode)
4139 : : {
5057 4140 [ + + ]: 8538 : if (IsA(jtnode, RangeTblRef))
4141 : : {
4142 : : /* nothing to do here */
4143 : : }
4144 [ + + ]: 4501 : else if (IsA(jtnode, FromExpr))
4145 : : {
4146 : 3605 : FromExpr *f = (FromExpr *) jtnode;
4147 : : ListCell *lc;
4148 : :
4149 [ + + + + : 6798 : foreach(lc, f->fromlist)
+ + ]
4150 : : {
4853 4151 [ + + ]: 3245 : if (has_dangerous_join_using(dpns, (Node *) lfirst(lc)))
5057 4152 : 52 : return true;
4153 : : }
4154 : : }
4155 [ + - ]: 896 : else if (IsA(jtnode, JoinExpr))
4156 : : {
4157 : 896 : JoinExpr *j = (JoinExpr *) jtnode;
4158 : :
4159 : : /* Is it an unnamed JOIN with USING? */
4853 4160 [ + + + + ]: 896 : if (j->alias == NULL && j->usingClause)
4161 : : {
4162 : : /*
4163 : : * Yes, so check each join alias var to see if any of them are not
4164 : : * simple references to underlying columns. If so, we have a
4165 : : * dangerous situation and must pick unique aliases.
4166 : : */
4167 : 188 : RangeTblEntry *jrte = rt_fetch(j->rtindex, dpns->rtable);
4168 : :
4169 : : /* We need only examine the merged columns */
2492 4170 [ + + ]: 388 : for (int i = 0; i < jrte->joinmergedcols; i++)
4171 : : {
4172 : 252 : Node *aliasvar = list_nth(jrte->joinaliasvars, i);
4173 : :
4174 [ + + ]: 252 : if (!IsA(aliasvar, Var))
4853 4175 : 52 : return true;
4176 : : }
4177 : : }
4178 : :
4179 : : /* Nope, but inspect children */
4180 [ - + ]: 844 : if (has_dangerous_join_using(dpns, j->larg))
5057 tgl@sss.pgh.pa.us 4181 :UBC 0 : return true;
4853 tgl@sss.pgh.pa.us 4182 [ - + ]:CBC 844 : if (has_dangerous_join_using(dpns, j->rarg))
5057 tgl@sss.pgh.pa.us 4183 :UBC 0 : return true;
4184 : : }
4185 : : else
4186 [ # # ]: 0 : elog(ERROR, "unrecognized node type: %d",
4187 : : (int) nodeTag(jtnode));
5057 tgl@sss.pgh.pa.us 4188 :CBC 8434 : return false;
4189 : : }
4190 : :
4191 : : /*
4192 : : * set_using_names: select column aliases to be used for merged USING columns
4193 : : *
4194 : : * We do this during a recursive descent of the query jointree.
4195 : : * dpns->unique_using must already be set to determine the global strategy.
4196 : : *
4197 : : * Column alias info is saved in the dpns->rtable_columns list, which is
4198 : : * assumed to be filled with pre-zeroed deparse_columns structs.
4199 : : *
4200 : : * parentUsing is a list of all USING aliases assigned in parent joins of
4201 : : * the current jointree node. (The passed-in list must not be modified.)
4202 : : *
4203 : : * Note that we do not use per-deparse_columns hash tables in this function.
4204 : : * The number of names that need to be assigned should be small enough that
4205 : : * we don't need to trouble with that.
4206 : : */
4207 : : static void
4571 4208 : 8758 : set_using_names(deparse_namespace *dpns, Node *jtnode, List *parentUsing)
4209 : : {
5057 4210 [ + + ]: 8758 : if (IsA(jtnode, RangeTblRef))
4211 : : {
4212 : : /* nothing to do now */
4213 : : }
4214 [ + + ]: 4545 : else if (IsA(jtnode, FromExpr))
4215 : : {
4216 : 3605 : FromExpr *f = (FromExpr *) jtnode;
4217 : : ListCell *lc;
4218 : :
4219 [ + + + + : 6878 : foreach(lc, f->fromlist)
+ + ]
4571 4220 : 3273 : set_using_names(dpns, (Node *) lfirst(lc), parentUsing);
4221 : : }
5057 4222 [ + - ]: 940 : else if (IsA(jtnode, JoinExpr))
4223 : : {
4224 : 940 : JoinExpr *j = (JoinExpr *) jtnode;
4225 : 940 : RangeTblEntry *rte = rt_fetch(j->rtindex, dpns->rtable);
4226 : 940 : deparse_columns *colinfo = deparse_columns_fetch(j->rtindex, dpns);
4227 : : int *leftattnos;
4228 : : int *rightattnos;
4229 : : deparse_columns *leftcolinfo;
4230 : : deparse_columns *rightcolinfo;
4231 : : int i;
4232 : : ListCell *lc;
4233 : :
4234 : : /* Get info about the shape of the join */
4235 : 940 : identify_join_columns(j, rte, colinfo);
4236 : 940 : leftattnos = colinfo->leftattnos;
4237 : 940 : rightattnos = colinfo->rightattnos;
4238 : :
4239 : : /* Look up the not-yet-filled-in child deparse_columns structs */
4240 : 940 : leftcolinfo = deparse_columns_fetch(colinfo->leftrti, dpns);
4241 : 940 : rightcolinfo = deparse_columns_fetch(colinfo->rightrti, dpns);
4242 : :
4243 : : /*
4244 : : * If this join is unnamed, then we cannot substitute new aliases at
4245 : : * this level, so any name requirements pushed down to here must be
4246 : : * pushed down again to the children.
4247 : : */
4248 [ + + ]: 940 : if (rte->alias == NULL)
4249 : : {
4250 [ + + ]: 960 : for (i = 0; i < colinfo->num_cols; i++)
4251 : : {
4252 : 92 : char *colname = colinfo->colnames[i];
4253 : :
4254 [ + + ]: 92 : if (colname == NULL)
4255 : 16 : continue;
4256 : :
4257 : : /* Push down to left column, unless it's a system column */
4258 [ + + ]: 76 : if (leftattnos[i] > 0)
4259 : : {
4260 : 68 : expand_colnames_array_to(leftcolinfo, leftattnos[i]);
4261 : 68 : leftcolinfo->colnames[leftattnos[i] - 1] = colname;
4262 : : }
4263 : :
4264 : : /* Same on the righthand side */
4265 [ + - ]: 76 : if (rightattnos[i] > 0)
4266 : : {
4267 : 76 : expand_colnames_array_to(rightcolinfo, rightattnos[i]);
4268 : 76 : rightcolinfo->colnames[rightattnos[i] - 1] = colname;
4269 : : }
4270 : : }
4271 : : }
4272 : :
4273 : : /*
4274 : : * If there's a USING clause, select the USING column names and push
4275 : : * those names down to the children. We have two strategies:
4276 : : *
4277 : : * If dpns->unique_using is true, we force all USING names to be
4278 : : * unique across the whole query level. In principle we'd only need
4279 : : * the names of dangerous USING columns to be globally unique, but to
4280 : : * safely assign all USING names in a single pass, we have to enforce
4281 : : * the same uniqueness rule for all of them. However, if a USING
4282 : : * column's name has been pushed down from the parent, we should use
4283 : : * it as-is rather than making a uniqueness adjustment. This is
4284 : : * necessary when we're at an unnamed join, and it creates no risk of
4285 : : * ambiguity. Also, if there's a user-written output alias for a
4286 : : * merged column, we prefer to use that rather than the input name;
4287 : : * this simplifies the logic and seems likely to lead to less aliasing
4288 : : * overall.
4289 : : *
4290 : : * If dpns->unique_using is false, we only need USING names to be
4291 : : * unique within their own join RTE. We still need to honor
4292 : : * pushed-down names, though.
4293 : : *
4294 : : * Though significantly different in results, these two strategies are
4295 : : * implemented by the same code, with only the difference of whether
4296 : : * to put assigned names into dpns->using_names.
4297 : : */
4298 [ + + ]: 940 : if (j->usingClause)
4299 : : {
4300 : : /* Copy the input parentUsing list so we don't modify it */
4571 4301 : 280 : parentUsing = list_copy(parentUsing);
4302 : :
4303 : : /* USING names must correspond to the first join output columns */
5057 4304 : 280 : expand_colnames_array_to(colinfo, list_length(j->usingClause));
4305 : 280 : i = 0;
4306 [ + - + + : 664 : foreach(lc, j->usingClause)
+ + ]
4307 : : {
4308 : 384 : char *colname = strVal(lfirst(lc));
4309 : :
4310 : : /* Assert it's a merged column */
4311 [ + - - + ]: 384 : Assert(leftattnos[i] != 0 && rightattnos[i] != 0);
4312 : :
4313 : : /* Adopt passed-down name if any, else select unique name */
4314 [ + + ]: 384 : if (colinfo->colnames[i] != NULL)
4315 : 68 : colname = colinfo->colnames[i];
4316 : : else
4317 : : {
4318 : : /* Prefer user-written output alias if any */
4319 [ + + - + ]: 316 : if (rte->alias && i < list_length(rte->alias->colnames))
5057 tgl@sss.pgh.pa.us 4320 :UBC 0 : colname = strVal(list_nth(rte->alias->colnames, i));
4321 : : /* Make it appropriately unique */
5057 tgl@sss.pgh.pa.us 4322 :CBC 316 : colname = make_colname_unique(colname, dpns, colinfo);
4323 [ + + ]: 316 : if (dpns->unique_using)
4324 : 88 : dpns->using_names = lappend(dpns->using_names,
4325 : : colname);
4326 : : /* Save it as output column name, too */
4327 : 316 : colinfo->colnames[i] = colname;
4328 : : }
4329 : :
4330 : : /* Remember selected names for use later */
4331 : 384 : colinfo->usingNames = lappend(colinfo->usingNames, colname);
4571 4332 : 384 : parentUsing = lappend(parentUsing, colname);
4333 : :
4334 : : /* Push down to left column, unless it's a system column */
5057 4335 [ + - ]: 384 : if (leftattnos[i] > 0)
4336 : : {
4337 : 384 : expand_colnames_array_to(leftcolinfo, leftattnos[i]);
4338 : 384 : leftcolinfo->colnames[leftattnos[i] - 1] = colname;
4339 : : }
4340 : :
4341 : : /* Same on the righthand side */
4342 [ + - ]: 384 : if (rightattnos[i] > 0)
4343 : : {
4344 : 384 : expand_colnames_array_to(rightcolinfo, rightattnos[i]);
4345 : 384 : rightcolinfo->colnames[rightattnos[i] - 1] = colname;
4346 : : }
4347 : :
4348 : 384 : i++;
4349 : : }
4350 : : }
4351 : :
4352 : : /* Mark child deparse_columns structs with correct parentUsing info */
4571 4353 : 940 : leftcolinfo->parentUsing = parentUsing;
4354 : 940 : rightcolinfo->parentUsing = parentUsing;
4355 : :
4356 : : /* Now recursively assign USING column names in children */
4357 : 940 : set_using_names(dpns, j->larg, parentUsing);
4358 : 940 : set_using_names(dpns, j->rarg, parentUsing);
4359 : : }
4360 : : else
5057 tgl@sss.pgh.pa.us 4361 [ # # ]:UBC 0 : elog(ERROR, "unrecognized node type: %d",
4362 : : (int) nodeTag(jtnode));
5057 tgl@sss.pgh.pa.us 4363 :CBC 8758 : }
4364 : :
4365 : : /*
4366 : : * set_relation_column_names: select column aliases for a non-join RTE
4367 : : *
4368 : : * Column alias info is saved in *colinfo, which is assumed to be pre-zeroed.
4369 : : * If any colnames entries are already filled in, those override local
4370 : : * choices.
4371 : : */
4372 : : static void
4373 : 65393 : set_relation_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
4374 : : deparse_columns *colinfo)
4375 : : {
4376 : : int ncolumns;
4377 : : char **real_colnames;
4378 : : bool changed_any;
4379 : : int noldcolumns;
4380 : : int i;
4381 : : int j;
4382 : :
4383 : : /*
4384 : : * Construct an array of the current "real" column names of the RTE.
4385 : : * real_colnames[] will be indexed by physical column number, with NULL
4386 : : * entries for dropped columns.
4387 : : */
4388 [ + + ]: 65393 : if (rte->rtekind == RTE_RELATION)
4389 : : {
4390 : : /* Relation --- look to the system catalogs for up-to-date info */
4391 : : Relation rel;
4392 : : TupleDesc tupdesc;
4393 : :
4394 : 54236 : rel = relation_open(rte->relid, AccessShareLock);
4395 : 54236 : tupdesc = RelationGetDescr(rel);
4396 : :
4397 : 54236 : ncolumns = tupdesc->natts;
80 michael@paquier.xyz 4398 :GNC 54236 : real_colnames = palloc_array(char *, ncolumns);
4399 : :
5057 tgl@sss.pgh.pa.us 4400 [ + + ]:CBC 338827 : for (i = 0; i < ncolumns; i++)
4401 : : {
3364 andres@anarazel.de 4402 : 284591 : Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
4403 : :
4404 [ + + ]: 284591 : if (attr->attisdropped)
5057 tgl@sss.pgh.pa.us 4405 : 1898 : real_colnames[i] = NULL;
4406 : : else
3364 andres@anarazel.de 4407 : 282693 : real_colnames[i] = pstrdup(NameStr(attr->attname));
4408 : : }
5057 tgl@sss.pgh.pa.us 4409 : 54236 : relation_close(rel, AccessShareLock);
4410 : : }
4411 : : else
4412 : : {
4413 : : /* Otherwise get the column names from eref or expandRTE() */
4414 : : List *colnames;
4415 : : ListCell *lc;
4416 : :
4417 : : /*
4418 : : * Functions returning composites have the annoying property that some
4419 : : * of the composite type's columns might have been dropped since the
4420 : : * query was parsed. If possible, use expandRTE() to handle that
4421 : : * case, since it has the tedious logic needed to find out about
4422 : : * dropped columns. However, if we're explaining a plan, then we
4423 : : * don't have rte->functions because the planner thinks that won't be
4424 : : * needed later, and that breaks expandRTE(). So in that case we have
4425 : : * to rely on rte->eref, which may lead us to report a dropped
4426 : : * column's old name; that seems close enough for EXPLAIN's purposes.
4427 : : *
4428 : : * For non-RELATION, non-FUNCTION RTEs, we can just look at rte->eref,
4429 : : * which should be sufficiently up-to-date: no other RTE types can
4430 : : * have columns get dropped from under them after parsing.
4431 : : */
1568 4432 [ + + + + ]: 11157 : if (rte->rtekind == RTE_FUNCTION && rte->functions != NIL)
4433 : : {
4434 : : /* Since we're not creating Vars, rtindex etc. don't matter */
658 dean.a.rasheed@gmail 4435 : 564 : expandRTE(rte, 1, 0, VAR_RETURNING_DEFAULT, -1,
4436 : : true /* include dropped */ , &colnames, NULL);
4437 : : }
4438 : : else
1568 tgl@sss.pgh.pa.us 4439 : 10593 : colnames = rte->eref->colnames;
4440 : :
4441 : 11157 : ncolumns = list_length(colnames);
80 michael@paquier.xyz 4442 :GNC 11157 : real_colnames = palloc_array(char *, ncolumns);
4443 : :
5057 tgl@sss.pgh.pa.us 4444 :CBC 11157 : i = 0;
1568 4445 [ + + + + : 34747 : foreach(lc, colnames)
+ + ]
4446 : : {
4447 : : /*
4448 : : * If the column name we find here is an empty string, then it's a
4449 : : * dropped column, so change to NULL.
4450 : : */
4492 4451 : 23590 : char *cname = strVal(lfirst(lc));
4452 : :
4453 [ + + ]: 23590 : if (cname[0] == '\0')
4454 : 36 : cname = NULL;
4455 : 23590 : real_colnames[i] = cname;
5057 4456 : 23590 : i++;
4457 : : }
4458 : : }
4459 : :
4460 : : /*
4461 : : * Ensure colinfo->colnames has a slot for each column. (It could be long
4462 : : * enough already, if we pushed down a name for the last column.) Note:
4463 : : * it's possible that there are now more columns than there were when the
4464 : : * query was parsed, ie colnames could be longer than rte->eref->colnames.
4465 : : * We must assign unique aliases to the new columns too, else there could
4466 : : * be unresolved conflicts when the view/rule is reloaded.
4467 : : */
4468 : 65393 : expand_colnames_array_to(colinfo, ncolumns);
4469 [ - + ]: 65393 : Assert(colinfo->num_cols == ncolumns);
4470 : :
4471 : : /*
4472 : : * Make sufficiently large new_colnames and is_new_col arrays, too.
4473 : : *
4474 : : * Note: because we leave colinfo->num_new_cols zero until after the loop,
4475 : : * colname_is_unique will not consult that array, which is fine because it
4476 : : * would only be duplicate effort.
4477 : : */
80 michael@paquier.xyz 4478 :GNC 65393 : colinfo->new_colnames = palloc_array(char *, ncolumns);
4479 : 65393 : colinfo->is_new_col = palloc_array(bool, ncolumns);
4480 : :
4481 : : /* If the RTE is wide enough, use a hash table to avoid O(N^2) costs */
786 tgl@sss.pgh.pa.us 4482 :CBC 65393 : build_colinfo_names_hash(colinfo);
4483 : :
4484 : : /*
4485 : : * Scan the columns, select a unique alias for each one, and store it in
4486 : : * colinfo->colnames and colinfo->new_colnames. The former array has NULL
4487 : : * entries for dropped columns, the latter omits them. Also mark
4488 : : * new_colnames entries as to whether they are new since parse time; this
4489 : : * is the case for entries beyond the length of rte->eref->colnames.
4490 : : */
5057 4491 : 65393 : noldcolumns = list_length(rte->eref->colnames);
4492 : 65393 : changed_any = false;
4493 : 65393 : j = 0;
4494 [ + + ]: 373574 : for (i = 0; i < ncolumns; i++)
4495 : : {
4496 : 308181 : char *real_colname = real_colnames[i];
4497 : 308181 : char *colname = colinfo->colnames[i];
4498 : :
4499 : : /* Skip dropped columns */
4500 [ + + ]: 308181 : if (real_colname == NULL)
4501 : : {
4502 [ - + ]: 1934 : Assert(colname == NULL); /* colnames[i] is already NULL */
4503 : 1934 : continue;
4504 : : }
4505 : :
4506 : : /* If alias already assigned, that's what to use */
4507 [ + + ]: 306247 : if (colname == NULL)
4508 : : {
4509 : : /* If user wrote an alias, prefer that over real column name */
4510 [ + + + + ]: 305547 : if (rte->alias && i < list_length(rte->alias->colnames))
4511 : 30850 : colname = strVal(list_nth(rte->alias->colnames, i));
4512 : : else
4513 : 274697 : colname = real_colname;
4514 : :
4515 : : /* Unique-ify and insert into colinfo */
4516 : 305547 : colname = make_colname_unique(colname, dpns, colinfo);
4517 : :
4518 : 305547 : colinfo->colnames[i] = colname;
786 4519 : 305547 : add_to_names_hash(colinfo, colname);
4520 : : }
4521 : :
4522 : : /* Put names of non-dropped columns in new_colnames[] too */
5057 4523 : 306247 : colinfo->new_colnames[j] = colname;
4524 : : /* And mark them as new or not */
4525 : 306247 : colinfo->is_new_col[j] = (i >= noldcolumns);
4526 : 306247 : j++;
4527 : :
4528 : : /* Remember if any assigned aliases differ from "real" name */
4529 [ + + + + ]: 306247 : if (!changed_any && strcmp(colname, real_colname) != 0)
4530 : 805 : changed_any = true;
4531 : : }
4532 : :
4533 : : /* We're now done needing the colinfo's names_hash */
786 4534 : 65393 : destroy_colinfo_names_hash(colinfo);
4535 : :
4536 : : /*
4537 : : * Set correct length for new_colnames[] array. (Note: if columns have
4538 : : * been added, colinfo->num_cols includes them, which is not really quite
4539 : : * right but is harmless, since any new columns must be at the end where
4540 : : * they won't affect varattnos of pre-existing columns.)
4541 : : */
5057 4542 : 65393 : colinfo->num_new_cols = j;
4543 : :
4544 : : /*
4545 : : * For a relation RTE, we need only print the alias column names if any
4546 : : * are different from the underlying "real" names. For a function RTE,
4547 : : * always emit a complete column alias list; this is to protect against
4548 : : * possible instability of the default column names (eg, from altering
4549 : : * parameter names). For tablefunc RTEs, we never print aliases, because
4550 : : * the column names are part of the clause itself. For other RTE types,
4551 : : * print if we changed anything OR if there were user-written column
4552 : : * aliases (since the latter would be part of the underlying "reality").
4553 : : */
4554 [ + + ]: 65393 : if (rte->rtekind == RTE_RELATION)
4555 : 54236 : colinfo->printaliases = changed_any;
4556 [ + + ]: 11157 : else if (rte->rtekind == RTE_FUNCTION)
4557 : 1042 : colinfo->printaliases = true;
3529 alvherre@alvh.no-ip. 4558 [ + + ]: 10115 : else if (rte->rtekind == RTE_TABLEFUNC)
4559 : 130 : colinfo->printaliases = false;
5057 tgl@sss.pgh.pa.us 4560 [ + + + + ]: 9985 : else if (rte->alias && rte->alias->colnames != NIL)
4561 : 644 : colinfo->printaliases = true;
4562 : : else
4563 : 9341 : colinfo->printaliases = changed_any;
4564 : 65393 : }
4565 : :
4566 : : /*
4567 : : * set_join_column_names: select column aliases for a join RTE
4568 : : *
4569 : : * Column alias info is saved in *colinfo, which is assumed to be pre-zeroed.
4570 : : * If any colnames entries are already filled in, those override local
4571 : : * choices. Also, names for USING columns were already chosen by
4572 : : * set_using_names(). We further expect that column alias selection has been
4573 : : * completed for both input RTEs.
4574 : : */
4575 : : static void
4576 : 940 : set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
4577 : : deparse_columns *colinfo)
4578 : : {
4579 : : deparse_columns *leftcolinfo;
4580 : : deparse_columns *rightcolinfo;
4581 : : bool changed_any;
4582 : : int noldcolumns;
4583 : : int nnewcolumns;
4584 : 940 : Bitmapset *leftmerged = NULL;
4585 : 940 : Bitmapset *rightmerged = NULL;
4586 : : int i;
4587 : : int j;
4588 : : int ic;
4589 : : int jc;
4590 : :
4591 : : /* Look up the previously-filled-in child deparse_columns structs */
4592 : 940 : leftcolinfo = deparse_columns_fetch(colinfo->leftrti, dpns);
4593 : 940 : rightcolinfo = deparse_columns_fetch(colinfo->rightrti, dpns);
4594 : :
4595 : : /*
4596 : : * Ensure colinfo->colnames has a slot for each column. (It could be long
4597 : : * enough already, if we pushed down a name for the last column.) Note:
4598 : : * it's possible that one or both inputs now have more columns than there
4599 : : * were when the query was parsed, but we'll deal with that below. We
4600 : : * only need entries in colnames for pre-existing columns.
4601 : : */
4602 : 940 : noldcolumns = list_length(rte->eref->colnames);
4603 : 940 : expand_colnames_array_to(colinfo, noldcolumns);
4604 [ - + ]: 940 : Assert(colinfo->num_cols == noldcolumns);
4605 : :
4606 : : /* If the RTE is wide enough, use a hash table to avoid O(N^2) costs */
786 4607 : 940 : build_colinfo_names_hash(colinfo);
4608 : :
4609 : : /*
4610 : : * Scan the join output columns, select an alias for each one, and store
4611 : : * it in colinfo->colnames. If there are USING columns, set_using_names()
4612 : : * already selected their names, so we can start the loop at the first
4613 : : * non-merged column.
4614 : : */
5057 4615 : 940 : changed_any = false;
4616 [ + + ]: 30521 : for (i = list_length(colinfo->usingNames); i < noldcolumns; i++)
4617 : : {
4618 : 29581 : char *colname = colinfo->colnames[i];
4619 : : char *real_colname;
4620 : :
4621 : : /* Join column must refer to at least one input column */
2492 4622 [ + + - + ]: 29581 : Assert(colinfo->leftattnos[i] != 0 || colinfo->rightattnos[i] != 0);
4623 : :
4624 : : /* Get the child column name */
5057 4625 [ + + ]: 29581 : if (colinfo->leftattnos[i] > 0)
4626 : 20735 : real_colname = leftcolinfo->colnames[colinfo->leftattnos[i] - 1];
4627 [ + - ]: 8846 : else if (colinfo->rightattnos[i] > 0)
4628 : 8846 : real_colname = rightcolinfo->colnames[colinfo->rightattnos[i] - 1];
4629 : : else
4630 : : {
4631 : : /* We're joining system columns --- use eref name */
4853 tgl@sss.pgh.pa.us 4632 :UBC 0 : real_colname = strVal(list_nth(rte->eref->colnames, i));
4633 : : }
4634 : :
4635 : : /* If child col has been dropped, no need to assign a join colname */
2492 tgl@sss.pgh.pa.us 4636 [ + + ]:CBC 29581 : if (real_colname == NULL)
4637 : : {
4638 : 4 : colinfo->colnames[i] = NULL;
4639 : 4 : continue;
4640 : : }
4641 : :
4642 : : /* In an unnamed join, just report child column names as-is */
5057 4643 [ + + ]: 29577 : if (rte->alias == NULL)
4644 : : {
4645 : 29325 : colinfo->colnames[i] = real_colname;
786 4646 : 29325 : add_to_names_hash(colinfo, real_colname);
5057 4647 : 29325 : continue;
4648 : : }
4649 : :
4650 : : /* If alias already assigned, that's what to use */
4651 [ + - ]: 252 : if (colname == NULL)
4652 : : {
4653 : : /* If user wrote an alias, prefer that over real column name */
4654 [ + - + + ]: 252 : if (rte->alias && i < list_length(rte->alias->colnames))
4655 : 64 : colname = strVal(list_nth(rte->alias->colnames, i));
4656 : : else
4657 : 188 : colname = real_colname;
4658 : :
4659 : : /* Unique-ify and insert into colinfo */
4660 : 252 : colname = make_colname_unique(colname, dpns, colinfo);
4661 : :
4662 : 252 : colinfo->colnames[i] = colname;
786 4663 : 252 : add_to_names_hash(colinfo, colname);
4664 : : }
4665 : :
4666 : : /* Remember if any assigned aliases differ from "real" name */
5057 4667 [ + + + + ]: 252 : if (!changed_any && strcmp(colname, real_colname) != 0)
4668 : 16 : changed_any = true;
4669 : : }
4670 : :
4671 : : /*
4672 : : * Calculate number of columns the join would have if it were re-parsed
4673 : : * now, and create storage for the new_colnames and is_new_col arrays.
4674 : : *
4675 : : * Note: colname_is_unique will be consulting new_colnames[] during the
4676 : : * loops below, so its not-yet-filled entries must be zeroes.
4677 : : */
4678 : 1880 : nnewcolumns = leftcolinfo->num_new_cols + rightcolinfo->num_new_cols -
4679 : 940 : list_length(colinfo->usingNames);
4680 : 940 : colinfo->num_new_cols = nnewcolumns;
80 michael@paquier.xyz 4681 :GNC 940 : colinfo->new_colnames = palloc0_array(char *, nnewcolumns);
4682 : 940 : colinfo->is_new_col = palloc0_array(bool, nnewcolumns);
4683 : :
4684 : : /*
4685 : : * Generating the new_colnames array is a bit tricky since any new columns
4686 : : * added since parse time must be inserted in the right places. This code
4687 : : * must match the parser, which will order a join's columns as merged
4688 : : * columns first (in USING-clause order), then non-merged columns from the
4689 : : * left input (in attnum order), then non-merged columns from the right
4690 : : * input (ditto). If one of the inputs is itself a join, its columns will
4691 : : * be ordered according to the same rule, which means newly-added columns
4692 : : * might not be at the end. We can figure out what's what by consulting
4693 : : * the leftattnos and rightattnos arrays plus the input is_new_col arrays.
4694 : : *
4695 : : * In these loops, i indexes leftattnos/rightattnos (so it's join varattno
4696 : : * less one), j indexes new_colnames/is_new_col, and ic/jc have similar
4697 : : * meanings for the current child RTE.
4698 : : */
4699 : :
4700 : : /* Handle merged columns; they are first and can't be new */
5057 tgl@sss.pgh.pa.us 4701 :CBC 940 : i = j = 0;
4702 : 940 : while (i < noldcolumns &&
4703 [ + - + - ]: 1324 : colinfo->leftattnos[i] != 0 &&
4704 [ + + ]: 1324 : colinfo->rightattnos[i] != 0)
4705 : : {
4706 : : /* column name is already determined and known unique */
4707 : 384 : colinfo->new_colnames[j] = colinfo->colnames[i];
4708 : 384 : colinfo->is_new_col[j] = false;
4709 : :
4710 : : /* build bitmapsets of child attnums of merged columns */
4711 [ + - ]: 384 : if (colinfo->leftattnos[i] > 0)
4712 : 384 : leftmerged = bms_add_member(leftmerged, colinfo->leftattnos[i]);
4713 [ + - ]: 384 : if (colinfo->rightattnos[i] > 0)
4714 : 384 : rightmerged = bms_add_member(rightmerged, colinfo->rightattnos[i]);
4715 : :
4716 : 384 : i++, j++;
4717 : : }
4718 : :
4719 : : /* Handle non-merged left-child columns */
4720 : 940 : ic = 0;
4721 [ + + ]: 22383 : for (jc = 0; jc < leftcolinfo->num_new_cols; jc++)
4722 : : {
4723 : 21443 : char *child_colname = leftcolinfo->new_colnames[jc];
4724 : :
4725 [ + + ]: 21443 : if (!leftcolinfo->is_new_col[jc])
4726 : : {
4727 : : /* Advance ic to next non-dropped old column of left child */
4728 [ + - ]: 21171 : while (ic < leftcolinfo->num_cols &&
4729 [ + + ]: 21171 : leftcolinfo->colnames[ic] == NULL)
4730 : 56 : ic++;
4731 [ - + ]: 21115 : Assert(ic < leftcolinfo->num_cols);
4732 : 21115 : ic++;
4733 : : /* If it is a merged column, we already processed it */
4734 [ + + ]: 21115 : if (bms_is_member(ic, leftmerged))
4735 : 384 : continue;
4736 : : /* Else, advance i to the corresponding existing join column */
4737 [ + - ]: 20735 : while (i < colinfo->num_cols &&
4738 [ + + ]: 20735 : colinfo->colnames[i] == NULL)
4739 : 4 : i++;
4740 [ - + ]: 20731 : Assert(i < colinfo->num_cols);
4741 [ - + ]: 20731 : Assert(ic == colinfo->leftattnos[i]);
4742 : : /* Use the already-assigned name of this column */
4743 : 20731 : colinfo->new_colnames[j] = colinfo->colnames[i];
4744 : 20731 : i++;
4745 : : }
4746 : : else
4747 : : {
4748 : : /*
4749 : : * Unique-ify the new child column name and assign, unless we're
4750 : : * in an unnamed join, in which case just copy
4751 : : */
4752 [ + + ]: 328 : if (rte->alias != NULL)
4753 : : {
4754 : 176 : colinfo->new_colnames[j] =
4755 : 88 : make_colname_unique(child_colname, dpns, colinfo);
4756 [ + + ]: 88 : if (!changed_any &&
4757 [ + + ]: 72 : strcmp(colinfo->new_colnames[j], child_colname) != 0)
4758 : 8 : changed_any = true;
4759 : : }
4760 : : else
4761 : 240 : colinfo->new_colnames[j] = child_colname;
786 4762 : 328 : add_to_names_hash(colinfo, colinfo->new_colnames[j]);
4763 : : }
4764 : :
5057 4765 : 21059 : colinfo->is_new_col[j] = leftcolinfo->is_new_col[jc];
4766 : 21059 : j++;
4767 : : }
4768 : :
4769 : : /* Handle non-merged right-child columns in exactly the same way */
4770 : 940 : ic = 0;
4771 [ + + ]: 10282 : for (jc = 0; jc < rightcolinfo->num_new_cols; jc++)
4772 : : {
4773 : 9342 : char *child_colname = rightcolinfo->new_colnames[jc];
4774 : :
4775 [ + + ]: 9342 : if (!rightcolinfo->is_new_col[jc])
4776 : : {
4777 : : /* Advance ic to next non-dropped old column of right child */
4778 [ + - ]: 9230 : while (ic < rightcolinfo->num_cols &&
4779 [ - + ]: 9230 : rightcolinfo->colnames[ic] == NULL)
5057 tgl@sss.pgh.pa.us 4780 :UBC 0 : ic++;
5057 tgl@sss.pgh.pa.us 4781 [ - + ]:CBC 9230 : Assert(ic < rightcolinfo->num_cols);
4782 : 9230 : ic++;
4783 : : /* If it is a merged column, we already processed it */
4784 [ + + ]: 9230 : if (bms_is_member(ic, rightmerged))
4785 : 384 : continue;
4786 : : /* Else, advance i to the corresponding existing join column */
4787 [ + - ]: 8846 : while (i < colinfo->num_cols &&
4788 [ - + ]: 8846 : colinfo->colnames[i] == NULL)
5057 tgl@sss.pgh.pa.us 4789 :UBC 0 : i++;
5057 tgl@sss.pgh.pa.us 4790 [ - + ]:CBC 8846 : Assert(i < colinfo->num_cols);
4791 [ - + ]: 8846 : Assert(ic == colinfo->rightattnos[i]);
4792 : : /* Use the already-assigned name of this column */
4793 : 8846 : colinfo->new_colnames[j] = colinfo->colnames[i];
4794 : 8846 : i++;
4795 : : }
4796 : : else
4797 : : {
4798 : : /*
4799 : : * Unique-ify the new child column name and assign, unless we're
4800 : : * in an unnamed join, in which case just copy
4801 : : */
4802 [ + + ]: 112 : if (rte->alias != NULL)
4803 : : {
4804 : 32 : colinfo->new_colnames[j] =
4805 : 16 : make_colname_unique(child_colname, dpns, colinfo);
4806 [ + - ]: 16 : if (!changed_any &&
4807 [ + + ]: 16 : strcmp(colinfo->new_colnames[j], child_colname) != 0)
4808 : 8 : changed_any = true;
4809 : : }
4810 : : else
4811 : 96 : colinfo->new_colnames[j] = child_colname;
786 4812 : 112 : add_to_names_hash(colinfo, colinfo->new_colnames[j]);
4813 : : }
4814 : :
5057 4815 : 8958 : colinfo->is_new_col[j] = rightcolinfo->is_new_col[jc];
4816 : 8958 : j++;
4817 : : }
4818 : :
4819 : : /* Assert we processed the right number of columns */
4820 : : #ifdef USE_ASSERT_CHECKING
4821 [ - + - - ]: 940 : while (i < colinfo->num_cols && colinfo->colnames[i] == NULL)
5057 tgl@sss.pgh.pa.us 4822 :UBC 0 : i++;
5057 tgl@sss.pgh.pa.us 4823 [ - + ]:CBC 940 : Assert(i == colinfo->num_cols);
4824 [ - + ]: 940 : Assert(j == nnewcolumns);
4825 : : #endif
4826 : :
4827 : : /* We're now done needing the colinfo's names_hash */
786 4828 : 940 : destroy_colinfo_names_hash(colinfo);
4829 : :
4830 : : /*
4831 : : * For a named join, print column aliases if we changed any from the child
4832 : : * names. Unnamed joins cannot print aliases.
4833 : : */
5057 4834 [ + + ]: 940 : if (rte->alias != NULL)
4835 : 72 : colinfo->printaliases = changed_any;
4836 : : else
4837 : 868 : colinfo->printaliases = false;
4838 : 940 : }
4839 : :
4840 : : /*
4841 : : * colname_is_unique: is colname distinct from already-chosen column names?
4842 : : *
4843 : : * dpns is query-wide info, colinfo is for the column's RTE
4844 : : */
4845 : : static bool
3292 peter_e@gmx.net 4846 : 307850 : colname_is_unique(const char *colname, deparse_namespace *dpns,
4847 : : deparse_columns *colinfo)
4848 : : {
4849 : : int i;
4850 : : ListCell *lc;
4851 : :
4852 : : /*
4853 : : * If we have a hash table, consult that instead of linearly scanning the
4854 : : * colinfo's strings.
4855 : : */
786 tgl@sss.pgh.pa.us 4856 [ + + ]: 307850 : if (colinfo->names_hash)
4857 : : {
4858 [ + + ]: 10922 : if (hash_search(colinfo->names_hash,
4859 : : colname,
4860 : : HASH_FIND,
4861 : : NULL) != NULL)
5057 4862 : 64 : return false;
4863 : : }
4864 : : else
4865 : : {
4866 : : /* Check against already-assigned column aliases within RTE */
786 4867 [ + + ]: 4093908 : for (i = 0; i < colinfo->num_cols; i++)
4868 : : {
4869 : 3798499 : char *oldname = colinfo->colnames[i];
4870 : :
4871 [ + + + + ]: 3798499 : if (oldname && strcmp(oldname, colname) == 0)
4872 : 1519 : return false;
4873 : : }
4874 : :
4875 : : /*
4876 : : * If we're building a new_colnames array, check that too (this will
4877 : : * be partially but not completely redundant with the previous checks)
4878 : : */
4879 [ + + ]: 296257 : for (i = 0; i < colinfo->num_new_cols; i++)
4880 : : {
4881 : 864 : char *oldname = colinfo->new_colnames[i];
4882 : :
4883 [ + + + + ]: 864 : if (oldname && strcmp(oldname, colname) == 0)
4884 : 16 : return false;
4885 : : }
4886 : :
4887 : : /*
4888 : : * Also check against names already assigned for parent-join USING
4889 : : * cols
4890 : : */
4891 [ + + + + : 297089 : foreach(lc, colinfo->parentUsing)
+ + ]
4892 : : {
4893 : 1700 : char *oldname = (char *) lfirst(lc);
4894 : :
4895 [ + + ]: 1700 : if (strcmp(oldname, colname) == 0)
4896 : 4 : return false;
4897 : : }
4898 : : }
4899 : :
4900 : : /*
4901 : : * Also check against USING-column names that must be globally unique.
4902 : : * These are not hashed, but there should be few of them.
4903 : : */
4904 [ + + + + : 306819 : foreach(lc, dpns->using_names)
+ + ]
4905 : : {
4571 4906 : 600 : char *oldname = (char *) lfirst(lc);
4907 : :
4908 [ + + ]: 600 : if (strcmp(oldname, colname) == 0)
4909 : 28 : return false;
4910 : : }
4911 : :
5057 4912 : 306219 : return true;
4913 : : }
4914 : :
4915 : : /*
4916 : : * make_colname_unique: modify colname if necessary to make it unique
4917 : : *
4918 : : * dpns is query-wide info, colinfo is for the column's RTE
4919 : : */
4920 : : static char *
4921 : 306219 : make_colname_unique(char *colname, deparse_namespace *dpns,
4922 : : deparse_columns *colinfo)
4923 : : {
4924 : : /*
4925 : : * If the selected name isn't unique, append digits to make it so. For a
4926 : : * very long input name, we might have to truncate to stay within
4927 : : * NAMEDATALEN.
4928 : : */
4929 [ + + ]: 306219 : if (!colname_is_unique(colname, dpns, colinfo))
4930 : : {
4007 4931 : 1150 : int colnamelen = strlen(colname);
4932 : 1150 : char *modname = (char *) palloc(colnamelen + 16);
5057 4933 : 1150 : int i = 0;
4934 : :
4935 : : do
4936 : : {
4007 4937 : 1631 : i++;
4938 : : for (;;)
4939 : : {
4940 : 1631 : memcpy(modname, colname, colnamelen);
4941 : 1631 : sprintf(modname + colnamelen, "_%d", i);
4942 [ + - ]: 1631 : if (strlen(modname) < NAMEDATALEN)
4943 : 1631 : break;
4944 : : /* drop chars from colname to keep all the digits */
4007 tgl@sss.pgh.pa.us 4945 :UBC 0 : colnamelen = pg_mbcliplen(colname, colnamelen,
4946 : : colnamelen - 1);
4947 : : }
5057 tgl@sss.pgh.pa.us 4948 [ + + ]:CBC 1631 : } while (!colname_is_unique(modname, dpns, colinfo));
4949 : 1150 : colname = modname;
4950 : : }
4951 : 306219 : return colname;
4952 : : }
4953 : :
4954 : : /*
4955 : : * expand_colnames_array_to: make colinfo->colnames at least n items long
4956 : : *
4957 : : * Any added array entries are initialized to zero.
4958 : : */
4959 : : static void
4960 : 67525 : expand_colnames_array_to(deparse_columns *colinfo, int n)
4961 : : {
4962 [ + + ]: 67525 : if (n > colinfo->num_cols)
4963 : : {
4964 [ + + ]: 65440 : if (colinfo->colnames == NULL)
1454 peter@eisentraut.org 4965 : 64504 : colinfo->colnames = palloc0_array(char *, n);
4966 : : else
4967 : 936 : colinfo->colnames = repalloc0_array(colinfo->colnames, char *, colinfo->num_cols, n);
5057 tgl@sss.pgh.pa.us 4968 : 65440 : colinfo->num_cols = n;
4969 : : }
4970 : 67525 : }
4971 : :
4972 : : /*
4973 : : * build_colinfo_names_hash: optionally construct a hash table for colinfo
4974 : : */
4975 : : static void
786 4976 : 66333 : build_colinfo_names_hash(deparse_columns *colinfo)
4977 : : {
4978 : : HASHCTL hash_ctl;
4979 : : int i;
4980 : : ListCell *lc;
4981 : :
4982 : : /*
4983 : : * Use a hash table only for RTEs with at least 32 columns. (The cutoff
4984 : : * is somewhat arbitrary, but let's choose it so that this code does get
4985 : : * exercised in the regression tests.)
4986 : : */
4987 [ + + ]: 66333 : if (colinfo->num_cols < 32)
4988 : 65513 : return;
4989 : :
4990 : : /*
4991 : : * Set up the hash table. The entries are just strings with no other
4992 : : * payload.
4993 : : */
4994 : 820 : hash_ctl.keysize = NAMEDATALEN;
4995 : 820 : hash_ctl.entrysize = NAMEDATALEN;
4996 : 820 : hash_ctl.hcxt = CurrentMemoryContext;
4997 : 1640 : colinfo->names_hash = hash_create("deparse_columns names",
4998 : 820 : colinfo->num_cols + colinfo->num_new_cols,
4999 : : &hash_ctl,
5000 : : HASH_ELEM | HASH_STRINGS | HASH_CONTEXT);
5001 : :
5002 : : /*
5003 : : * Preload the hash table with any names already present (these would have
5004 : : * come from set_using_names).
5005 : : */
5006 [ + + ]: 38320 : for (i = 0; i < colinfo->num_cols; i++)
5007 : : {
5008 : 37500 : char *oldname = colinfo->colnames[i];
5009 : :
5010 [ - + ]: 37500 : if (oldname)
786 tgl@sss.pgh.pa.us 5011 :UBC 0 : add_to_names_hash(colinfo, oldname);
5012 : : }
5013 : :
786 tgl@sss.pgh.pa.us 5014 [ - + ]:CBC 820 : for (i = 0; i < colinfo->num_new_cols; i++)
5015 : : {
786 tgl@sss.pgh.pa.us 5016 :UBC 0 : char *oldname = colinfo->new_colnames[i];
5017 : :
5018 [ # # ]: 0 : if (oldname)
5019 : 0 : add_to_names_hash(colinfo, oldname);
5020 : : }
5021 : :
786 tgl@sss.pgh.pa.us 5022 [ - + - - :CBC 820 : foreach(lc, colinfo->parentUsing)
- + ]
5023 : : {
786 tgl@sss.pgh.pa.us 5024 :UBC 0 : char *oldname = (char *) lfirst(lc);
5025 : :
5026 : 0 : add_to_names_hash(colinfo, oldname);
5027 : : }
5028 : : }
5029 : :
5030 : : /*
5031 : : * add_to_names_hash: add a string to the names_hash, if we're using one
5032 : : */
5033 : : static void
786 tgl@sss.pgh.pa.us 5034 :CBC 335564 : add_to_names_hash(deparse_columns *colinfo, const char *name)
5035 : : {
5036 [ + + ]: 335564 : if (colinfo->names_hash)
5037 : 37500 : (void) hash_search(colinfo->names_hash,
5038 : : name,
5039 : : HASH_ENTER,
5040 : : NULL);
5041 : 335564 : }
5042 : :
5043 : : /*
5044 : : * destroy_colinfo_names_hash: destroy hash table when done with it
5045 : : */
5046 : : static void
5047 : 66333 : destroy_colinfo_names_hash(deparse_columns *colinfo)
5048 : : {
5049 [ + + ]: 66333 : if (colinfo->names_hash)
5050 : : {
5051 : 820 : hash_destroy(colinfo->names_hash);
5052 : 820 : colinfo->names_hash = NULL;
5053 : : }
5054 : 66333 : }
5055 : :
5056 : : /*
5057 : : * identify_join_columns: figure out where columns of a join come from
5058 : : *
5059 : : * Fills the join-specific fields of the colinfo struct, except for
5060 : : * usingNames which is filled later.
5061 : : */
5062 : : static void
5057 5063 : 940 : identify_join_columns(JoinExpr *j, RangeTblEntry *jrte,
5064 : : deparse_columns *colinfo)
5065 : : {
5066 : : int numjoincols;
5067 : : int jcolno;
5068 : : int rcolno;
5069 : : ListCell *lc;
5070 : :
5071 : : /* Extract left/right child RT indexes */
5072 [ + + ]: 940 : if (IsA(j->larg, RangeTblRef))
5073 : 605 : colinfo->leftrti = ((RangeTblRef *) j->larg)->rtindex;
5074 [ + - ]: 335 : else if (IsA(j->larg, JoinExpr))
5075 : 335 : colinfo->leftrti = ((JoinExpr *) j->larg)->rtindex;
5076 : : else
5057 tgl@sss.pgh.pa.us 5077 [ # # ]:UBC 0 : elog(ERROR, "unrecognized node type in jointree: %d",
5078 : : (int) nodeTag(j->larg));
5057 tgl@sss.pgh.pa.us 5079 [ + - ]:CBC 940 : if (IsA(j->rarg, RangeTblRef))
5080 : 940 : colinfo->rightrti = ((RangeTblRef *) j->rarg)->rtindex;
5057 tgl@sss.pgh.pa.us 5081 [ # # ]:UBC 0 : else if (IsA(j->rarg, JoinExpr))
5082 : 0 : colinfo->rightrti = ((JoinExpr *) j->rarg)->rtindex;
5083 : : else
5084 [ # # ]: 0 : elog(ERROR, "unrecognized node type in jointree: %d",
5085 : : (int) nodeTag(j->rarg));
5086 : :
5087 : : /* Assert children will be processed earlier than join in second pass */
5057 tgl@sss.pgh.pa.us 5088 [ - + ]:CBC 940 : Assert(colinfo->leftrti < j->rtindex);
5089 [ - + ]: 940 : Assert(colinfo->rightrti < j->rtindex);
5090 : :
5091 : : /* Initialize result arrays with zeroes */
5092 : 940 : numjoincols = list_length(jrte->joinaliasvars);
5093 [ - + ]: 940 : Assert(numjoincols == list_length(jrte->eref->colnames));
80 michael@paquier.xyz 5094 :GNC 940 : colinfo->leftattnos = palloc0_array(int, numjoincols);
5095 : 940 : colinfo->rightattnos = palloc0_array(int, numjoincols);
5096 : :
5097 : : /*
5098 : : * Deconstruct RTE's joinleftcols/joinrightcols into desired format.
5099 : : * Recall that the column(s) merged due to USING are the first column(s)
5100 : : * of the join output. We need not do anything special while scanning
5101 : : * joinleftcols, but while scanning joinrightcols we must distinguish
5102 : : * merged from unmerged columns.
5103 : : */
2492 tgl@sss.pgh.pa.us 5104 :CBC 940 : jcolno = 0;
5105 [ + - + + : 22059 : foreach(lc, jrte->joinleftcols)
+ + ]
5106 : : {
5107 : 21119 : int leftattno = lfirst_int(lc);
5108 : :
5109 : 21119 : colinfo->leftattnos[jcolno++] = leftattno;
5110 : : }
5111 : 940 : rcolno = 0;
5112 [ + - + + : 10170 : foreach(lc, jrte->joinrightcols)
+ + ]
5113 : : {
5114 : 9230 : int rightattno = lfirst_int(lc);
5115 : :
5116 [ + + ]: 9230 : if (rcolno < jrte->joinmergedcols) /* merged column? */
5117 : 384 : colinfo->rightattnos[rcolno] = rightattno;
5118 : : else
5119 : 8846 : colinfo->rightattnos[jcolno++] = rightattno;
5120 : 9230 : rcolno++;
5121 : : }
5122 [ - + ]: 940 : Assert(jcolno == numjoincols);
5057 5123 : 940 : }
5124 : :
5125 : : /*
5126 : : * get_rtable_name: convenience function to get a previously assigned RTE alias
5127 : : *
5128 : : * The RTE must belong to the topmost namespace level in "context".
5129 : : */
5130 : : static char *
5158 5131 : 4104 : get_rtable_name(int rtindex, deparse_context *context)
5132 : : {
5133 : 4104 : deparse_namespace *dpns = (deparse_namespace *) linitial(context->namespaces);
5134 : :
5135 [ + - - + ]: 4104 : Assert(rtindex > 0 && rtindex <= list_length(dpns->rtable_names));
5136 : 4104 : return (char *) list_nth(dpns->rtable_names, rtindex - 1);
5137 : : }
5138 : :
5139 : : /*
5140 : : * set_deparse_plan: set up deparse_namespace to parse subexpressions
5141 : : * of a given Plan node
5142 : : *
5143 : : * This sets the plan, outer_plan, inner_plan, outer_tlist, inner_tlist,
5144 : : * index_tlist, ret_old_alias, and ret_new_alias fields as appropriate.
5145 : : * Caller must already have adjusted the ancestors list if necessary.
5146 : : * Note that the rtable, subplans, and ctes fields do not need to change
5147 : : * when shifting attention to different plan nodes in a single plan tree.
5148 : : */
5149 : : static void
2521 5150 : 112776 : set_deparse_plan(deparse_namespace *dpns, Plan *plan)
5151 : : {
5152 : 112776 : dpns->plan = plan;
5153 : :
5154 : : /*
5155 : : * We special-case Append and MergeAppend to pretend that the first child
5156 : : * plan is the OUTER referent; we have to interpret OUTER Vars in their
5157 : : * tlists according to one of the children, and the first one is the most
5158 : : * natural choice.
5159 : : */
5160 [ + + ]: 112776 : if (IsA(plan, Append))
5161 : 3116 : dpns->outer_plan = linitial(((Append *) plan)->appendplans);
5162 [ + + ]: 109660 : else if (IsA(plan, MergeAppend))
5163 : 372 : dpns->outer_plan = linitial(((MergeAppend *) plan)->mergeplans);
5164 : : else
5165 : 109288 : dpns->outer_plan = outerPlan(plan);
5166 : :
5167 [ + + ]: 112776 : if (dpns->outer_plan)
5168 : 55213 : dpns->outer_tlist = dpns->outer_plan->targetlist;
5169 : : else
5504 5170 : 57563 : dpns->outer_tlist = NIL;
5171 : :
5172 : : /*
5173 : : * For a SubqueryScan, pretend the subplan is INNER referent. (We don't
5174 : : * use OUTER because that could someday conflict with the normal meaning.)
5175 : : * Likewise, for a CteScan, pretend the subquery's plan is INNER referent.
5176 : : * For a WorkTableScan, locate the parent RecursiveUnion plan node and use
5177 : : * that as INNER referent.
5178 : : *
5179 : : * For MERGE, pretend the ModifyTable's source plan (its outer plan) is
5180 : : * INNER referent. This is the join from the target relation to the data
5181 : : * source, and all INNER_VAR Vars in other parts of the query refer to its
5182 : : * targetlist.
5183 : : *
5184 : : * For ON CONFLICT DO SELECT/UPDATE we just need the inner tlist to point
5185 : : * to the excluded expression's tlist. (Similar to the SubqueryScan we
5186 : : * don't want to reuse OUTER, it's used for RETURNING in some modify table
5187 : : * cases, although not INSERT .. CONFLICT).
5188 : : */
2521 5189 [ + + ]: 112776 : if (IsA(plan, SubqueryScan))
5190 : 589 : dpns->inner_plan = ((SubqueryScan *) plan)->subplan;
5191 [ + + ]: 112187 : else if (IsA(plan, CteScan))
5192 : 413 : dpns->inner_plan = list_nth(dpns->subplans,
5193 : 413 : ((CteScan *) plan)->ctePlanId - 1);
1876 5194 [ + + ]: 111774 : else if (IsA(plan, WorkTableScan))
5195 : 116 : dpns->inner_plan = find_recursive_union(dpns,
5196 : : (WorkTableScan *) plan);
2521 5197 [ + + ]: 111658 : else if (IsA(plan, ModifyTable))
5198 : : {
1683 alvherre@alvh.no-ip. 5199 [ + + ]: 317 : if (((ModifyTable *) plan)->operation == CMD_MERGE)
963 dean.a.rasheed@gmail 5200 : 40 : dpns->inner_plan = outerPlan(plan);
5201 : : else
5202 : 277 : dpns->inner_plan = plan;
5203 : : }
5204 : : else
5205 : 111341 : dpns->inner_plan = innerPlan(plan);
5206 : :
5207 [ + + + + ]: 112776 : if (IsA(plan, ModifyTable) && ((ModifyTable *) plan)->operation == CMD_INSERT)
5208 : 141 : dpns->inner_tlist = ((ModifyTable *) plan)->exclRelTlist;
2521 tgl@sss.pgh.pa.us 5209 [ + + ]: 112635 : else if (dpns->inner_plan)
5210 : 20716 : dpns->inner_tlist = dpns->inner_plan->targetlist;
5211 : : else
5504 5212 : 91919 : dpns->inner_tlist = NIL;
5213 : :
5214 : : /* Set up referent for INDEX_VAR Vars, if needed */
2521 5215 [ + + ]: 112776 : if (IsA(plan, IndexOnlyScan))
5216 : 2536 : dpns->index_tlist = ((IndexOnlyScan *) plan)->indextlist;
5217 [ + + ]: 110240 : else if (IsA(plan, ForeignScan))
5218 : 1660 : dpns->index_tlist = ((ForeignScan *) plan)->fdw_scan_tlist;
5219 [ + + ]: 108580 : else if (IsA(plan, CustomScan))
2521 tgl@sss.pgh.pa.us 5220 :GBC 4 : dpns->index_tlist = ((CustomScan *) plan)->custom_scan_tlist;
5221 : : else
5504 tgl@sss.pgh.pa.us 5222 :CBC 108576 : dpns->index_tlist = NIL;
5223 : :
5224 : : /* Set up table aliases for OLD and NEW in RETURNING */
16 5225 [ + + ]: 112776 : if (IsA(plan, ModifyTable))
5226 : : {
5227 : 317 : dpns->ret_old_alias = ((ModifyTable *) plan)->returningOldAlias;
5228 : 317 : dpns->ret_new_alias = ((ModifyTable *) plan)->returningNewAlias;
5229 : : }
5230 : : else
5231 : 112459 : dpns->ret_old_alias = dpns->ret_new_alias = NULL;
5959 5232 : 112776 : }
5233 : :
5234 : : /*
5235 : : * Locate the ancestor plan node that is the RecursiveUnion generating
5236 : : * the WorkTableScan's work table. We can match on wtParam, since that
5237 : : * should be unique within the plan tree.
5238 : : */
5239 : : static Plan *
1876 5240 : 116 : find_recursive_union(deparse_namespace *dpns, WorkTableScan *wtscan)
5241 : : {
5242 : : ListCell *lc;
5243 : :
5244 [ + - + - : 292 : foreach(lc, dpns->ancestors)
+ - ]
5245 : : {
5246 : 292 : Plan *ancestor = (Plan *) lfirst(lc);
5247 : :
5248 [ + + ]: 292 : if (IsA(ancestor, RecursiveUnion) &&
5249 [ + - ]: 116 : ((RecursiveUnion *) ancestor)->wtParam == wtscan->wtParam)
5250 : 116 : return ancestor;
5251 : : }
1876 tgl@sss.pgh.pa.us 5252 [ # # ]:UBC 0 : elog(ERROR, "could not find RecursiveUnion for WorkTableScan with wtParam %d",
5253 : : wtscan->wtParam);
5254 : : return NULL;
5255 : : }
5256 : :
5257 : : /*
5258 : : * push_child_plan: temporarily transfer deparsing attention to a child plan
5259 : : *
5260 : : * When expanding an OUTER_VAR or INNER_VAR reference, we must adjust the
5261 : : * deparse context in case the referenced expression itself uses
5262 : : * OUTER_VAR/INNER_VAR. We modify the top stack entry in-place to avoid
5263 : : * affecting levelsup issues (although in a Plan tree there really shouldn't
5264 : : * be any).
5265 : : *
5266 : : * Caller must provide a local deparse_namespace variable to save the
5267 : : * previous state for pop_child_plan.
5268 : : */
5269 : : static void
2521 tgl@sss.pgh.pa.us 5270 :CBC 65971 : push_child_plan(deparse_namespace *dpns, Plan *plan,
5271 : : deparse_namespace *save_dpns)
5272 : : {
5273 : : /* Save state for restoration later */
5959 5274 : 65971 : *save_dpns = *dpns;
5275 : :
5276 : : /* Link current plan node into ancestors list */
2521 5277 : 65971 : dpns->ancestors = lcons(dpns->plan, dpns->ancestors);
5278 : :
5279 : : /* Set attention on selected child */
5280 : 65971 : set_deparse_plan(dpns, plan);
9395 5281 : 65971 : }
5282 : :
5283 : : /*
5284 : : * pop_child_plan: undo the effects of push_child_plan
5285 : : */
5286 : : static void
5959 5287 : 65971 : pop_child_plan(deparse_namespace *dpns, deparse_namespace *save_dpns)
5288 : : {
5289 : : List *ancestors;
5290 : :
5291 : : /* Get rid of ancestors list cell added by push_child_plan */
5180 5292 : 65971 : ancestors = list_delete_first(dpns->ancestors);
5293 : :
5294 : : /* Restore fields changed by push_child_plan */
5959 5295 : 65971 : *dpns = *save_dpns;
5296 : :
5297 : : /* Make sure dpns->ancestors is right (may be unnecessary) */
5180 5298 : 65971 : dpns->ancestors = ancestors;
5959 5299 : 65971 : }
5300 : :
5301 : : /*
5302 : : * push_ancestor_plan: temporarily transfer deparsing attention to an
5303 : : * ancestor plan
5304 : : *
5305 : : * When expanding a Param reference, we must adjust the deparse context
5306 : : * to match the plan node that contains the expression being printed;
5307 : : * otherwise we'd fail if that expression itself contains a Param or
5308 : : * OUTER_VAR/INNER_VAR/INDEX_VAR variable.
5309 : : *
5310 : : * The target ancestor is conveniently identified by the ListCell holding it
5311 : : * in dpns->ancestors.
5312 : : *
5313 : : * Caller must provide a local deparse_namespace variable to save the
5314 : : * previous state for pop_ancestor_plan.
5315 : : */
5316 : : static void
5317 : 3875 : push_ancestor_plan(deparse_namespace *dpns, ListCell *ancestor_cell,
5318 : : deparse_namespace *save_dpns)
5319 : : {
2521 5320 : 3875 : Plan *plan = (Plan *) lfirst(ancestor_cell);
5321 : :
5322 : : /* Save state for restoration later */
5959 5323 : 3875 : *save_dpns = *dpns;
5324 : :
5325 : : /* Build a new ancestor list with just this node's ancestors */
2670 5326 : 3875 : dpns->ancestors =
5327 : 3875 : list_copy_tail(dpns->ancestors,
5328 : 3875 : list_cell_number(dpns->ancestors, ancestor_cell) + 1);
5329 : :
5330 : : /* Set attention on selected ancestor */
2521 5331 : 3875 : set_deparse_plan(dpns, plan);
5959 5332 : 3875 : }
5333 : :
5334 : : /*
5335 : : * pop_ancestor_plan: undo the effects of push_ancestor_plan
5336 : : */
5337 : : static void
5338 : 3875 : pop_ancestor_plan(deparse_namespace *dpns, deparse_namespace *save_dpns)
5339 : : {
5340 : : /* Free the ancestor list made in push_ancestor_plan */
5341 : 3875 : list_free(dpns->ancestors);
5342 : :
5343 : : /* Restore fields changed by push_ancestor_plan */
5344 : 3875 : *dpns = *save_dpns;
5345 : 3875 : }
5346 : :
5347 : :
5348 : : /* ----------
5349 : : * make_ruledef - reconstruct the CREATE RULE command
5350 : : * for a given pg_rewrite tuple
5351 : : * ----------
5352 : : */
5353 : : static void
8499 5354 : 309 : make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
5355 : : int prettyFlags)
5356 : : {
5357 : : char *rulename;
5358 : : char ev_type;
5359 : : Oid ev_class;
5360 : : bool is_instead;
5361 : : char *ev_qual;
5362 : : char *ev_action;
5363 : : List *actions;
5364 : : Relation ev_relation;
3391 5365 : 309 : TupleDesc viewResultDesc = NULL;
5366 : : int fno;
5367 : : Datum dat;
5368 : : bool isnull;
5369 : :
5370 : : /*
5371 : : * Get the attribute values from the rules tuple
5372 : : */
8967 5373 : 309 : fno = SPI_fnumber(rulettc, "rulename");
5374 : 309 : dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
5375 [ - + ]: 309 : Assert(!isnull);
5376 : 309 : rulename = NameStr(*(DatumGetName(dat)));
5377 : :
10292 bruce@momjian.us 5378 : 309 : fno = SPI_fnumber(rulettc, "ev_type");
8967 tgl@sss.pgh.pa.us 5379 : 309 : dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
5380 [ - + ]: 309 : Assert(!isnull);
5381 : 309 : ev_type = DatumGetChar(dat);
5382 : :
10292 bruce@momjian.us 5383 : 309 : fno = SPI_fnumber(rulettc, "ev_class");
8967 tgl@sss.pgh.pa.us 5384 : 309 : dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
5385 [ - + ]: 309 : Assert(!isnull);
5386 : 309 : ev_class = DatumGetObjectId(dat);
5387 : :
10292 bruce@momjian.us 5388 : 309 : fno = SPI_fnumber(rulettc, "is_instead");
8967 tgl@sss.pgh.pa.us 5389 : 309 : dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
5390 [ - + ]: 309 : Assert(!isnull);
5391 : 309 : is_instead = DatumGetBool(dat);
5392 : :
10292 bruce@momjian.us 5393 : 309 : fno = SPI_fnumber(rulettc, "ev_qual");
5394 : 309 : ev_qual = SPI_getvalue(ruletup, rulettc, fno);
2194 tgl@sss.pgh.pa.us 5395 [ - + ]: 309 : Assert(ev_qual != NULL);
5396 : :
10292 bruce@momjian.us 5397 : 309 : fno = SPI_fnumber(rulettc, "ev_action");
5398 : 309 : ev_action = SPI_getvalue(ruletup, rulettc, fno);
2194 tgl@sss.pgh.pa.us 5399 [ - + ]: 309 : Assert(ev_action != NULL);
5400 : 309 : actions = (List *) stringToNode(ev_action);
5401 [ - + ]: 309 : if (actions == NIL)
2194 tgl@sss.pgh.pa.us 5402 [ # # ]:UBC 0 : elog(ERROR, "invalid empty ev_action list");
5403 : :
2845 andres@anarazel.de 5404 :CBC 309 : ev_relation = table_open(ev_class, AccessShareLock);
5405 : :
5406 : : /*
5407 : : * Build the rules definition text
5408 : : */
8499 tgl@sss.pgh.pa.us 5409 : 309 : appendStringInfo(buf, "CREATE RULE %s AS",
5410 : : quote_identifier(rulename));
5411 : :
5412 [ + - ]: 309 : if (prettyFlags & PRETTYFLAG_INDENT)
8494 bruce@momjian.us 5413 : 309 : appendStringInfoString(buf, "\n ON ");
5414 : : else
8494 bruce@momjian.us 5415 :UBC 0 : appendStringInfoString(buf, " ON ");
5416 : :
5417 : : /* The event the rule is fired for */
10292 bruce@momjian.us 5418 [ + + + + :CBC 309 : switch (ev_type)
- ]
5419 : : {
5420 : 4 : case '1':
4753 rhaas@postgresql.org 5421 : 4 : appendStringInfoString(buf, "SELECT");
3391 tgl@sss.pgh.pa.us 5422 : 4 : viewResultDesc = RelationGetDescr(ev_relation);
10300 bruce@momjian.us 5423 : 4 : break;
5424 : :
10292 5425 : 84 : case '2':
4753 rhaas@postgresql.org 5426 : 84 : appendStringInfoString(buf, "UPDATE");
10300 bruce@momjian.us 5427 : 84 : break;
5428 : :
10292 5429 : 165 : case '3':
4753 rhaas@postgresql.org 5430 : 165 : appendStringInfoString(buf, "INSERT");
10300 bruce@momjian.us 5431 : 165 : break;
5432 : :
10292 5433 : 56 : case '4':
4753 rhaas@postgresql.org 5434 : 56 : appendStringInfoString(buf, "DELETE");
10300 bruce@momjian.us 5435 : 56 : break;
5436 : :
10292 bruce@momjian.us 5437 :UBC 0 : default:
8502 tgl@sss.pgh.pa.us 5438 [ # # ]: 0 : ereport(ERROR,
5439 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
5440 : : errmsg("rule \"%s\" has unsupported event type %d",
5441 : : rulename, ev_type)));
5442 : : break;
5443 : : }
5444 : :
5445 : : /* The relation the rule is fired on */
3174 tgl@sss.pgh.pa.us 5446 :CBC 309 : appendStringInfo(buf, " TO %s",
5447 [ + + ]: 309 : (prettyFlags & PRETTYFLAG_SCHEMA) ?
5448 : 76 : generate_relation_name(ev_class, NIL) :
5449 : 233 : generate_qualified_relation_name(ev_class));
5450 : :
5451 : : /* If the rule has an event qualification, add it */
2194 5452 [ + + ]: 309 : if (strcmp(ev_qual, "<>") != 0)
5453 : : {
5454 : : Node *qual;
5455 : : Query *query;
5456 : : deparse_context context;
5457 : : deparse_namespace dpns;
5458 : :
8499 5459 [ + - ]: 62 : if (prettyFlags & PRETTYFLAG_INDENT)
8494 bruce@momjian.us 5460 : 62 : appendStringInfoString(buf, "\n ");
4753 rhaas@postgresql.org 5461 : 62 : appendStringInfoString(buf, " WHERE ");
5462 : :
10292 bruce@momjian.us 5463 : 62 : qual = stringToNode(ev_qual);
5464 : :
5465 : : /*
5466 : : * We need to make a context for recognizing any Vars in the qual
5467 : : * (which can only be references to OLD and NEW). Use the rtable of
5468 : : * the first query in the action list for this purpose.
5469 : : */
8198 neilc@samurai.com 5470 : 62 : query = (Query *) linitial(actions);
5471 : :
5472 : : /*
5473 : : * If the action is INSERT...SELECT, OLD/NEW have been pushed down
5474 : : * into the SELECT, and that's what we need to look at. (Ugly kluge
5475 : : * ... try to fix this when we redesign querytrees.)
5476 : : */
9110 tgl@sss.pgh.pa.us 5477 : 62 : query = getInsertSelectQuery(query, NULL);
5478 : :
5479 : : /* Must acquire locks right away; see notes in get_query_def() */
4627 5480 : 62 : AcquireRewriteLocks(query, false, false);
5481 : :
9895 5482 : 62 : context.buf = buf;
8194 neilc@samurai.com 5483 : 62 : context.namespaces = list_make1(&dpns);
798 tgl@sss.pgh.pa.us 5484 : 62 : context.resultDesc = NULL;
5485 : 62 : context.targetList = NIL;
6521 5486 : 62 : context.windowClause = NIL;
8194 neilc@samurai.com 5487 : 62 : context.varprefix = (list_length(query->rtable) != 1);
8499 tgl@sss.pgh.pa.us 5488 : 62 : context.prettyFlags = prettyFlags;
5064 5489 : 62 : context.wrapColumn = WRAP_COLUMN_DEFAULT;
8499 5490 : 62 : context.indentLevel = PRETTYINDENT_STD;
798 5491 : 62 : context.colNamesVisible = true;
5492 : 62 : context.inGroupBy = false;
5493 : 62 : context.varInOrderBy = false;
2521 5494 : 62 : context.appendparents = NULL;
5495 : :
5057 5496 : 62 : set_deparse_for_query(&dpns, query, NIL);
5497 : :
8813 5498 : 62 : get_rule_expr(qual, &context, false);
5499 : : }
5500 : :
4753 rhaas@postgresql.org 5501 : 309 : appendStringInfoString(buf, " DO ");
5502 : :
5503 : : /* The INSTEAD keyword (if so) */
10292 bruce@momjian.us 5504 [ + + ]: 309 : if (is_instead)
4753 rhaas@postgresql.org 5505 : 183 : appendStringInfoString(buf, "INSTEAD ");
5506 : :
5507 : : /* Finally the rules actions */
8194 neilc@samurai.com 5508 [ + + ]: 309 : if (list_length(actions) > 1)
5509 : : {
5510 : : ListCell *action;
5511 : : Query *query;
5512 : :
4753 rhaas@postgresql.org 5513 : 10 : appendStringInfoChar(buf, '(');
10292 bruce@momjian.us 5514 [ + - + + : 30 : foreach(action, actions)
+ + ]
5515 : : {
5516 : 20 : query = (Query *) lfirst(action);
1629 tgl@sss.pgh.pa.us 5517 : 20 : get_query_def(query, buf, NIL, viewResultDesc, true,
5518 : : prettyFlags, WRAP_COLUMN_DEFAULT, 0);
8499 5519 [ + - ]: 20 : if (prettyFlags)
4753 rhaas@postgresql.org 5520 : 20 : appendStringInfoString(buf, ";\n");
5521 : : else
4753 rhaas@postgresql.org 5522 :UBC 0 : appendStringInfoString(buf, "; ");
5523 : : }
4753 rhaas@postgresql.org 5524 :CBC 10 : appendStringInfoString(buf, ");");
5525 : : }
5526 : : else
5527 : : {
5528 : : Query *query;
5529 : :
8198 neilc@samurai.com 5530 : 299 : query = (Query *) linitial(actions);
1629 tgl@sss.pgh.pa.us 5531 : 299 : get_query_def(query, buf, NIL, viewResultDesc, true,
5532 : : prettyFlags, WRAP_COLUMN_DEFAULT, 0);
4753 rhaas@postgresql.org 5533 : 299 : appendStringInfoChar(buf, ';');
5534 : : }
5535 : :
2845 andres@anarazel.de 5536 : 309 : table_close(ev_relation, AccessShareLock);
10300 bruce@momjian.us 5537 : 309 : }
5538 : :
5539 : :
5540 : : /* ----------
5541 : : * make_viewdef - reconstruct the SELECT part of a
5542 : : * view rewrite rule
5543 : : * ----------
5544 : : */
5545 : : static void
8499 tgl@sss.pgh.pa.us 5546 : 2149 : make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
5547 : : int prettyFlags, int wrapColumn)
5548 : : {
5549 : : Query *query;
5550 : : char ev_type;
5551 : : Oid ev_class;
5552 : : bool is_instead;
5553 : : char *ev_qual;
5554 : : char *ev_action;
5555 : : List *actions;
5556 : : Relation ev_relation;
5557 : : int fno;
5558 : : Datum dat;
5559 : : bool isnull;
5560 : :
5561 : : /*
5562 : : * Get the attribute values from the rules tuple
5563 : : */
10292 bruce@momjian.us 5564 : 2149 : fno = SPI_fnumber(rulettc, "ev_type");
3391 tgl@sss.pgh.pa.us 5565 : 2149 : dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
5566 [ - + ]: 2149 : Assert(!isnull);
5567 : 2149 : ev_type = DatumGetChar(dat);
5568 : :
10292 bruce@momjian.us 5569 : 2149 : fno = SPI_fnumber(rulettc, "ev_class");
3391 tgl@sss.pgh.pa.us 5570 : 2149 : dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
5571 [ - + ]: 2149 : Assert(!isnull);
5572 : 2149 : ev_class = DatumGetObjectId(dat);
5573 : :
10292 bruce@momjian.us 5574 : 2149 : fno = SPI_fnumber(rulettc, "is_instead");
3391 tgl@sss.pgh.pa.us 5575 : 2149 : dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
5576 [ - + ]: 2149 : Assert(!isnull);
5577 : 2149 : is_instead = DatumGetBool(dat);
5578 : :
10292 bruce@momjian.us 5579 : 2149 : fno = SPI_fnumber(rulettc, "ev_qual");
5580 : 2149 : ev_qual = SPI_getvalue(ruletup, rulettc, fno);
2194 tgl@sss.pgh.pa.us 5581 [ - + ]: 2149 : Assert(ev_qual != NULL);
5582 : :
10292 bruce@momjian.us 5583 : 2149 : fno = SPI_fnumber(rulettc, "ev_action");
5584 : 2149 : ev_action = SPI_getvalue(ruletup, rulettc, fno);
2194 tgl@sss.pgh.pa.us 5585 [ - + ]: 2149 : Assert(ev_action != NULL);
5586 : 2149 : actions = (List *) stringToNode(ev_action);
5587 : :
8194 neilc@samurai.com 5588 [ - + ]: 2149 : if (list_length(actions) != 1)
5589 : : {
5590 : : /* keep output buffer empty and leave */
9896 tgl@sss.pgh.pa.us 5591 :UBC 0 : return;
5592 : : }
5593 : :
8198 neilc@samurai.com 5594 :CBC 2149 : query = (Query *) linitial(actions);
5595 : :
4809 kgrittn@postgresql.o 5596 [ + - + - ]: 2149 : if (ev_type != '1' || !is_instead ||
8855 tgl@sss.pgh.pa.us 5597 [ + - - + ]: 2149 : strcmp(ev_qual, "<>") != 0 || query->commandType != CMD_SELECT)
5598 : : {
5599 : : /* keep output buffer empty and leave */
9896 tgl@sss.pgh.pa.us 5600 :UBC 0 : return;
5601 : : }
5602 : :
2845 andres@anarazel.de 5603 :CBC 2149 : ev_relation = table_open(ev_class, AccessShareLock);
5604 : :
1629 tgl@sss.pgh.pa.us 5605 : 2149 : get_query_def(query, buf, NIL, RelationGetDescr(ev_relation), true,
5606 : : prettyFlags, wrapColumn, 0);
4753 rhaas@postgresql.org 5607 : 2149 : appendStringInfoChar(buf, ';');
5608 : :
2845 andres@anarazel.de 5609 : 2149 : table_close(ev_relation, AccessShareLock);
5610 : : }
5611 : :
5612 : :
5613 : : /* ----------
5614 : : * get_query_def - Parse back one query parsetree
5615 : : *
5616 : : * query: parsetree to be displayed
5617 : : * buf: output text is appended to buf
5618 : : * parentnamespace: list (initially empty) of outer-level deparse_namespace's
5619 : : * resultDesc: if not NULL, the output tuple descriptor for the view
5620 : : * represented by a SELECT query. We use the column names from it
5621 : : * to label SELECT output columns, in preference to names in the query
5622 : : * colNamesVisible: true if the surrounding context cares about the output
5623 : : * column names at all (as, for example, an EXISTS() context does not);
5624 : : * when false, we can suppress dummy column labels such as "?column?"
5625 : : * prettyFlags: bitmask of PRETTYFLAG_XXX options
5626 : : * wrapColumn: maximum line length, or -1 to disable wrapping
5627 : : * startIndent: initial indentation amount
5628 : : * ----------
5629 : : */
5630 : : static void
8855 tgl@sss.pgh.pa.us 5631 : 3528 : get_query_def(Query *query, StringInfo buf, List *parentnamespace,
5632 : : TupleDesc resultDesc, bool colNamesVisible,
5633 : : int prettyFlags, int wrapColumn, int startIndent)
5634 : : {
5635 : : deparse_context context;
5636 : : deparse_namespace dpns;
5637 : : int rtable_size;
5638 : :
5639 : : /* Guard against excessively long or deeply-nested queries */
4572 5640 [ - + ]: 3528 : CHECK_FOR_INTERRUPTS();
5641 : 3528 : check_stack_depth();
5642 : :
786 rguo@postgresql.org 5643 : 7056 : rtable_size = query->hasGroupRTE ?
5644 [ + + ]: 3528 : list_length(query->rtable) - 1 :
5645 : 3409 : list_length(query->rtable);
5646 : :
5647 : : /*
5648 : : * Replace any Vars in the query's targetlist and havingQual that
5649 : : * reference GROUP outputs with the underlying grouping expressions.
5650 : : *
5651 : : * We can safely pass NULL for the root here. Preserving varnullingrels
5652 : : * makes no difference to the deparsed source text.
5653 : : */
5654 [ + + ]: 3528 : if (query->hasGroupRTE)
5655 : : {
5656 : 119 : query->targetList = (List *)
5657 : 119 : flatten_group_exprs(NULL, query, (Node *) query->targetList);
5658 : 119 : query->havingQual =
5659 : 119 : flatten_group_exprs(NULL, query, query->havingQual);
5660 : : }
5661 : :
5662 : : /*
5663 : : * Before we begin to examine the query, acquire locks on referenced
5664 : : * relations, and fix up deleted columns in JOIN RTEs. This ensures
5665 : : * consistent results. Note we assume it's OK to scribble on the passed
5666 : : * querytree!
5667 : : *
5668 : : * We are only deparsing the query (we are not about to execute it), so we
5669 : : * only need AccessShareLock on the relations it mentions.
5670 : : */
4627 tgl@sss.pgh.pa.us 5671 : 3528 : AcquireRewriteLocks(query, false, false);
5672 : :
9895 5673 : 3528 : context.buf = buf;
8198 neilc@samurai.com 5674 : 3528 : context.namespaces = lcons(&dpns, list_copy(parentnamespace));
798 tgl@sss.pgh.pa.us 5675 : 3528 : context.resultDesc = NULL;
5676 : 3528 : context.targetList = NIL;
6521 5677 : 3528 : context.windowClause = NIL;
9395 5678 [ + + + + ]: 3528 : context.varprefix = (parentnamespace != NIL ||
786 rguo@postgresql.org 5679 : 3528 : rtable_size != 1);
8499 tgl@sss.pgh.pa.us 5680 : 3528 : context.prettyFlags = prettyFlags;
5064 5681 : 3528 : context.wrapColumn = wrapColumn;
8499 5682 : 3528 : context.indentLevel = startIndent;
798 5683 : 3528 : context.colNamesVisible = colNamesVisible;
5684 : 3528 : context.inGroupBy = false;
5685 : 3528 : context.varInOrderBy = false;
2521 5686 : 3528 : context.appendparents = NULL;
5687 : :
5057 5688 : 3528 : set_deparse_for_query(&dpns, query, parentnamespace);
5689 : :
10292 bruce@momjian.us 5690 [ + + + + : 3528 : switch (query->commandType)
+ + + - ]
5691 : : {
10026 5692 : 3170 : case CMD_SELECT:
5693 : : /* We set context.resultDesc only if it's a SELECT */
798 tgl@sss.pgh.pa.us 5694 : 3170 : context.resultDesc = resultDesc;
5695 : 3170 : get_select_query_def(query, &context);
10292 bruce@momjian.us 5696 : 3170 : break;
5697 : :
5698 : 86 : case CMD_UPDATE:
798 tgl@sss.pgh.pa.us 5699 : 86 : get_update_query_def(query, &context);
10292 bruce@momjian.us 5700 : 86 : break;
5701 : :
5702 : 194 : case CMD_INSERT:
798 tgl@sss.pgh.pa.us 5703 : 194 : get_insert_query_def(query, &context);
10292 bruce@momjian.us 5704 : 194 : break;
5705 : :
5706 : 39 : case CMD_DELETE:
798 tgl@sss.pgh.pa.us 5707 : 39 : get_delete_query_def(query, &context);
10292 bruce@momjian.us 5708 : 39 : break;
5709 : :
1278 tgl@sss.pgh.pa.us 5710 : 8 : case CMD_MERGE:
798 5711 : 8 : get_merge_query_def(query, &context);
1278 5712 : 8 : break;
5713 : :
10292 bruce@momjian.us 5714 : 22 : case CMD_NOTHING:
4753 rhaas@postgresql.org 5715 : 22 : appendStringInfoString(buf, "NOTHING");
10292 bruce@momjian.us 5716 : 22 : break;
5717 : :
9437 tgl@sss.pgh.pa.us 5718 : 9 : case CMD_UTILITY:
5719 : 9 : get_utility_query_def(query, &context);
5720 : 9 : break;
5721 : :
10292 bruce@momjian.us 5722 :UBC 0 : default:
8502 tgl@sss.pgh.pa.us 5723 [ # # ]: 0 : elog(ERROR, "unrecognized query command type: %d",
5724 : : query->commandType);
5725 : : break;
5726 : : }
10300 bruce@momjian.us 5727 :CBC 3528 : }
5728 : :
5729 : : /* ----------
5730 : : * get_values_def - Parse back a VALUES list
5731 : : * ----------
5732 : : */
5733 : : static void
7400 mail@joeconway.com 5734 : 197 : get_values_def(List *values_lists, deparse_context *context)
5735 : : {
5736 : 197 : StringInfo buf = context->buf;
5737 : 197 : bool first_list = true;
5738 : : ListCell *vtl;
5739 : :
5740 : 197 : appendStringInfoString(buf, "VALUES ");
5741 : :
5742 [ + - + + : 589 : foreach(vtl, values_lists)
+ + ]
5743 : : {
5744 : 392 : List *sublist = (List *) lfirst(vtl);
5745 : 392 : bool first_col = true;
5746 : : ListCell *lc;
5747 : :
5748 [ + + ]: 392 : if (first_list)
5749 : 197 : first_list = false;
5750 : : else
5751 : 195 : appendStringInfoString(buf, ", ");
5752 : :
5753 : 392 : appendStringInfoChar(buf, '(');
5754 [ + - + + : 1392 : foreach(lc, sublist)
+ + ]
5755 : : {
7337 bruce@momjian.us 5756 : 1000 : Node *col = (Node *) lfirst(lc);
5757 : :
7400 mail@joeconway.com 5758 [ + + ]: 1000 : if (first_col)
5759 : 392 : first_col = false;
5760 : : else
5761 : 608 : appendStringInfoChar(buf, ',');
5762 : :
5763 : : /*
5764 : : * Print the value. Whole-row Vars need special treatment.
5765 : : */
3746 tgl@sss.pgh.pa.us 5766 : 1000 : get_rule_expr_toplevel(col, context, false);
5767 : : }
7400 mail@joeconway.com 5768 : 392 : appendStringInfoChar(buf, ')');
5769 : : }
5770 : 197 : }
5771 : :
5772 : : /* ----------
5773 : : * get_with_clause - Parse back a WITH clause
5774 : : * ----------
5775 : : */
5776 : : static void
6606 tgl@sss.pgh.pa.us 5777 : 3497 : get_with_clause(Query *query, deparse_context *context)
5778 : : {
5779 : 3497 : StringInfo buf = context->buf;
5780 : : const char *sep;
5781 : : ListCell *l;
5782 : :
5783 [ + + ]: 3497 : if (query->cteList == NIL)
5784 : 3434 : return;
5785 : :
5786 [ + - ]: 63 : if (PRETTY_INDENT(context))
5787 : : {
5788 : 63 : context->indentLevel += PRETTYINDENT_STD;
5789 : 63 : appendStringInfoChar(buf, ' ');
5790 : : }
5791 : :
5792 [ + + ]: 63 : if (query->hasRecursive)
5793 : 34 : sep = "WITH RECURSIVE ";
5794 : : else
5795 : 29 : sep = "WITH ";
5796 [ + - + + : 156 : foreach(l, query->cteList)
+ + ]
5797 : : {
5798 : 93 : CommonTableExpr *cte = (CommonTableExpr *) lfirst(l);
5799 : :
5800 : 93 : appendStringInfoString(buf, sep);
5801 : 93 : appendStringInfoString(buf, quote_identifier(cte->ctename));
5802 [ + + ]: 93 : if (cte->aliascolnames)
5803 : : {
5804 : 38 : bool first = true;
5805 : : ListCell *col;
5806 : :
5807 : 38 : appendStringInfoChar(buf, '(');
5808 [ + - + + : 100 : foreach(col, cte->aliascolnames)
+ + ]
5809 : : {
5810 [ + + ]: 62 : if (first)
5811 : 38 : first = false;
5812 : : else
5813 : 24 : appendStringInfoString(buf, ", ");
5814 : 62 : appendStringInfoString(buf,
5815 : 62 : quote_identifier(strVal(lfirst(col))));
5816 : : }
5817 : 38 : appendStringInfoChar(buf, ')');
5818 : : }
2819 5819 : 93 : appendStringInfoString(buf, " AS ");
5820 [ + + - - ]: 93 : switch (cte->ctematerialized)
5821 : : {
5822 : 81 : case CTEMaterializeDefault:
5823 : 81 : break;
5824 : 12 : case CTEMaterializeAlways:
5825 : 12 : appendStringInfoString(buf, "MATERIALIZED ");
5826 : 12 : break;
2819 tgl@sss.pgh.pa.us 5827 :UBC 0 : case CTEMaterializeNever:
5828 : 0 : appendStringInfoString(buf, "NOT MATERIALIZED ");
5829 : 0 : break;
5830 : : }
2819 tgl@sss.pgh.pa.us 5831 :CBC 93 : appendStringInfoChar(buf, '(');
6606 5832 [ + - ]: 93 : if (PRETTY_INDENT(context))
5833 : 93 : appendContextKeyword(context, "", 0, 0, 0);
5834 : 93 : get_query_def((Query *) cte->ctequery, buf, context->namespaces, NULL,
5835 : : true,
5836 : : context->prettyFlags, context->wrapColumn,
5837 : : context->indentLevel);
5838 [ + - ]: 93 : if (PRETTY_INDENT(context))
5839 : 93 : appendContextKeyword(context, "", 0, 0, 0);
5840 : 93 : appendStringInfoChar(buf, ')');
5841 : :
2103 peter@eisentraut.org 5842 [ + + ]: 93 : if (cte->search_clause)
5843 : : {
5844 : 4 : bool first = true;
5845 : : ListCell *lc;
5846 : :
5847 : 4 : appendStringInfo(buf, " SEARCH %s FIRST BY ",
5848 [ - + ]: 4 : cte->search_clause->search_breadth_first ? "BREADTH" : "DEPTH");
5849 : :
5850 [ + - + + : 12 : foreach(lc, cte->search_clause->search_col_list)
+ + ]
5851 : : {
5852 [ + + ]: 8 : if (first)
5853 : 4 : first = false;
5854 : : else
5855 : 4 : appendStringInfoString(buf, ", ");
5856 : 8 : appendStringInfoString(buf,
5857 : 8 : quote_identifier(strVal(lfirst(lc))));
5858 : : }
5859 : :
5860 : 4 : appendStringInfo(buf, " SET %s", quote_identifier(cte->search_clause->search_seq_column));
5861 : : }
5862 : :
5863 [ + + ]: 93 : if (cte->cycle_clause)
5864 : : {
5865 : 8 : bool first = true;
5866 : : ListCell *lc;
5867 : :
5868 : 8 : appendStringInfoString(buf, " CYCLE ");
5869 : :
5870 [ + - + + : 24 : foreach(lc, cte->cycle_clause->cycle_col_list)
+ + ]
5871 : : {
5872 [ + + ]: 16 : if (first)
5873 : 8 : first = false;
5874 : : else
5875 : 8 : appendStringInfoString(buf, ", ");
5876 : 16 : appendStringInfoString(buf,
5877 : 16 : quote_identifier(strVal(lfirst(lc))));
5878 : : }
5879 : :
5880 : 8 : appendStringInfo(buf, " SET %s", quote_identifier(cte->cycle_clause->cycle_mark_column));
5881 : :
5882 : : {
2077 5883 : 8 : Const *cmv = castNode(Const, cte->cycle_clause->cycle_mark_value);
5884 : 8 : Const *cmd = castNode(Const, cte->cycle_clause->cycle_mark_default);
5885 : :
5886 [ + + + - : 12 : if (!(cmv->consttype == BOOLOID && !cmv->constisnull && DatumGetBool(cmv->constvalue) == true &&
+ - - + ]
5887 [ + - + - ]: 4 : cmd->consttype == BOOLOID && !cmd->constisnull && DatumGetBool(cmd->constvalue) == false))
5888 : : {
5889 : 4 : appendStringInfoString(buf, " TO ");
5890 : 4 : get_rule_expr(cte->cycle_clause->cycle_mark_value, context, false);
5891 : 4 : appendStringInfoString(buf, " DEFAULT ");
5892 : 4 : get_rule_expr(cte->cycle_clause->cycle_mark_default, context, false);
5893 : : }
5894 : : }
5895 : :
2103 5896 : 8 : appendStringInfo(buf, " USING %s", quote_identifier(cte->cycle_clause->cycle_path_column));
5897 : : }
5898 : :
6606 tgl@sss.pgh.pa.us 5899 : 93 : sep = ", ";
5900 : : }
5901 : :
5902 [ + - ]: 63 : if (PRETTY_INDENT(context))
5903 : : {
5904 : 63 : context->indentLevel -= PRETTYINDENT_STD;
5905 : 63 : appendContextKeyword(context, "", 0, 0, 0);
5906 : : }
5907 : : else
6606 tgl@sss.pgh.pa.us 5908 :UBC 0 : appendStringInfoChar(buf, ' ');
5909 : : }
5910 : :
5911 : : /* ----------
5912 : : * get_select_query_def - Parse back a SELECT parsetree
5913 : : * ----------
5914 : : */
5915 : : static void
798 tgl@sss.pgh.pa.us 5916 :CBC 3170 : get_select_query_def(Query *query, deparse_context *context)
5917 : : {
9527 5918 : 3170 : StringInfo buf = context->buf;
5919 : : bool force_colno;
5920 : : ListCell *l;
5921 : :
5922 : : /* Insert the WITH clause if given */
6606 5923 : 3170 : get_with_clause(query, context);
5924 : :
5925 : : /* Subroutines may need to consult the SELECT targetlist and windowClause */
798 5926 : 3170 : context->targetList = query->targetList;
6521 5927 : 3170 : context->windowClause = query->windowClause;
5928 : :
5929 : : /*
5930 : : * If the Query node has a setOperations tree, then it's the top level of
5931 : : * a UNION/INTERSECT/EXCEPT query; only the WITH, ORDER BY and LIMIT
5932 : : * fields are interesting in the top query itself.
5933 : : */
9527 5934 [ + + ]: 3170 : if (query->setOperations)
5935 : : {
798 5936 : 103 : get_setop_query(query->setOperations, query, context);
5937 : : /* ORDER BY clauses must be simple in this case */
9335 5938 : 103 : force_colno = true;
5939 : : }
5940 : : else
5941 : : {
798 5942 : 3067 : get_basic_select_query(query, context);
9335 5943 : 3067 : force_colno = false;
5944 : : }
5945 : :
5946 : : /* Add the ORDER BY clause if given */
9527 5947 [ + + ]: 3170 : if (query->sortClause != NIL)
5948 : : {
8494 bruce@momjian.us 5949 : 119 : appendContextKeyword(context, " ORDER BY ",
5950 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
6521 tgl@sss.pgh.pa.us 5951 : 119 : get_rule_orderby(query->sortClause, query->targetList,
5952 : : force_colno, context);
5953 : : }
5954 : :
5955 : : /*
5956 : : * Add the LIMIT/OFFSET clauses if given. If non-default options, use the
5957 : : * standard spelling of LIMIT.
5958 : : */
9506 5959 [ + + ]: 3170 : if (query->limitOffset != NULL)
5960 : : {
8499 5961 : 18 : appendContextKeyword(context, " OFFSET ",
5962 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
8813 5963 : 18 : get_rule_expr(query->limitOffset, context, false);
5964 : : }
9506 5965 [ + + ]: 3170 : if (query->limitCount != NULL)
5966 : : {
2403 alvherre@alvh.no-ip. 5967 [ + + ]: 53 : if (query->limitOption == LIMIT_OPTION_WITH_TIES)
5968 : : {
5969 : : /*
5970 : : * The limitCount arg is a c_expr, so it needs parens. Simple
5971 : : * literals and function expressions would not need parens, but
5972 : : * unfortunately it's hard to tell if the expression will be
5973 : : * printed as a simple literal like 123 or as a typecast
5974 : : * expression, like '-123'::int4. The grammar accepts the former
5975 : : * without quoting, but not the latter.
5976 : : */
5977 : 27 : appendContextKeyword(context, " FETCH FIRST ",
5978 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
535 heikki.linnakangas@i 5979 : 27 : appendStringInfoChar(buf, '(');
8813 tgl@sss.pgh.pa.us 5980 : 27 : get_rule_expr(query->limitCount, context, false);
535 heikki.linnakangas@i 5981 : 27 : appendStringInfoChar(buf, ')');
2212 drowley@postgresql.o 5982 : 27 : appendStringInfoString(buf, " ROWS WITH TIES");
5983 : : }
5984 : : else
5985 : : {
2403 alvherre@alvh.no-ip. 5986 : 26 : appendContextKeyword(context, " LIMIT ",
5987 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
5988 [ + + ]: 26 : if (IsA(query->limitCount, Const) &&
5989 [ + - ]: 9 : ((Const *) query->limitCount)->constisnull)
5990 : 9 : appendStringInfoString(buf, "ALL");
5991 : : else
5992 : 17 : get_rule_expr(query->limitCount, context, false);
5993 : : }
5994 : : }
5995 : :
5996 : : /* Add FOR [KEY] UPDATE/SHARE clauses if present */
6217 tgl@sss.pgh.pa.us 5997 [ + + ]: 3170 : if (query->hasForUpdate)
5998 : : {
5999 [ + - + + : 8 : foreach(l, query->rowMarks)
+ + ]
6000 : : {
6001 : 4 : RowMarkClause *rc = (RowMarkClause *) lfirst(l);
6002 : :
6003 : : /* don't print implicit clauses */
6004 [ - + ]: 4 : if (rc->pushedDown)
6217 tgl@sss.pgh.pa.us 6005 :UBC 0 : continue;
6006 : :
266 dean.a.rasheed@gmail 6007 :CBC 4 : appendContextKeyword(context,
6008 : 4 : get_lock_clause_strength(rc->strength),
6009 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
6010 : :
6217 tgl@sss.pgh.pa.us 6011 : 4 : appendStringInfo(buf, " OF %s",
5158 6012 : 4 : quote_identifier(get_rtable_name(rc->rti,
6013 : : context)));
4412 alvherre@alvh.no-ip. 6014 [ - + ]: 4 : if (rc->waitPolicy == LockWaitError)
4753 rhaas@postgresql.org 6015 :UBC 0 : appendStringInfoString(buf, " NOWAIT");
4412 alvherre@alvh.no-ip. 6016 [ - + ]:CBC 4 : else if (rc->waitPolicy == LockWaitSkip)
4412 alvherre@alvh.no-ip. 6017 :UBC 0 : appendStringInfoString(buf, " SKIP LOCKED");
6018 : : }
6019 : : }
9527 tgl@sss.pgh.pa.us 6020 :CBC 3170 : }
6021 : :
6022 : : static char *
266 dean.a.rasheed@gmail 6023 : 8 : get_lock_clause_strength(LockClauseStrength strength)
6024 : : {
6025 [ - - - - : 8 : switch (strength)
+ - ]
6026 : : {
266 dean.a.rasheed@gmail 6027 :UBC 0 : case LCS_NONE:
6028 : : /* we intentionally throw an error for LCS_NONE */
6029 [ # # ]: 0 : elog(ERROR, "unrecognized LockClauseStrength %d",
6030 : : (int) strength);
6031 : : break;
6032 : 0 : case LCS_FORKEYSHARE:
6033 : 0 : return " FOR KEY SHARE";
6034 : 0 : case LCS_FORSHARE:
6035 : 0 : return " FOR SHARE";
6036 : 0 : case LCS_FORNOKEYUPDATE:
6037 : 0 : return " FOR NO KEY UPDATE";
266 dean.a.rasheed@gmail 6038 :CBC 8 : case LCS_FORUPDATE:
6039 : 8 : return " FOR UPDATE";
6040 : : }
266 dean.a.rasheed@gmail 6041 :UBC 0 : return NULL; /* keep compiler quiet */
6042 : : }
6043 : :
6044 : : /*
6045 : : * Detect whether query looks like SELECT ... FROM VALUES(),
6046 : : * with no need to rename the output columns of the VALUES RTE.
6047 : : * If so, return the VALUES RTE. Otherwise return NULL.
6048 : : */
6049 : : static RangeTblEntry *
2546 tgl@sss.pgh.pa.us 6050 :CBC 3067 : get_simple_values_rte(Query *query, TupleDesc resultDesc)
6051 : : {
5191 6052 : 3067 : RangeTblEntry *result = NULL;
6053 : : ListCell *lc;
6054 : :
6055 : : /*
6056 : : * We want to detect a match even if the Query also contains OLD or NEW
6057 : : * rule RTEs. So the idea is to scan the rtable and see if there is only
6058 : : * one inFromCl RTE that is a VALUES RTE.
6059 : : */
6060 [ + + + + : 3322 : foreach(lc, query->rtable)
+ + ]
6061 : : {
6062 : 2772 : RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
6063 : :
6064 [ + + + - ]: 2772 : if (rte->rtekind == RTE_VALUES && rte->inFromCl)
6065 : : {
6066 [ - + ]: 171 : if (result)
6067 : 2517 : return NULL; /* multiple VALUES (probably not possible) */
6068 : 171 : result = rte;
6069 : : }
6070 [ + + + + ]: 2601 : else if (rte->rtekind == RTE_RELATION && !rte->inFromCl)
6071 : 84 : continue; /* ignore rule entries */
6072 : : else
6073 : 2517 : return NULL; /* something else -> not simple VALUES */
6074 : : }
6075 : :
6076 : : /*
6077 : : * We don't need to check the targetlist in any great detail, because
6078 : : * parser/analyze.c will never generate a "bare" VALUES RTE --- they only
6079 : : * appear inside auto-generated sub-queries with very restricted
6080 : : * structure. However, DefineView might have modified the tlist by
6081 : : * injecting new column aliases, or we might have some other column
6082 : : * aliases forced by a resultDesc. We can only simplify if the RTE's
6083 : : * column names match the names that get_target_list() would select.
6084 : : */
4271 6085 [ + + ]: 550 : if (result)
6086 : : {
6087 : : ListCell *lcn;
6088 : : int colno;
6089 : :
6090 [ - + ]: 171 : if (list_length(query->targetList) != list_length(result->eref->colnames))
4271 tgl@sss.pgh.pa.us 6091 :UBC 0 : return NULL; /* this probably cannot happen */
2546 tgl@sss.pgh.pa.us 6092 :CBC 171 : colno = 0;
4271 6093 [ + - + + : 600 : forboth(lc, query->targetList, lcn, result->eref->colnames)
+ - + + +
+ + - +
+ ]
6094 : : {
6095 : 437 : TargetEntry *tle = (TargetEntry *) lfirst(lc);
6096 : 437 : char *cname = strVal(lfirst(lcn));
6097 : : char *colname;
6098 : :
6099 [ - + ]: 437 : if (tle->resjunk)
6100 : 8 : return NULL; /* this probably cannot happen */
6101 : :
6102 : : /* compute name that get_target_list would use for column */
2546 6103 : 437 : colno++;
6104 [ + + + - ]: 437 : if (resultDesc && colno <= resultDesc->natts)
6105 : 20 : colname = NameStr(TupleDescAttr(resultDesc, colno - 1)->attname);
6106 : : else
6107 : 417 : colname = tle->resname;
6108 : :
6109 : : /* does it match the VALUES RTE? */
6110 [ + - + + ]: 437 : if (colname == NULL || strcmp(colname, cname) != 0)
4271 6111 : 8 : return NULL; /* column name has been changed */
6112 : : }
6113 : : }
6114 : :
5191 6115 : 542 : return result;
6116 : : }
6117 : :
6118 : : static void
798 6119 : 3067 : get_basic_select_query(Query *query, deparse_context *context)
6120 : : {
9895 6121 : 3067 : StringInfo buf = context->buf;
6122 : : RangeTblEntry *values_rte;
6123 : : char *sep;
6124 : : ListCell *l;
6125 : :
8499 6126 [ + + ]: 3067 : if (PRETTY_INDENT(context))
6127 : : {
8494 bruce@momjian.us 6128 : 3040 : context->indentLevel += PRETTYINDENT_STD;
6129 : 3040 : appendStringInfoChar(buf, ' ');
6130 : : }
6131 : :
6132 : : /*
6133 : : * If the query looks like SELECT * FROM (VALUES ...), then print just the
6134 : : * VALUES part. This reverses what transformValuesClause() did at parse
6135 : : * time.
6136 : : */
798 tgl@sss.pgh.pa.us 6137 : 3067 : values_rte = get_simple_values_rte(query, context->resultDesc);
5191 6138 [ + + ]: 3067 : if (values_rte)
6139 : : {
6140 : 163 : get_values_def(values_rte->values_lists, context);
6141 : 163 : return;
6142 : : }
6143 : :
6144 : : /*
6145 : : * Build up the query string - first we say SELECT
6146 : : */
2038 peter@eisentraut.org 6147 [ + + ]: 2904 : if (query->isReturn)
6148 : 31 : appendStringInfoString(buf, "RETURN");
6149 : : else
6150 : 2873 : appendStringInfoString(buf, "SELECT");
6151 : :
6152 : : /* Add the DISTINCT clause if given */
9527 tgl@sss.pgh.pa.us 6153 [ - + ]: 2904 : if (query->distinctClause != NIL)
6154 : : {
6669 tgl@sss.pgh.pa.us 6155 [ # # ]:UBC 0 : if (query->hasDistinctOn)
6156 : : {
4753 rhaas@postgresql.org 6157 : 0 : appendStringInfoString(buf, " DISTINCT ON (");
9527 tgl@sss.pgh.pa.us 6158 : 0 : sep = "";
6159 [ # # # # : 0 : foreach(l, query->distinctClause)
# # ]
6160 : : {
6669 6161 : 0 : SortGroupClause *srt = (SortGroupClause *) lfirst(l);
6162 : :
7859 neilc@samurai.com 6163 : 0 : appendStringInfoString(buf, sep);
4191 andres@anarazel.de 6164 : 0 : get_rule_sortgroupclause(srt->tleSortGroupRef, query->targetList,
6165 : : false, context);
9527 tgl@sss.pgh.pa.us 6166 : 0 : sep = ", ";
6167 : : }
4753 rhaas@postgresql.org 6168 : 0 : appendStringInfoChar(buf, ')');
6169 : : }
6170 : : else
6171 : 0 : appendStringInfoString(buf, " DISTINCT");
6172 : : }
6173 : :
6174 : : /* Then we tell what to select (the targetlist) */
798 tgl@sss.pgh.pa.us 6175 :CBC 2904 : get_target_list(query->targetList, context);
6176 : :
6177 : : /* Add the FROM clause if needed */
7390 6178 : 2904 : get_from_clause(query, " FROM ", context);
6179 : :
6180 : : /* Add the WHERE clause if given */
6181 [ + + ]: 2904 : if (query->jointree->quals != NULL)
6182 : : {
6183 : 903 : appendContextKeyword(context, " WHERE ",
6184 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
6185 : 903 : get_rule_expr(query->jointree->quals, context, false);
6186 : : }
6187 : :
6188 : : /* Add the GROUP BY clause if given */
4191 andres@anarazel.de 6189 [ + + - + ]: 2904 : if (query->groupClause != NULL || query->groupingSets != NULL)
6190 : : {
6191 : : bool save_ingroupby;
6192 : :
7390 tgl@sss.pgh.pa.us 6193 : 119 : appendContextKeyword(context, " GROUP BY ",
6194 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
2058 tomas.vondra@postgre 6195 [ - + ]: 119 : if (query->groupDistinct)
2058 tomas.vondra@postgre 6196 :UBC 0 : appendStringInfoString(buf, "DISTINCT ");
6197 : :
798 tgl@sss.pgh.pa.us 6198 :CBC 119 : save_ingroupby = context->inGroupBy;
6199 : 119 : context->inGroupBy = true;
6200 : :
111 6201 [ + + ]: 119 : if (query->groupingSets == NIL)
6202 : : {
4191 andres@anarazel.de 6203 : 115 : sep = "";
6204 [ + - + + : 263 : foreach(l, query->groupClause)
+ + ]
6205 : : {
6206 : 148 : SortGroupClause *grp = (SortGroupClause *) lfirst(l);
6207 : :
6208 : 148 : appendStringInfoString(buf, sep);
6209 : 148 : get_rule_sortgroupclause(grp->tleSortGroupRef, query->targetList,
6210 : : false, context);
6211 : 148 : sep = ", ";
6212 : : }
6213 : : }
6214 : : else
6215 : : {
6216 : 4 : sep = "";
6217 [ + - + + : 8 : foreach(l, query->groupingSets)
+ + ]
6218 : : {
6219 : 4 : GroupingSet *grp = lfirst(l);
6220 : :
6221 : 4 : appendStringInfoString(buf, sep);
6222 : 4 : get_rule_groupingset(grp, query->targetList, true, context);
6223 : 4 : sep = ", ";
6224 : : }
6225 : : }
6226 : :
798 tgl@sss.pgh.pa.us 6227 : 119 : context->inGroupBy = save_ingroupby;
6228 : : }
6229 : :
6230 : : /* Add the HAVING clause if given */
7390 6231 [ + + ]: 2904 : if (query->havingQual != NULL)
6232 : : {
6233 : 5 : appendContextKeyword(context, " HAVING ",
6234 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
6235 : 5 : get_rule_expr(query->havingQual, context, false);
6236 : : }
6237 : :
6238 : : /* Add the WINDOW clause if needed */
6521 6239 [ + + ]: 2904 : if (query->windowClause != NIL)
6240 : 32 : get_rule_windowclause(query, context);
6241 : : }
6242 : :
6243 : : /* ----------
6244 : : * get_target_list - Parse back a SELECT target list
6245 : : *
6246 : : * This is also used for RETURNING lists in INSERT/UPDATE/DELETE/MERGE.
6247 : : * ----------
6248 : : */
6249 : : static void
798 6250 : 2997 : get_target_list(List *targetList, deparse_context *context)
6251 : : {
7390 6252 : 2997 : StringInfo buf = context->buf;
6253 : : StringInfoData targetbuf;
5064 6254 : 2997 : bool last_was_multiline = false;
6255 : : char *sep;
6256 : : int colno;
6257 : : ListCell *l;
6258 : :
6259 : : /* we use targetbuf to hold each TLE's text temporarily */
6260 : 2997 : initStringInfo(&targetbuf);
6261 : :
10300 bruce@momjian.us 6262 : 2997 : sep = " ";
8855 tgl@sss.pgh.pa.us 6263 : 2997 : colno = 0;
7390 6264 [ + - + + : 15820 : foreach(l, targetList)
+ + ]
6265 : : {
9550 6266 : 12823 : TargetEntry *tle = (TargetEntry *) lfirst(l);
6267 : : char *colname;
6268 : : char *attname;
6269 : :
7883 6270 [ + + ]: 12823 : if (tle->resjunk)
9533 6271 : 25 : continue; /* ignore junk entries */
6272 : :
7859 neilc@samurai.com 6273 : 12798 : appendStringInfoString(buf, sep);
10292 bruce@momjian.us 6274 : 12798 : sep = ", ";
8855 tgl@sss.pgh.pa.us 6275 : 12798 : colno++;
6276 : :
6277 : : /*
6278 : : * Put the new field text into targetbuf so we can decide after we've
6279 : : * got it whether or not it needs to go on a new line.
6280 : : */
5064 6281 : 12798 : resetStringInfo(&targetbuf);
5373 andrew@dunslane.net 6282 : 12798 : context->buf = &targetbuf;
6283 : :
6284 : : /*
6285 : : * We special-case Var nodes rather than using get_rule_expr. This is
6286 : : * needed because get_rule_expr will display a whole-row Var as
6287 : : * "foo.*", which is the preferred notation in most contexts, but at
6288 : : * the top level of a SELECT list it's not right (the parser will
6289 : : * expand that notation into multiple columns, yielding behavior
6290 : : * different from a whole-row Var). We need to call get_variable
6291 : : * directly so that we can tell it to do the right thing, and so that
6292 : : * we can get the attribute name which is the default AS label.
6293 : : */
4191 andres@anarazel.de 6294 [ + - + + ]: 12798 : if (tle->expr && (IsA(tle->expr, Var)))
6295 : : {
5305 tgl@sss.pgh.pa.us 6296 : 9819 : attname = get_variable((Var *) tle->expr, 0, true, context);
6297 : : }
6298 : : else
6299 : : {
7588 6300 : 2979 : get_rule_expr((Node *) tle->expr, context, true);
6301 : :
6302 : : /*
6303 : : * When colNamesVisible is true, we should always show the
6304 : : * assigned column name explicitly. Otherwise, show it only if
6305 : : * it's not FigureColname's fallback.
6306 : : */
798 6307 [ + + ]: 2979 : attname = context->colNamesVisible ? NULL : "?column?";
6308 : : }
6309 : :
6310 : : /*
6311 : : * Figure out what the result column should be called. In the context
6312 : : * of a view, use the view's tuple descriptor (so as to pick up the
6313 : : * effects of any column RENAME that's been done on the view).
6314 : : * Otherwise, just use what we can find in the TLE.
6315 : : */
6316 [ + + + - ]: 12798 : if (context->resultDesc && colno <= context->resultDesc->natts)
6317 : 11623 : colname = NameStr(TupleDescAttr(context->resultDesc,
6318 : : colno - 1)->attname);
6319 : : else
7883 6320 : 1175 : colname = tle->resname;
6321 : :
6322 : : /* Show AS unless the column's name is correct as-is */
8487 6323 [ + + ]: 12798 : if (colname) /* resname could be NULL */
6324 : : {
7588 6325 [ + + + + ]: 12767 : if (attname == NULL || strcmp(attname, colname) != 0)
5373 andrew@dunslane.net 6326 : 4196 : appendStringInfo(&targetbuf, " AS %s", quote_identifier(colname));
6327 : : }
6328 : :
6329 : : /* Restore context's output buffer */
6330 : 12798 : context->buf = buf;
6331 : :
6332 : : /* Consider line-wrapping if enabled */
5064 tgl@sss.pgh.pa.us 6333 [ + + + - ]: 12798 : if (PRETTY_INDENT(context) && context->wrapColumn >= 0)
6334 : : {
6335 : : int leading_nl_pos;
6336 : :
6337 : : /* Does the new field start with a new line? */
4742 6338 [ + - + + ]: 12771 : if (targetbuf.len > 0 && targetbuf.data[0] == '\n')
6339 : 375 : leading_nl_pos = 0;
6340 : : else
6341 : 12396 : leading_nl_pos = -1;
6342 : :
6343 : : /* If so, we shouldn't add anything */
6344 [ + + ]: 12771 : if (leading_nl_pos >= 0)
6345 : : {
6346 : : /* instead, remove any trailing spaces currently in buf */
6347 : 375 : removeStringInfoSpaces(buf);
6348 : : }
6349 : : else
6350 : : {
6351 : : char *trailing_nl;
6352 : :
6353 : : /* Locate the start of the current line in the output buffer */
6354 : 12396 : trailing_nl = strrchr(buf->data, '\n');
6355 [ + + ]: 12396 : if (trailing_nl == NULL)
6356 : 3679 : trailing_nl = buf->data;
6357 : : else
6358 : 8717 : trailing_nl++;
6359 : :
6360 : : /*
6361 : : * Add a newline, plus some indentation, if the new field is
6362 : : * not the first and either the new field would cause an
6363 : : * overflow or the last field used more than one line.
6364 : : */
6365 [ + + ]: 12396 : if (colno > 1 &&
6366 [ - + - - ]: 9435 : ((strlen(trailing_nl) + targetbuf.len > context->wrapColumn) ||
6367 : : last_was_multiline))
6368 : 9435 : appendContextKeyword(context, "", -PRETTYINDENT_STD,
6369 : : PRETTYINDENT_STD, PRETTYINDENT_VAR);
6370 : : }
6371 : :
6372 : : /* Remember this field's multiline status for next iteration */
5064 6373 : 12771 : last_was_multiline =
6374 : 12771 : (strchr(targetbuf.data + leading_nl_pos + 1, '\n') != NULL);
6375 : : }
6376 : :
6377 : : /* Add the new field */
2662 drowley@postgresql.o 6378 : 12798 : appendBinaryStringInfo(buf, targetbuf.data, targetbuf.len);
6379 : : }
6380 : :
6381 : : /* clean up */
5064 tgl@sss.pgh.pa.us 6382 : 2997 : pfree(targetbuf.data);
9527 6383 : 2997 : }
6384 : :
6385 : : static void
658 dean.a.rasheed@gmail 6386 : 93 : get_returning_clause(Query *query, deparse_context *context)
6387 : : {
6388 : 93 : StringInfo buf = context->buf;
6389 : :
6390 [ + - ]: 93 : if (query->returningList)
6391 : : {
6392 : 93 : bool have_with = false;
6393 : :
6394 : 93 : appendContextKeyword(context, " RETURNING",
6395 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
6396 : :
6397 : : /* Add WITH (OLD/NEW) options, if they're not the defaults */
6398 [ + + + + ]: 93 : if (query->returningOldAlias && strcmp(query->returningOldAlias, "old") != 0)
6399 : : {
6400 : 12 : appendStringInfo(buf, " WITH (OLD AS %s",
6401 : 12 : quote_identifier(query->returningOldAlias));
6402 : 12 : have_with = true;
6403 : : }
6404 [ + + + + ]: 93 : if (query->returningNewAlias && strcmp(query->returningNewAlias, "new") != 0)
6405 : : {
6406 [ + + ]: 12 : if (have_with)
6407 : 8 : appendStringInfo(buf, ", NEW AS %s",
6408 : 8 : quote_identifier(query->returningNewAlias));
6409 : : else
6410 : : {
6411 : 4 : appendStringInfo(buf, " WITH (NEW AS %s",
6412 : 4 : quote_identifier(query->returningNewAlias));
6413 : 4 : have_with = true;
6414 : : }
6415 : : }
6416 [ + + ]: 93 : if (have_with)
6417 : 16 : appendStringInfoChar(buf, ')');
6418 : :
6419 : : /* Add the returning expressions themselves */
6420 : 93 : get_target_list(query->returningList, context);
6421 : : }
6422 : 93 : }
6423 : :
6424 : : static void
798 tgl@sss.pgh.pa.us 6425 : 463 : get_setop_query(Node *setOp, Query *query, deparse_context *context)
6426 : : {
9527 6427 : 463 : StringInfo buf = context->buf;
6428 : : bool need_paren;
6429 : :
6430 : : /* Guard against excessively long or deeply-nested queries */
4572 6431 [ - + ]: 463 : CHECK_FOR_INTERRUPTS();
6432 : 463 : check_stack_depth();
6433 : :
9527 6434 [ + + ]: 463 : if (IsA(setOp, RangeTblRef))
6435 : : {
6436 : 283 : RangeTblRef *rtr = (RangeTblRef *) setOp;
6437 : 283 : RangeTblEntry *rte = rt_fetch(rtr->rtindex, query->rtable);
9359 bruce@momjian.us 6438 : 283 : Query *subquery = rte->subquery;
6439 : :
9527 tgl@sss.pgh.pa.us 6440 [ - + ]: 283 : Assert(subquery != NULL);
6441 : :
6442 : : /*
6443 : : * We need parens if WITH, ORDER BY, FOR UPDATE, or LIMIT; see gram.y.
6444 : : * Also add parens if the leaf query contains its own set operations.
6445 : : * (That shouldn't happen unless one of the other clauses is also
6446 : : * present, see transformSetOperationTree; but let's be safe.)
6447 : : */
6606 6448 : 849 : need_paren = (subquery->cteList ||
6449 [ + - ]: 283 : subquery->sortClause ||
8157 6450 [ + - ]: 283 : subquery->rowMarks ||
6451 [ + - ]: 283 : subquery->limitOffset ||
715 6452 [ + - + - ]: 849 : subquery->limitCount ||
6453 [ - + ]: 283 : subquery->setOperations);
8157 6454 [ - + ]: 283 : if (need_paren)
8157 tgl@sss.pgh.pa.us 6455 :UBC 0 : appendStringInfoChar(buf, '(');
798 tgl@sss.pgh.pa.us 6456 :CBC 283 : get_query_def(subquery, buf, context->namespaces,
6457 : 283 : context->resultDesc, context->colNamesVisible,
6458 : : context->prettyFlags, context->wrapColumn,
6459 : : context->indentLevel);
8157 6460 [ - + ]: 283 : if (need_paren)
8157 tgl@sss.pgh.pa.us 6461 :UBC 0 : appendStringInfoChar(buf, ')');
6462 : : }
9527 tgl@sss.pgh.pa.us 6463 [ + - ]:CBC 180 : else if (IsA(setOp, SetOperationStmt))
6464 : : {
6465 : 180 : SetOperationStmt *op = (SetOperationStmt *) setOp;
6466 : : int subindent;
6467 : : bool save_colnamesvisible;
6468 : :
6469 : : /*
6470 : : * We force parens when nesting two SetOperationStmts, except when the
6471 : : * lefthand input is another setop of the same kind. Syntactically,
6472 : : * we could omit parens in rather more cases, but it seems best to use
6473 : : * parens to flag cases where the setop operator changes. If we use
6474 : : * parens, we also increase the indentation level for the child query.
6475 : : *
6476 : : * There are some cases in which parens are needed around a leaf query
6477 : : * too, but those are more easily handled at the next level down (see
6478 : : * code above).
6479 : : */
4572 6480 [ + + ]: 180 : if (IsA(op->larg, SetOperationStmt))
6481 : : {
6482 : 77 : SetOperationStmt *lop = (SetOperationStmt *) op->larg;
6483 : :
6484 [ + - + - ]: 77 : if (op->op == lop->op && op->all == lop->all)
6485 : 77 : need_paren = false;
6486 : : else
4572 tgl@sss.pgh.pa.us 6487 :UBC 0 : need_paren = true;
6488 : : }
6489 : : else
4572 tgl@sss.pgh.pa.us 6490 :CBC 103 : need_paren = false;
6491 : :
8157 6492 [ - + ]: 180 : if (need_paren)
6493 : : {
8157 tgl@sss.pgh.pa.us 6494 :UBC 0 : appendStringInfoChar(buf, '(');
4572 6495 : 0 : subindent = PRETTYINDENT_STD;
6496 : 0 : appendContextKeyword(context, "", subindent, 0, 0);
6497 : : }
6498 : : else
4572 tgl@sss.pgh.pa.us 6499 :CBC 180 : subindent = 0;
6500 : :
798 6501 : 180 : get_setop_query(op->larg, query, context);
6502 : :
4572 6503 [ - + ]: 180 : if (need_paren)
4572 tgl@sss.pgh.pa.us 6504 :UBC 0 : appendContextKeyword(context, ") ", -subindent, 0, 0);
4572 tgl@sss.pgh.pa.us 6505 [ + - ]:CBC 180 : else if (PRETTY_INDENT(context))
6506 : 180 : appendContextKeyword(context, "", -subindent, 0, 0);
6507 : : else
8494 bruce@momjian.us 6508 :UBC 0 : appendStringInfoChar(buf, ' ');
6509 : :
9527 tgl@sss.pgh.pa.us 6510 [ + - - - ]:CBC 180 : switch (op->op)
6511 : : {
6512 : 180 : case SETOP_UNION:
4572 6513 : 180 : appendStringInfoString(buf, "UNION ");
9527 6514 : 180 : break;
9527 tgl@sss.pgh.pa.us 6515 :UBC 0 : case SETOP_INTERSECT:
4572 6516 : 0 : appendStringInfoString(buf, "INTERSECT ");
9527 6517 : 0 : break;
6518 : 0 : case SETOP_EXCEPT:
4572 6519 : 0 : appendStringInfoString(buf, "EXCEPT ");
9527 6520 : 0 : break;
6521 : 0 : default:
8502 6522 [ # # ]: 0 : elog(ERROR, "unrecognized set op: %d",
6523 : : (int) op->op);
6524 : : }
9527 tgl@sss.pgh.pa.us 6525 [ + + ]:CBC 180 : if (op->all)
4753 rhaas@postgresql.org 6526 : 172 : appendStringInfoString(buf, "ALL ");
6527 : :
6528 : : /* Always parenthesize if RHS is another setop */
4572 tgl@sss.pgh.pa.us 6529 : 180 : need_paren = IsA(op->rarg, SetOperationStmt);
6530 : :
6531 : : /*
6532 : : * The indentation code here is deliberately a bit different from that
6533 : : * for the lefthand input, because we want the line breaks in
6534 : : * different places.
6535 : : */
8157 6536 [ - + ]: 180 : if (need_paren)
6537 : : {
8157 tgl@sss.pgh.pa.us 6538 :UBC 0 : appendStringInfoChar(buf, '(');
4572 6539 : 0 : subindent = PRETTYINDENT_STD;
6540 : : }
6541 : : else
4572 tgl@sss.pgh.pa.us 6542 :CBC 180 : subindent = 0;
6543 : 180 : appendContextKeyword(context, "", subindent, 0, 0);
6544 : :
6545 : : /*
6546 : : * The output column names of the RHS sub-select don't matter.
6547 : : */
798 6548 : 180 : save_colnamesvisible = context->colNamesVisible;
6549 : 180 : context->colNamesVisible = false;
6550 : :
6551 : 180 : get_setop_query(op->rarg, query, context);
6552 : :
6553 : 180 : context->colNamesVisible = save_colnamesvisible;
6554 : :
6606 6555 [ + - ]: 180 : if (PRETTY_INDENT(context))
4572 6556 : 180 : context->indentLevel -= subindent;
6557 [ - + ]: 180 : if (need_paren)
4572 tgl@sss.pgh.pa.us 6558 :UBC 0 : appendContextKeyword(context, ")", 0, 0, 0);
6559 : : }
6560 : : else
6561 : : {
8502 6562 [ # # ]: 0 : elog(ERROR, "unrecognized node type: %d",
6563 : : (int) nodeTag(setOp));
6564 : : }
9527 tgl@sss.pgh.pa.us 6565 :CBC 463 : }
6566 : :
6567 : : /*
6568 : : * Display a sort/group clause.
6569 : : *
6570 : : * Also returns the expression tree, so caller need not find it again.
6571 : : */
6572 : : static Node *
4191 andres@anarazel.de 6573 : 435 : get_rule_sortgroupclause(Index ref, List *tlist, bool force_colno,
6574 : : deparse_context *context)
6575 : : {
9335 tgl@sss.pgh.pa.us 6576 : 435 : StringInfo buf = context->buf;
6577 : : TargetEntry *tle;
6578 : : Node *expr;
6579 : :
4191 andres@anarazel.de 6580 : 435 : tle = get_sortgroupref_tle(ref, tlist);
8729 tgl@sss.pgh.pa.us 6581 : 435 : expr = (Node *) tle->expr;
6582 : :
6583 : : /*
6584 : : * Use column-number form if requested by caller. Otherwise, if
6585 : : * expression is a constant, force it to be dumped with an explicit cast
6586 : : * as decoration --- this is because a simple integer constant is
6587 : : * ambiguous (and will be misinterpreted by findTargetlistEntrySQL92()) if
6588 : : * we dump it without any decoration. Similarly, if it's just a Var,
6589 : : * there is risk of misinterpretation if the column name is reassigned in
6590 : : * the SELECT list, so we may need to force table qualification. And, if
6591 : : * it's anything more complex than a simple Var, then force extra parens
6592 : : * around it, to ensure it can't be misinterpreted as a cube() or rollup()
6593 : : * construct.
6594 : : */
6878 6595 [ + + ]: 435 : if (force_colno)
6596 : : {
7883 6597 [ - + ]: 7 : Assert(!tle->resjunk);
6598 : 7 : appendStringInfo(buf, "%d", tle->resno);
6599 : : }
798 6600 [ + - ]: 428 : else if (!expr)
6601 : : /* do nothing, probably can't happen */ ;
6602 [ - + ]: 428 : else if (IsA(expr, Const))
6878 tgl@sss.pgh.pa.us 6603 :UBC 0 : get_const_expr((Const *) expr, context, 1);
798 tgl@sss.pgh.pa.us 6604 [ + + ]:CBC 428 : else if (IsA(expr, Var))
6605 : : {
6606 : : /* Tell get_variable to check for name conflict */
6607 : 411 : bool save_varinorderby = context->varInOrderBy;
6608 : :
6609 : 411 : context->varInOrderBy = true;
6610 : 411 : (void) get_variable((Var *) expr, 0, false, context);
6611 : 411 : context->varInOrderBy = save_varinorderby;
6612 : : }
6613 : : else
6614 : : {
6615 : : /*
6616 : : * We must force parens for function-like expressions even if
6617 : : * PRETTY_PAREN is off, since those are the ones in danger of
6618 : : * misparsing. For other expressions we need to force them only if
6619 : : * PRETTY_PAREN is on, since otherwise the expression will output them
6620 : : * itself. (We can't skip the parens.)
6621 : : */
4184 bruce@momjian.us 6622 : 34 : bool need_paren = (PRETTY_PAREN(context)
6623 [ + + ]: 17 : || IsA(expr, FuncExpr)
2364 tgl@sss.pgh.pa.us 6624 [ + - ]: 15 : || IsA(expr, Aggref)
1317 alvherre@alvh.no-ip. 6625 [ + - ]: 15 : || IsA(expr, WindowFunc)
6626 [ + - - + ]: 34 : || IsA(expr, JsonConstructorExpr));
6627 : :
4191 andres@anarazel.de 6628 [ + + ]: 17 : if (need_paren)
3369 peter_e@gmx.net 6629 : 2 : appendStringInfoChar(context->buf, '(');
8813 tgl@sss.pgh.pa.us 6630 : 17 : get_rule_expr(expr, context, true);
4191 andres@anarazel.de 6631 [ + + ]: 17 : if (need_paren)
3369 peter_e@gmx.net 6632 : 2 : appendStringInfoChar(context->buf, ')');
6633 : : }
6634 : :
8952 tgl@sss.pgh.pa.us 6635 : 435 : return expr;
6636 : : }
6637 : :
6638 : : /*
6639 : : * Display a GroupingSet
6640 : : */
6641 : : static void
4191 andres@anarazel.de 6642 : 12 : get_rule_groupingset(GroupingSet *gset, List *targetlist,
6643 : : bool omit_parens, deparse_context *context)
6644 : : {
6645 : : ListCell *l;
6646 : 12 : StringInfo buf = context->buf;
6647 : 12 : bool omit_child_parens = true;
6648 : 12 : char *sep = "";
6649 : :
6650 [ - + + - : 12 : switch (gset->kind)
- - ]
6651 : : {
4191 andres@anarazel.de 6652 :UBC 0 : case GROUPING_SET_EMPTY:
6653 : 0 : appendStringInfoString(buf, "()");
6654 : 0 : return;
6655 : :
4191 andres@anarazel.de 6656 :CBC 8 : case GROUPING_SET_SIMPLE:
6657 : : {
6658 [ + - + - ]: 8 : if (!omit_parens || list_length(gset->content) != 1)
3369 peter_e@gmx.net 6659 : 8 : appendStringInfoChar(buf, '(');
6660 : :
4191 andres@anarazel.de 6661 [ + - + + : 28 : foreach(l, gset->content)
+ + ]
6662 : : {
4184 bruce@momjian.us 6663 : 20 : Index ref = lfirst_int(l);
6664 : :
4191 andres@anarazel.de 6665 : 20 : appendStringInfoString(buf, sep);
6666 : 20 : get_rule_sortgroupclause(ref, targetlist,
6667 : : false, context);
6668 : 20 : sep = ", ";
6669 : : }
6670 : :
6671 [ + - + - ]: 8 : if (!omit_parens || list_length(gset->content) != 1)
3369 peter_e@gmx.net 6672 : 8 : appendStringInfoChar(buf, ')');
6673 : : }
4191 andres@anarazel.de 6674 : 8 : return;
6675 : :
6676 : 4 : case GROUPING_SET_ROLLUP:
6677 : 4 : appendStringInfoString(buf, "ROLLUP(");
6678 : 4 : break;
4191 andres@anarazel.de 6679 :UBC 0 : case GROUPING_SET_CUBE:
6680 : 0 : appendStringInfoString(buf, "CUBE(");
6681 : 0 : break;
6682 : 0 : case GROUPING_SET_SETS:
6683 : 0 : appendStringInfoString(buf, "GROUPING SETS (");
6684 : 0 : omit_child_parens = false;
6685 : 0 : break;
6686 : : }
6687 : :
4191 andres@anarazel.de 6688 [ + - + + :CBC 12 : foreach(l, gset->content)
+ + ]
6689 : : {
6690 : 8 : appendStringInfoString(buf, sep);
6691 : 8 : get_rule_groupingset(lfirst(l), targetlist, omit_child_parens, context);
6692 : 8 : sep = ", ";
6693 : : }
6694 : :
3369 peter_e@gmx.net 6695 : 4 : appendStringInfoChar(buf, ')');
6696 : : }
6697 : :
6698 : : /*
6699 : : * Display an ORDER BY list.
6700 : : */
6701 : : static void
6521 tgl@sss.pgh.pa.us 6702 : 246 : get_rule_orderby(List *orderList, List *targetList,
6703 : : bool force_colno, deparse_context *context)
6704 : : {
6705 : 246 : StringInfo buf = context->buf;
6706 : : const char *sep;
6707 : : ListCell *l;
6708 : :
6709 : 246 : sep = "";
6710 [ + - + + : 513 : foreach(l, orderList)
+ + ]
6711 : : {
6712 : 267 : SortGroupClause *srt = (SortGroupClause *) lfirst(l);
6713 : : Node *sortexpr;
6714 : : Oid sortcoltype;
6715 : : TypeCacheEntry *typentry;
6716 : :
6717 : 267 : appendStringInfoString(buf, sep);
4191 andres@anarazel.de 6718 : 267 : sortexpr = get_rule_sortgroupclause(srt->tleSortGroupRef, targetList,
6719 : : force_colno, context);
6521 tgl@sss.pgh.pa.us 6720 : 267 : sortcoltype = exprType(sortexpr);
6721 : : /* See whether operator is default < or > for datatype */
6722 : 267 : typentry = lookup_type_cache(sortcoltype,
6723 : : TYPECACHE_LT_OPR | TYPECACHE_GT_OPR);
6724 [ + + ]: 267 : if (srt->sortop == typentry->lt_opr)
6725 : : {
6726 : : /* ASC is default, so emit nothing for it */
6727 [ - + ]: 250 : if (srt->nulls_first)
4753 rhaas@postgresql.org 6728 :UBC 0 : appendStringInfoString(buf, " NULLS FIRST");
6729 : : }
6521 tgl@sss.pgh.pa.us 6730 [ + + ]:CBC 17 : else if (srt->sortop == typentry->gt_opr)
6731 : : {
4753 rhaas@postgresql.org 6732 : 6 : appendStringInfoString(buf, " DESC");
6733 : : /* DESC defaults to NULLS FIRST */
6521 tgl@sss.pgh.pa.us 6734 [ + + ]: 6 : if (!srt->nulls_first)
4753 rhaas@postgresql.org 6735 : 1 : appendStringInfoString(buf, " NULLS LAST");
6736 : : }
6737 : : else
6738 : : {
6521 tgl@sss.pgh.pa.us 6739 : 11 : appendStringInfo(buf, " USING %s",
6740 : : generate_operator_name(srt->sortop,
6741 : : sortcoltype,
6742 : : sortcoltype));
6743 : : /* be specific to eliminate ambiguity */
6744 [ - + ]: 11 : if (srt->nulls_first)
4753 rhaas@postgresql.org 6745 :UBC 0 : appendStringInfoString(buf, " NULLS FIRST");
6746 : : else
4753 rhaas@postgresql.org 6747 :CBC 11 : appendStringInfoString(buf, " NULLS LAST");
6748 : : }
6521 tgl@sss.pgh.pa.us 6749 : 267 : sep = ", ";
6750 : : }
6751 : 246 : }
6752 : :
6753 : : /*
6754 : : * Display a WINDOW clause.
6755 : : *
6756 : : * Note that the windowClause list might contain only anonymous window
6757 : : * specifications, in which case we should print nothing here.
6758 : : */
6759 : : static void
6760 : 32 : get_rule_windowclause(Query *query, deparse_context *context)
6761 : : {
6762 : 32 : StringInfo buf = context->buf;
6763 : : const char *sep;
6764 : : ListCell *l;
6765 : :
6766 : 32 : sep = NULL;
6767 [ + - + + : 64 : foreach(l, query->windowClause)
+ + ]
6768 : : {
6769 : 32 : WindowClause *wc = (WindowClause *) lfirst(l);
6770 : :
6771 [ + + ]: 32 : if (wc->name == NULL)
6772 : 28 : continue; /* ignore anonymous windows */
6773 : :
6774 [ + - ]: 4 : if (sep == NULL)
6775 : 4 : appendContextKeyword(context, " WINDOW ",
6776 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
6777 : : else
6521 tgl@sss.pgh.pa.us 6778 :UBC 0 : appendStringInfoString(buf, sep);
6779 : :
6521 tgl@sss.pgh.pa.us 6780 :CBC 4 : appendStringInfo(buf, "%s AS ", quote_identifier(wc->name));
6781 : :
6782 : 4 : get_rule_windowspec(wc, query->targetList, context);
6783 : :
6784 : 4 : sep = ", ";
6785 : : }
6786 : 32 : }
6787 : :
6788 : : /*
6789 : : * Display a window definition
6790 : : */
6791 : : static void
6792 : 32 : get_rule_windowspec(WindowClause *wc, List *targetList,
6793 : : deparse_context *context)
6794 : : {
6795 : 32 : StringInfo buf = context->buf;
6796 : 32 : bool needspace = false;
6797 : : const char *sep;
6798 : : ListCell *l;
6799 : :
6800 : 32 : appendStringInfoChar(buf, '(');
6801 [ - + ]: 32 : if (wc->refname)
6802 : : {
6521 tgl@sss.pgh.pa.us 6803 :UBC 0 : appendStringInfoString(buf, quote_identifier(wc->refname));
6804 : 0 : needspace = true;
6805 : : }
6806 : : /* partition clauses are always inherited, so only print if no refname */
6521 tgl@sss.pgh.pa.us 6807 [ - + - - ]:CBC 32 : if (wc->partitionClause && !wc->refname)
6808 : : {
6521 tgl@sss.pgh.pa.us 6809 [ # # ]:UBC 0 : if (needspace)
6810 : 0 : appendStringInfoChar(buf, ' ');
6811 : 0 : appendStringInfoString(buf, "PARTITION BY ");
6812 : 0 : sep = "";
6813 [ # # # # : 0 : foreach(l, wc->partitionClause)
# # ]
6814 : : {
6815 : 0 : SortGroupClause *grp = (SortGroupClause *) lfirst(l);
6816 : :
6817 : 0 : appendStringInfoString(buf, sep);
4191 andres@anarazel.de 6818 : 0 : get_rule_sortgroupclause(grp->tleSortGroupRef, targetList,
6819 : : false, context);
6521 tgl@sss.pgh.pa.us 6820 : 0 : sep = ", ";
6821 : : }
6822 : 0 : needspace = true;
6823 : : }
6824 : : /* print ordering clause only if not inherited */
6521 tgl@sss.pgh.pa.us 6825 [ + - + - ]:CBC 32 : if (wc->orderClause && !wc->copiedOrder)
6826 : : {
6827 [ - + ]: 32 : if (needspace)
6521 tgl@sss.pgh.pa.us 6828 :UBC 0 : appendStringInfoChar(buf, ' ');
6521 tgl@sss.pgh.pa.us 6829 :CBC 32 : appendStringInfoString(buf, "ORDER BY ");
6830 : 32 : get_rule_orderby(wc->orderClause, targetList, false, context);
6831 : 32 : needspace = true;
6832 : : }
6833 : : /* framing clause is never inherited, so print unless it's default */
6518 6834 [ + + ]: 32 : if (wc->frameOptions & FRAMEOPTION_NONDEFAULT)
6835 : : {
6836 [ + - ]: 28 : if (needspace)
6837 : 28 : appendStringInfoChar(buf, ' ');
604 6838 : 28 : get_window_frame_options(wc->frameOptions,
6839 : : wc->startOffset, wc->endOffset,
6840 : : context);
6841 : : }
6842 : 32 : appendStringInfoChar(buf, ')');
6843 : 32 : }
6844 : :
6845 : : /*
6846 : : * Append the description of a window's framing options to context->buf
6847 : : */
6848 : : static void
6849 : 218 : get_window_frame_options(int frameOptions,
6850 : : Node *startOffset, Node *endOffset,
6851 : : deparse_context *context)
6852 : : {
6853 : 218 : StringInfo buf = context->buf;
6854 : :
6855 [ + - ]: 218 : if (frameOptions & FRAMEOPTION_NONDEFAULT)
6856 : : {
6857 [ + + ]: 218 : if (frameOptions & FRAMEOPTION_RANGE)
6518 6858 : 29 : appendStringInfoString(buf, "RANGE ");
604 6859 [ + + ]: 189 : else if (frameOptions & FRAMEOPTION_ROWS)
6518 6860 : 169 : appendStringInfoString(buf, "ROWS ");
604 6861 [ + - ]: 20 : else if (frameOptions & FRAMEOPTION_GROUPS)
3193 6862 : 20 : appendStringInfoString(buf, "GROUPS ");
6863 : : else
6518 tgl@sss.pgh.pa.us 6864 :UBC 0 : Assert(false);
604 tgl@sss.pgh.pa.us 6865 [ + + ]:CBC 218 : if (frameOptions & FRAMEOPTION_BETWEEN)
6518 6866 : 113 : appendStringInfoString(buf, "BETWEEN ");
604 6867 [ + + ]: 218 : if (frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING)
6518 6868 : 145 : appendStringInfoString(buf, "UNBOUNDED PRECEDING ");
604 6869 [ + + ]: 73 : else if (frameOptions & FRAMEOPTION_START_CURRENT_ROW)
6518 6870 : 33 : appendStringInfoString(buf, "CURRENT ROW ");
604 6871 [ + - ]: 40 : else if (frameOptions & FRAMEOPTION_START_OFFSET)
6872 : : {
6873 : 40 : get_rule_expr(startOffset, context, false);
6874 [ + - ]: 40 : if (frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING)
6110 6875 : 40 : appendStringInfoString(buf, " PRECEDING ");
604 tgl@sss.pgh.pa.us 6876 [ # # ]:UBC 0 : else if (frameOptions & FRAMEOPTION_START_OFFSET_FOLLOWING)
6110 6877 : 0 : appendStringInfoString(buf, " FOLLOWING ");
6878 : : else
6879 : 0 : Assert(false);
6880 : : }
6881 : : else
6518 6882 : 0 : Assert(false);
604 tgl@sss.pgh.pa.us 6883 [ + + ]:CBC 218 : if (frameOptions & FRAMEOPTION_BETWEEN)
6884 : : {
6518 6885 : 113 : appendStringInfoString(buf, "AND ");
604 6886 [ + + ]: 113 : if (frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING)
6518 6887 : 13 : appendStringInfoString(buf, "UNBOUNDED FOLLOWING ");
604 6888 [ + + ]: 100 : else if (frameOptions & FRAMEOPTION_END_CURRENT_ROW)
6518 6889 : 28 : appendStringInfoString(buf, "CURRENT ROW ");
604 6890 [ + - ]: 72 : else if (frameOptions & FRAMEOPTION_END_OFFSET)
6891 : : {
6892 : 72 : get_rule_expr(endOffset, context, false);
6893 [ + + ]: 72 : if (frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING)
6110 6894 : 28 : appendStringInfoString(buf, " PRECEDING ");
604 6895 [ + - ]: 44 : else if (frameOptions & FRAMEOPTION_END_OFFSET_FOLLOWING)
6110 6896 : 44 : appendStringInfoString(buf, " FOLLOWING ");
6897 : : else
6110 tgl@sss.pgh.pa.us 6898 :UBC 0 : Assert(false);
6899 : : }
6900 : : else
6518 6901 : 0 : Assert(false);
6902 : : }
604 tgl@sss.pgh.pa.us 6903 [ + + ]:CBC 218 : if (frameOptions & FRAMEOPTION_EXCLUDE_CURRENT_ROW)
3193 6904 : 24 : appendStringInfoString(buf, "EXCLUDE CURRENT ROW ");
604 6905 [ + + ]: 194 : else if (frameOptions & FRAMEOPTION_EXCLUDE_GROUP)
3193 6906 : 12 : appendStringInfoString(buf, "EXCLUDE GROUP ");
604 6907 [ + + ]: 182 : else if (frameOptions & FRAMEOPTION_EXCLUDE_TIES)
3193 6908 : 12 : appendStringInfoString(buf, "EXCLUDE TIES ");
6909 : : /* we will now have a trailing space; remove it */
604 6910 : 218 : buf->data[--(buf->len)] = '\0';
6911 : : }
6912 : 218 : }
6913 : :
6914 : : /*
6915 : : * Return the description of a window's framing options as a palloc'd string
6916 : : */
6917 : : char *
6918 : 190 : get_window_frame_options_for_explain(int frameOptions,
6919 : : Node *startOffset, Node *endOffset,
6920 : : List *dpcontext, bool forceprefix)
6921 : : {
6922 : : StringInfoData buf;
6923 : : deparse_context context;
6924 : :
6925 : 190 : initStringInfo(&buf);
6926 : 190 : context.buf = &buf;
6927 : 190 : context.namespaces = dpcontext;
6928 : 190 : context.resultDesc = NULL;
6929 : 190 : context.targetList = NIL;
6930 : 190 : context.windowClause = NIL;
6931 : 190 : context.varprefix = forceprefix;
6932 : 190 : context.prettyFlags = 0;
6933 : 190 : context.wrapColumn = WRAP_COLUMN_DEFAULT;
6934 : 190 : context.indentLevel = 0;
6935 : 190 : context.colNamesVisible = true;
6936 : 190 : context.inGroupBy = false;
6937 : 190 : context.varInOrderBy = false;
6938 : 190 : context.appendparents = NULL;
6939 : :
6940 : 190 : get_window_frame_options(frameOptions, startOffset, endOffset, &context);
6941 : :
6942 : 190 : return buf.data;
6943 : : }
6944 : :
6945 : : /* ----------
6946 : : * get_insert_query_def - Parse back an INSERT parsetree
6947 : : * ----------
6948 : : */
6949 : : static void
798 6950 : 194 : get_insert_query_def(Query *query, deparse_context *context)
6951 : : {
9895 6952 : 194 : StringInfo buf = context->buf;
9527 6953 : 194 : RangeTblEntry *select_rte = NULL;
7400 mail@joeconway.com 6954 : 194 : RangeTblEntry *values_rte = NULL;
6955 : : RangeTblEntry *rte;
6956 : : char *sep;
6957 : : ListCell *l;
6958 : : List *strippedexprs;
6959 : :
6960 : : /* Insert the WITH clause if given */
5865 tgl@sss.pgh.pa.us 6961 : 194 : get_with_clause(query, context);
6962 : :
6963 : : /*
6964 : : * If it's an INSERT ... SELECT or multi-row VALUES, there will be a
6965 : : * single RTE for the SELECT or VALUES. Plain VALUES has neither.
6966 : : */
10292 bruce@momjian.us 6967 [ + - + + : 758 : foreach(l, query->rtable)
+ + ]
6968 : : {
6969 : 564 : rte = (RangeTblEntry *) lfirst(l);
6970 : :
7400 mail@joeconway.com 6971 [ + + ]: 564 : if (rte->rtekind == RTE_SUBQUERY)
6972 : : {
6973 [ - + ]: 30 : if (select_rte)
7400 mail@joeconway.com 6974 [ # # ]:UBC 0 : elog(ERROR, "too many subquery RTEs in INSERT");
7400 mail@joeconway.com 6975 :CBC 30 : select_rte = rte;
6976 : : }
6977 : :
6978 [ + + ]: 564 : if (rte->rtekind == RTE_VALUES)
6979 : : {
6980 [ - + ]: 26 : if (values_rte)
7400 mail@joeconway.com 6981 [ # # ]:UBC 0 : elog(ERROR, "too many values RTEs in INSERT");
7400 mail@joeconway.com 6982 :CBC 26 : values_rte = rte;
6983 : : }
6984 : : }
6985 [ + + - + ]: 194 : if (select_rte && values_rte)
7400 mail@joeconway.com 6986 [ # # ]:UBC 0 : elog(ERROR, "both subquery and values RTEs in INSERT");
6987 : :
6988 : : /*
6989 : : * Start the query with INSERT INTO relname
6990 : : */
9867 tgl@sss.pgh.pa.us 6991 :CBC 194 : rte = rt_fetch(query->resultRelation, query->rtable);
8952 6992 [ - + ]: 194 : Assert(rte->rtekind == RTE_RELATION);
6993 : :
8499 6994 [ + - ]: 194 : if (PRETTY_INDENT(context))
6995 : : {
8494 bruce@momjian.us 6996 : 194 : context->indentLevel += PRETTYINDENT_STD;
6997 : 194 : appendStringInfoChar(buf, ' ');
6998 : : }
1357 tgl@sss.pgh.pa.us 6999 : 194 : appendStringInfo(buf, "INSERT INTO %s",
7000 : : generate_relation_name(rte->relid, NIL));
7001 : :
7002 : : /* Print the relation alias, if needed; INSERT requires explicit AS */
7003 : 194 : get_rte_alias(rte, query->resultRelation, true, context);
7004 : :
7005 : : /* always want a space here */
7006 : 194 : appendStringInfoChar(buf, ' ');
7007 : :
7008 : : /*
7009 : : * Add the insert-column-names list. Any indirection decoration needed on
7010 : : * the column names can be inferred from the top targetlist.
7011 : : */
8184 7012 : 194 : strippedexprs = NIL;
7013 : 194 : sep = "";
5130 7014 [ + - ]: 194 : if (query->targetList)
7015 : 194 : appendStringInfoChar(buf, '(');
10292 bruce@momjian.us 7016 [ + - + + : 695 : foreach(l, query->targetList)
+ + ]
7017 : : {
9550 tgl@sss.pgh.pa.us 7018 : 501 : TargetEntry *tle = (TargetEntry *) lfirst(l);
7019 : :
7883 7020 [ - + ]: 501 : if (tle->resjunk)
9533 tgl@sss.pgh.pa.us 7021 :UBC 0 : continue; /* ignore junk entries */
7022 : :
7859 neilc@samurai.com 7023 :CBC 501 : appendStringInfoString(buf, sep);
10292 bruce@momjian.us 7024 : 501 : sep = ", ";
7025 : :
7026 : : /*
7027 : : * Put out name of target column; look in the catalogs, not at
7028 : : * tle->resname, since resname will fail to track RENAME.
7029 : : */
8314 neilc@samurai.com 7030 : 501 : appendStringInfoString(buf,
3188 alvherre@alvh.no-ip. 7031 : 501 : quote_identifier(get_attname(rte->relid,
7032 : 501 : tle->resno,
7033 : : false)));
7034 : :
7035 : : /*
7036 : : * Print any indirection needed (subfields or subscripts), and strip
7037 : : * off the top-level nodes representing the indirection assignments.
7038 : : * Add the stripped expressions to strippedexprs. (If it's a
7039 : : * single-VALUES statement, the stripped expressions are the VALUES to
7040 : : * print below. Otherwise they're just Vars and not really
7041 : : * interesting.)
7042 : : */
3746 tgl@sss.pgh.pa.us 7043 : 501 : strippedexprs = lappend(strippedexprs,
7044 : 501 : processIndirection((Node *) tle->expr,
7045 : : context));
7046 : : }
5130 7047 [ + - ]: 194 : if (query->targetList)
4753 rhaas@postgresql.org 7048 : 194 : appendStringInfoString(buf, ") ");
7049 : :
3500 peter_e@gmx.net 7050 [ - + ]: 194 : if (query->override)
7051 : : {
3500 peter_e@gmx.net 7052 [ # # ]:UBC 0 : if (query->override == OVERRIDING_SYSTEM_VALUE)
7053 : 0 : appendStringInfoString(buf, "OVERRIDING SYSTEM VALUE ");
7054 [ # # ]: 0 : else if (query->override == OVERRIDING_USER_VALUE)
7055 : 0 : appendStringInfoString(buf, "OVERRIDING USER VALUE ");
7056 : : }
7057 : :
7400 mail@joeconway.com 7058 [ + + ]:CBC 194 : if (select_rte)
7059 : : {
7060 : : /* Add the SELECT */
1814 tgl@sss.pgh.pa.us 7061 : 30 : get_query_def(select_rte->subquery, buf, context->namespaces, NULL,
7062 : : false,
7063 : : context->prettyFlags, context->wrapColumn,
7064 : : context->indentLevel);
7065 : : }
7400 mail@joeconway.com 7066 [ + + ]: 164 : else if (values_rte)
7067 : : {
7068 : : /* Add the multi-VALUES expression lists */
7069 : 26 : get_values_def(values_rte->values_lists, context);
7070 : : }
5130 tgl@sss.pgh.pa.us 7071 [ + - ]: 138 : else if (strippedexprs)
7072 : : {
7073 : : /* Add the single-VALUES expression list */
8499 7074 : 138 : appendContextKeyword(context, "VALUES (",
7075 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 2);
1757 7076 : 138 : get_rule_list_toplevel(strippedexprs, context, false);
9755 7077 : 138 : appendStringInfoChar(buf, ')');
7078 : : }
7079 : : else
7080 : : {
7081 : : /* No expressions, so it must be DEFAULT VALUES */
4753 rhaas@postgresql.org 7082 :UBC 0 : appendStringInfoString(buf, "DEFAULT VALUES");
7083 : : }
7084 : :
7085 : : /* Add ON CONFLICT if present */
4199 andres@anarazel.de 7086 [ + + ]:CBC 194 : if (query->onConflict)
7087 : : {
7088 : 24 : OnConflictExpr *confl = query->onConflict;
7089 : :
4144 heikki.linnakangas@i 7090 : 24 : appendStringInfoString(buf, " ON CONFLICT");
7091 : :
4188 andres@anarazel.de 7092 [ + + ]: 24 : if (confl->arbiterElems)
7093 : : {
7094 : : /* Add the single-VALUES expression list */
7095 : 20 : appendStringInfoChar(buf, '(');
7096 : 20 : get_rule_expr((Node *) confl->arbiterElems, context, false);
7097 : 20 : appendStringInfoChar(buf, ')');
7098 : :
7099 : : /* Add a WHERE clause (for partial indexes) if given */
7100 [ + + ]: 20 : if (confl->arbiterWhere != NULL)
7101 : : {
7102 : : bool save_varprefix;
7103 : :
7104 : : /*
7105 : : * Force non-prefixing of Vars, since parser assumes that they
7106 : : * belong to target relation. WHERE clause does not use
7107 : : * InferenceElem, so this is separately required.
7108 : : */
3924 tgl@sss.pgh.pa.us 7109 : 8 : save_varprefix = context->varprefix;
7110 : 8 : context->varprefix = false;
7111 : :
4188 andres@anarazel.de 7112 : 8 : appendContextKeyword(context, " WHERE ",
7113 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
7114 : 8 : get_rule_expr(confl->arbiterWhere, context, false);
7115 : :
3924 tgl@sss.pgh.pa.us 7116 : 8 : context->varprefix = save_varprefix;
7117 : : }
7118 : : }
3830 7119 [ - + ]: 4 : else if (OidIsValid(confl->constraint))
7120 : : {
4184 bruce@momjian.us 7121 :UBC 0 : char *constraint = get_constraint_name(confl->constraint);
7122 : :
3830 tgl@sss.pgh.pa.us 7123 [ # # ]: 0 : if (!constraint)
7124 [ # # ]: 0 : elog(ERROR, "cache lookup failed for constraint %u",
7125 : : confl->constraint);
4188 andres@anarazel.de 7126 : 0 : appendStringInfo(buf, " ON CONSTRAINT %s",
7127 : : quote_identifier(constraint));
7128 : : }
7129 : :
4199 andres@anarazel.de 7130 [ + + ]:CBC 24 : if (confl->action == ONCONFLICT_NOTHING)
7131 : : {
4188 7132 : 12 : appendStringInfoString(buf, " DO NOTHING");
7133 : : }
266 dean.a.rasheed@gmail 7134 [ + + ]: 12 : else if (confl->action == ONCONFLICT_UPDATE)
7135 : : {
4188 andres@anarazel.de 7136 : 8 : appendStringInfoString(buf, " DO UPDATE SET ");
7137 : : /* Deparse targetlist */
4199 7138 : 8 : get_update_query_targetlist_def(query, confl->onConflictSet,
7139 : : context, rte);
7140 : :
7141 : : /* Add a WHERE clause if given */
7142 [ + - ]: 8 : if (confl->onConflictWhere != NULL)
7143 : : {
7144 : 8 : appendContextKeyword(context, " WHERE ",
7145 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
7146 : 8 : get_rule_expr(confl->onConflictWhere, context, false);
7147 : : }
7148 : : }
7149 : : else
7150 : : {
266 dean.a.rasheed@gmail 7151 [ - + ]: 4 : Assert(confl->action == ONCONFLICT_SELECT);
7152 : 4 : appendStringInfoString(buf, " DO SELECT");
7153 : :
7154 : : /* Add FOR [KEY] UPDATE/SHARE clause if present */
7155 [ + - ]: 4 : if (confl->lockStrength != LCS_NONE)
7156 : 4 : appendStringInfoString(buf, get_lock_clause_strength(confl->lockStrength));
7157 : :
7158 : : /* Add a WHERE clause if given */
7159 [ + - ]: 4 : if (confl->onConflictWhere != NULL)
7160 : : {
7161 : 4 : appendContextKeyword(context, " WHERE ",
7162 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
7163 : 4 : get_rule_expr(confl->onConflictWhere, context, false);
7164 : : }
7165 : : }
7166 : : }
7167 : :
7168 : : /* Add RETURNING if present */
7390 tgl@sss.pgh.pa.us 7169 [ + + ]: 194 : if (query->returningList)
658 dean.a.rasheed@gmail 7170 : 51 : get_returning_clause(query, context);
10300 bruce@momjian.us 7171 : 194 : }
7172 : :
7173 : :
7174 : : /* ----------
7175 : : * get_update_query_def - Parse back an UPDATE parsetree
7176 : : * ----------
7177 : : */
7178 : : static void
798 tgl@sss.pgh.pa.us 7179 : 86 : get_update_query_def(Query *query, deparse_context *context)
7180 : : {
8103 bruce@momjian.us 7181 : 86 : StringInfo buf = context->buf;
7182 : : RangeTblEntry *rte;
7183 : :
7184 : : /* Insert the WITH clause if given */
5865 tgl@sss.pgh.pa.us 7185 : 86 : get_with_clause(query, context);
7186 : :
7187 : : /*
7188 : : * Start the query with UPDATE relname SET
7189 : : */
9867 7190 : 86 : rte = rt_fetch(query->resultRelation, query->rtable);
8952 7191 [ - + ]: 86 : Assert(rte->rtekind == RTE_RELATION);
8499 7192 [ + - ]: 86 : if (PRETTY_INDENT(context))
7193 : : {
8494 bruce@momjian.us 7194 : 86 : appendStringInfoChar(buf, ' ');
7195 : 86 : context->indentLevel += PRETTYINDENT_STD;
7196 : : }
6760 tgl@sss.pgh.pa.us 7197 : 172 : appendStringInfo(buf, "UPDATE %s%s",
9644 7198 [ + - ]: 86 : only_marker(rte),
7199 : : generate_relation_name(rte->relid, NIL));
7200 : :
7201 : : /*
7202 : : * Print the relation alias, if needed. Always write AS, since a bare
7203 : : * alias named "set" would be read as the SET keyword.
7204 : : */
17 7205 : 86 : get_rte_alias(rte, query->resultRelation, true, context);
7206 : :
6760 7207 : 86 : appendStringInfoString(buf, " SET ");
7208 : :
7209 : : /* Deparse targetlist */
4199 andres@anarazel.de 7210 : 86 : get_update_query_targetlist_def(query, query->targetList, context, rte);
7211 : :
7212 : : /* Add the FROM clause if needed */
7213 : 86 : get_from_clause(query, " FROM ", context);
7214 : :
7215 : : /* Add a WHERE clause if given */
7216 [ + + ]: 86 : if (query->jointree->quals != NULL)
7217 : : {
7218 : 61 : appendContextKeyword(context, " WHERE ",
7219 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
7220 : 61 : get_rule_expr(query->jointree->quals, context, false);
7221 : : }
7222 : :
7223 : : /* Add RETURNING if present */
7224 [ + + ]: 86 : if (query->returningList)
658 dean.a.rasheed@gmail 7225 : 29 : get_returning_clause(query, context);
4199 andres@anarazel.de 7226 : 86 : }
7227 : :
7228 : :
7229 : : /* ----------
7230 : : * get_update_query_targetlist_def - Parse back an UPDATE targetlist
7231 : : * ----------
7232 : : */
7233 : : static void
7234 : 110 : get_update_query_targetlist_def(Query *query, List *targetList,
7235 : : deparse_context *context, RangeTblEntry *rte)
7236 : : {
7237 : 110 : StringInfo buf = context->buf;
7238 : : ListCell *l;
7239 : : ListCell *next_ma_cell;
7240 : : int remaining_ma_columns;
7241 : : const char *sep;
7242 : : SubLink *cur_ma_sublink;
7243 : : List *ma_sublinks;
7244 : :
7245 : : /*
7246 : : * Prepare to deal with MULTIEXPR assignments: collect the source SubLinks
7247 : : * into a list. We expect them to appear, in ID order, in resjunk tlist
7248 : : * entries.
7249 : : */
4523 tgl@sss.pgh.pa.us 7250 : 110 : ma_sublinks = NIL;
7251 [ + + ]: 110 : if (query->hasSubLinks) /* else there can't be any */
7252 : : {
4199 andres@anarazel.de 7253 [ + - + + : 28 : foreach(l, targetList)
+ + ]
7254 : : {
4523 tgl@sss.pgh.pa.us 7255 : 20 : TargetEntry *tle = (TargetEntry *) lfirst(l);
7256 : :
7257 [ + + + - ]: 20 : if (tle->resjunk && IsA(tle->expr, SubLink))
7258 : : {
7259 : 4 : SubLink *sl = (SubLink *) tle->expr;
7260 : :
7261 [ + - ]: 4 : if (sl->subLinkType == MULTIEXPR_SUBLINK)
7262 : : {
7263 : 4 : ma_sublinks = lappend(ma_sublinks, sl);
7264 [ - + ]: 4 : Assert(sl->subLinkId == list_length(ma_sublinks));
7265 : : }
7266 : : }
7267 : : }
7268 : : }
7269 : 110 : next_ma_cell = list_head(ma_sublinks);
7270 : 110 : cur_ma_sublink = NULL;
7271 : 110 : remaining_ma_columns = 0;
7272 : :
7273 : : /* Add the comma separated list of 'attname = value' */
10292 bruce@momjian.us 7274 : 110 : sep = "";
4199 andres@anarazel.de 7275 [ + - + + : 282 : foreach(l, targetList)
+ + ]
7276 : : {
9533 tgl@sss.pgh.pa.us 7277 : 172 : TargetEntry *tle = (TargetEntry *) lfirst(l);
7278 : : Node *expr;
7279 : :
7883 7280 [ + + ]: 172 : if (tle->resjunk)
9533 7281 : 4 : continue; /* ignore junk entries */
7282 : :
7283 : : /* Emit separator (OK whether we're in multiassignment or not) */
7859 neilc@samurai.com 7284 : 168 : appendStringInfoString(buf, sep);
10292 bruce@momjian.us 7285 : 168 : sep = ", ";
7286 : :
7287 : : /*
7288 : : * Check to see if we're starting a multiassignment group: if so,
7289 : : * output a left paren.
7290 : : */
4523 tgl@sss.pgh.pa.us 7291 [ + + + - ]: 168 : if (next_ma_cell != NULL && cur_ma_sublink == NULL)
7292 : : {
7293 : : /*
7294 : : * We must dig down into the expr to see if it's a PARAM_MULTIEXPR
7295 : : * Param. That could be buried under FieldStores and
7296 : : * SubscriptingRefs and CoerceToDomains (cf processIndirection()),
7297 : : * and underneath those there could be an implicit type coercion.
7298 : : * Because we would ignore implicit type coercions anyway, we
7299 : : * don't need to be as careful as processIndirection() is about
7300 : : * descending past implicit CoerceToDomains.
7301 : : */
7302 : 4 : expr = (Node *) tle->expr;
7303 [ + - ]: 8 : while (expr)
7304 : : {
7305 [ - + ]: 8 : if (IsA(expr, FieldStore))
7306 : : {
4523 tgl@sss.pgh.pa.us 7307 :UBC 0 : FieldStore *fstore = (FieldStore *) expr;
7308 : :
7309 : 0 : expr = (Node *) linitial(fstore->newvals);
7310 : : }
2834 alvherre@alvh.no-ip. 7311 [ + + ]:CBC 8 : else if (IsA(expr, SubscriptingRef))
7312 : : {
7313 : 4 : SubscriptingRef *sbsref = (SubscriptingRef *) expr;
7314 : :
7315 [ - + ]: 4 : if (sbsref->refassgnexpr == NULL)
4523 tgl@sss.pgh.pa.us 7316 :UBC 0 : break;
7317 : :
2834 alvherre@alvh.no-ip. 7318 :CBC 4 : expr = (Node *) sbsref->refassgnexpr;
7319 : : }
3403 tgl@sss.pgh.pa.us 7320 [ - + ]: 4 : else if (IsA(expr, CoerceToDomain))
7321 : : {
3403 tgl@sss.pgh.pa.us 7322 :UBC 0 : CoerceToDomain *cdomain = (CoerceToDomain *) expr;
7323 : :
7324 [ # # ]: 0 : if (cdomain->coercionformat != COERCE_IMPLICIT_CAST)
7325 : 0 : break;
7326 : 0 : expr = (Node *) cdomain->arg;
7327 : : }
7328 : : else
4523 tgl@sss.pgh.pa.us 7329 :CBC 4 : break;
7330 : : }
7331 : 4 : expr = strip_implicit_coercions(expr);
7332 : :
7333 [ + - + - ]: 4 : if (expr && IsA(expr, Param) &&
7334 [ + - ]: 4 : ((Param *) expr)->paramkind == PARAM_MULTIEXPR)
7335 : : {
7336 : 4 : cur_ma_sublink = (SubLink *) lfirst(next_ma_cell);
2670 7337 : 4 : next_ma_cell = lnext(ma_sublinks, next_ma_cell);
2471 alvherre@alvh.no-ip. 7338 : 4 : remaining_ma_columns = count_nonjunk_tlist_entries(((Query *) cur_ma_sublink->subselect)->targetList);
4523 tgl@sss.pgh.pa.us 7339 [ - + ]: 4 : Assert(((Param *) expr)->paramid ==
7340 : : ((cur_ma_sublink->subLinkId << 16) | 1));
7341 : 4 : appendStringInfoChar(buf, '(');
7342 : : }
7343 : : }
7344 : :
7345 : : /*
7346 : : * Put out name of target column; look in the catalogs, not at
7347 : : * tle->resname, since resname will fail to track RENAME.
7348 : : */
8184 7349 : 168 : appendStringInfoString(buf,
3188 alvherre@alvh.no-ip. 7350 : 168 : quote_identifier(get_attname(rte->relid,
7351 : 168 : tle->resno,
7352 : : false)));
7353 : :
7354 : : /*
7355 : : * Print any indirection needed (subfields or subscripts), and strip
7356 : : * off the top-level nodes representing the indirection assignments.
7357 : : */
3746 tgl@sss.pgh.pa.us 7358 : 168 : expr = processIndirection((Node *) tle->expr, context);
7359 : :
7360 : : /*
7361 : : * If we're in a multiassignment, skip printing anything more, unless
7362 : : * this is the last column; in which case, what we print should be the
7363 : : * sublink, not the Param.
7364 : : */
4523 7365 [ + + ]: 168 : if (cur_ma_sublink != NULL)
7366 : : {
7367 [ + + ]: 12 : if (--remaining_ma_columns > 0)
7368 : 8 : continue; /* not the last column of multiassignment */
7369 : 4 : appendStringInfoChar(buf, ')');
7370 : 4 : expr = (Node *) cur_ma_sublink;
7371 : 4 : cur_ma_sublink = NULL;
7372 : : }
7373 : :
4753 rhaas@postgresql.org 7374 : 160 : appendStringInfoString(buf, " = ");
7375 : :
8184 tgl@sss.pgh.pa.us 7376 : 160 : get_rule_expr(expr, context, false);
7377 : : }
10300 bruce@momjian.us 7378 : 110 : }
7379 : :
7380 : :
7381 : : /* ----------
7382 : : * get_delete_query_def - Parse back a DELETE parsetree
7383 : : * ----------
7384 : : */
7385 : : static void
798 tgl@sss.pgh.pa.us 7386 : 39 : get_delete_query_def(Query *query, deparse_context *context)
7387 : : {
9895 7388 : 39 : StringInfo buf = context->buf;
7389 : : RangeTblEntry *rte;
7390 : :
7391 : : /* Insert the WITH clause if given */
5865 7392 : 39 : get_with_clause(query, context);
7393 : :
7394 : : /*
7395 : : * Start the query with DELETE FROM relname
7396 : : */
9867 7397 : 39 : rte = rt_fetch(query->resultRelation, query->rtable);
8952 7398 [ - + ]: 39 : Assert(rte->rtekind == RTE_RELATION);
8499 7399 [ + - ]: 39 : if (PRETTY_INDENT(context))
7400 : : {
8494 bruce@momjian.us 7401 : 39 : appendStringInfoChar(buf, ' ');
6760 tgl@sss.pgh.pa.us 7402 : 39 : context->indentLevel += PRETTYINDENT_STD;
7403 : : }
9867 7404 : 78 : appendStringInfo(buf, "DELETE FROM %s%s",
9644 7405 [ + - ]: 39 : only_marker(rte),
7406 : : generate_relation_name(rte->relid, NIL));
7407 : :
7408 : : /*
7409 : : * Print the relation alias, if needed. Always write AS, since a bare
7410 : : * alias named "set" would be read as the SET keyword.
7411 : : */
17 7412 : 39 : get_rte_alias(rte, query->resultRelation, true, context);
7413 : :
7414 : : /* Add the USING clause if given */
7766 7415 : 39 : get_from_clause(query, " USING ", context);
7416 : :
7417 : : /* Add a WHERE clause if given */
9533 7418 [ + - ]: 39 : if (query->jointree->quals != NULL)
7419 : : {
8499 7420 : 39 : appendContextKeyword(context, " WHERE ",
7421 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
8813 7422 : 39 : get_rule_expr(query->jointree->quals, context, false);
7423 : : }
7424 : :
7425 : : /* Add RETURNING if present */
7390 7426 [ + + ]: 39 : if (query->returningList)
658 dean.a.rasheed@gmail 7427 : 9 : get_returning_clause(query, context);
10300 bruce@momjian.us 7428 : 39 : }
7429 : :
7430 : :
7431 : : /* ----------
7432 : : * get_merge_query_def - Parse back a MERGE parsetree
7433 : : * ----------
7434 : : */
7435 : : static void
798 tgl@sss.pgh.pa.us 7436 : 8 : get_merge_query_def(Query *query, deparse_context *context)
7437 : : {
1278 7438 : 8 : StringInfo buf = context->buf;
7439 : : RangeTblEntry *rte;
7440 : : ListCell *lc;
7441 : : bool haveNotMatchedBySource;
7442 : :
7443 : : /* Insert the WITH clause if given */
7444 : 8 : get_with_clause(query, context);
7445 : :
7446 : : /*
7447 : : * Start the query with MERGE INTO relname
7448 : : */
7449 : 8 : rte = rt_fetch(query->resultRelation, query->rtable);
7450 [ - + ]: 8 : Assert(rte->rtekind == RTE_RELATION);
7451 [ + - ]: 8 : if (PRETTY_INDENT(context))
7452 : : {
7453 : 8 : appendStringInfoChar(buf, ' ');
7454 : 8 : context->indentLevel += PRETTYINDENT_STD;
7455 : : }
7456 : 16 : appendStringInfo(buf, "MERGE INTO %s%s",
7457 [ + - ]: 8 : only_marker(rte),
7458 : : generate_relation_name(rte->relid, NIL));
7459 : :
7460 : : /*
7461 : : * Print the relation alias, if needed. Always write AS, since a bare
7462 : : * alias named "set" would be read as the SET keyword.
7463 : : */
17 7464 : 8 : get_rte_alias(rte, query->resultRelation, true, context);
7465 : :
7466 : : /* Print the source relation and join clause */
1278 7467 : 8 : get_from_clause(query, " USING ", context);
7468 : 8 : appendContextKeyword(context, " ON ",
7469 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 2);
950 dean.a.rasheed@gmail 7470 : 8 : get_rule_expr(query->mergeJoinCondition, context, false);
7471 : :
7472 : : /*
7473 : : * Test for any NOT MATCHED BY SOURCE actions. If there are none, then
7474 : : * any NOT MATCHED BY TARGET actions are output as "WHEN NOT MATCHED", per
7475 : : * SQL standard. Otherwise, we have a non-SQL-standard query, so output
7476 : : * "BY SOURCE" / "BY TARGET" qualifiers for all NOT MATCHED actions, to be
7477 : : * more explicit.
7478 : : */
7479 : 8 : haveNotMatchedBySource = false;
7480 [ + - + + : 56 : foreach(lc, query->mergeActionList)
+ + ]
7481 : : {
7482 : 52 : MergeAction *action = lfirst_node(MergeAction, lc);
7483 : :
7484 [ + + ]: 52 : if (action->matchKind == MERGE_WHEN_NOT_MATCHED_BY_SOURCE)
7485 : : {
7486 : 4 : haveNotMatchedBySource = true;
7487 : 4 : break;
7488 : : }
7489 : : }
7490 : :
7491 : : /* Print each merge action */
1278 tgl@sss.pgh.pa.us 7492 [ + - + + : 60 : foreach(lc, query->mergeActionList)
+ + ]
7493 : : {
7494 : 52 : MergeAction *action = lfirst_node(MergeAction, lc);
7495 : :
7496 : 52 : appendContextKeyword(context, " WHEN ",
7497 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 2);
950 dean.a.rasheed@gmail 7498 [ + + + - ]: 52 : switch (action->matchKind)
7499 : : {
7500 : 24 : case MERGE_WHEN_MATCHED:
939 drowley@postgresql.o 7501 : 24 : appendStringInfoString(buf, "MATCHED");
950 dean.a.rasheed@gmail 7502 : 24 : break;
7503 : 4 : case MERGE_WHEN_NOT_MATCHED_BY_SOURCE:
939 drowley@postgresql.o 7504 : 4 : appendStringInfoString(buf, "NOT MATCHED BY SOURCE");
950 dean.a.rasheed@gmail 7505 : 4 : break;
7506 : 24 : case MERGE_WHEN_NOT_MATCHED_BY_TARGET:
7507 [ + + ]: 24 : if (haveNotMatchedBySource)
939 drowley@postgresql.o 7508 : 4 : appendStringInfoString(buf, "NOT MATCHED BY TARGET");
7509 : : else
7510 : 20 : appendStringInfoString(buf, "NOT MATCHED");
950 dean.a.rasheed@gmail 7511 : 24 : break;
950 dean.a.rasheed@gmail 7512 :UBC 0 : default:
7513 [ # # ]: 0 : elog(ERROR, "unrecognized matchKind: %d",
7514 : : (int) action->matchKind);
7515 : : }
7516 : :
1278 tgl@sss.pgh.pa.us 7517 [ + + ]:CBC 52 : if (action->qual)
7518 : : {
7519 : 32 : appendContextKeyword(context, " AND ",
7520 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 3);
7521 : 32 : get_rule_expr(action->qual, context, false);
7522 : : }
7523 : 52 : appendContextKeyword(context, " THEN ",
7524 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 3);
7525 : :
7526 [ + + ]: 52 : if (action->commandType == CMD_INSERT)
7527 : : {
7528 : : /* This generally matches get_insert_query_def() */
7529 : 24 : List *strippedexprs = NIL;
7530 : 24 : const char *sep = "";
7531 : : ListCell *lc2;
7532 : :
7533 : 24 : appendStringInfoString(buf, "INSERT");
7534 : :
7535 [ + + ]: 24 : if (action->targetList)
7536 : 20 : appendStringInfoString(buf, " (");
7537 [ + + + + : 68 : foreach(lc2, action->targetList)
+ + ]
7538 : : {
7539 : 44 : TargetEntry *tle = (TargetEntry *) lfirst(lc2);
7540 : :
7541 [ - + ]: 44 : Assert(!tle->resjunk);
7542 : :
7543 : 44 : appendStringInfoString(buf, sep);
7544 : 44 : sep = ", ";
7545 : :
7546 : 44 : appendStringInfoString(buf,
7547 : 44 : quote_identifier(get_attname(rte->relid,
7548 : 44 : tle->resno,
7549 : : false)));
7550 : 44 : strippedexprs = lappend(strippedexprs,
7551 : 44 : processIndirection((Node *) tle->expr,
7552 : : context));
7553 : : }
7554 [ + + ]: 24 : if (action->targetList)
7555 : 20 : appendStringInfoChar(buf, ')');
7556 : :
7557 [ + + ]: 24 : if (action->override)
7558 : : {
7559 [ - + ]: 4 : if (action->override == OVERRIDING_SYSTEM_VALUE)
1278 tgl@sss.pgh.pa.us 7560 :UBC 0 : appendStringInfoString(buf, " OVERRIDING SYSTEM VALUE");
1278 tgl@sss.pgh.pa.us 7561 [ + - ]:CBC 4 : else if (action->override == OVERRIDING_USER_VALUE)
7562 : 4 : appendStringInfoString(buf, " OVERRIDING USER VALUE");
7563 : : }
7564 : :
7565 [ + + ]: 24 : if (strippedexprs)
7566 : : {
7567 : 20 : appendContextKeyword(context, " VALUES (",
7568 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 4);
7569 : 20 : get_rule_list_toplevel(strippedexprs, context, false);
7570 : 20 : appendStringInfoChar(buf, ')');
7571 : : }
7572 : : else
7573 : 4 : appendStringInfoString(buf, " DEFAULT VALUES");
7574 : : }
7575 [ + + ]: 28 : else if (action->commandType == CMD_UPDATE)
7576 : : {
7577 : 16 : appendStringInfoString(buf, "UPDATE SET ");
7578 : 16 : get_update_query_targetlist_def(query, action->targetList,
7579 : : context, rte);
7580 : : }
7581 [ + + ]: 12 : else if (action->commandType == CMD_DELETE)
7582 : 8 : appendStringInfoString(buf, "DELETE");
7583 [ + - ]: 4 : else if (action->commandType == CMD_NOTHING)
7584 : 4 : appendStringInfoString(buf, "DO NOTHING");
7585 : : }
7586 : :
7587 : : /* Add RETURNING if present */
963 dean.a.rasheed@gmail 7588 [ + + ]: 8 : if (query->returningList)
658 7589 : 4 : get_returning_clause(query, context);
1278 tgl@sss.pgh.pa.us 7590 : 8 : }
7591 : :
7592 : :
7593 : : /* ----------
7594 : : * get_utility_query_def - Parse back a UTILITY parsetree
7595 : : * ----------
7596 : : */
7597 : : static void
9437 7598 : 9 : get_utility_query_def(Query *query, deparse_context *context)
7599 : : {
7600 : 9 : StringInfo buf = context->buf;
7601 : :
7602 [ + - + - ]: 9 : if (query->utilityStmt && IsA(query->utilityStmt, NotifyStmt))
7603 : 9 : {
7604 : 9 : NotifyStmt *stmt = (NotifyStmt *) query->utilityStmt;
7605 : :
8499 7606 : 9 : appendContextKeyword(context, "",
7607 : : 0, PRETTYINDENT_STD, 1);
8994 7608 : 9 : appendStringInfo(buf, "NOTIFY %s",
6639 7609 : 9 : quote_identifier(stmt->conditionname));
6106 7610 [ - + ]: 9 : if (stmt->payload)
7611 : : {
6106 tgl@sss.pgh.pa.us 7612 :UBC 0 : appendStringInfoString(buf, ", ");
7613 : 0 : simple_quote_literal(buf, stmt->payload);
7614 : : }
7615 : : }
7616 : : else
7617 : : {
7618 : : /* Currently only NOTIFY utility commands can appear in rules */
8502 7619 [ # # ]: 0 : elog(ERROR, "unexpected utility statement type");
7620 : : }
9437 tgl@sss.pgh.pa.us 7621 :CBC 9 : }
7622 : :
7623 : : /*
7624 : : * Display a Var appropriately.
7625 : : *
7626 : : * In some cases (currently only when recursing into an unnamed join)
7627 : : * the Var's varlevelsup has to be interpreted with respect to a context
7628 : : * above the current one; levelsup indicates the offset.
7629 : : *
7630 : : * If istoplevel is true, the Var is at the top level of a SELECT's
7631 : : * targetlist, which means we need special treatment of whole-row Vars.
7632 : : * Instead of the normal "tab.*", we'll print "tab.*::typename", which is a
7633 : : * dirty hack to prevent "tab.*" from being expanded into multiple columns.
7634 : : * (The parser will strip the useless coercion, so no inefficiency is added in
7635 : : * dump and reload.) We used to print just "tab" in such cases, but that is
7636 : : * ambiguous and will yield the wrong result if "tab" is also a plain column
7637 : : * name in the query.
7638 : : *
7639 : : * Returns the attname of the Var, or NULL if the Var has no attname (because
7640 : : * it is a whole-row Var or a subplan output reference).
7641 : : */
7642 : : static char *
5305 7643 : 134968 : get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
7644 : : {
7195 7645 : 134968 : StringInfo buf = context->buf;
7646 : : RangeTblEntry *rte;
7647 : : AttrNumber attnum;
7648 : : int netlevelsup;
7649 : : deparse_namespace *dpns;
7650 : : int varno;
7651 : : AttrNumber varattno;
7652 : : deparse_columns *colinfo;
7653 : : char *refname;
7654 : : char *attname;
7655 : : bool need_prefix;
7656 : :
7657 : : /* Find appropriate nesting depth */
7966 7658 : 134968 : netlevelsup = var->varlevelsup + levelsup;
7659 [ - + ]: 134968 : if (netlevelsup >= list_length(context->namespaces))
7966 tgl@sss.pgh.pa.us 7660 [ # # ]:UBC 0 : elog(ERROR, "bogus varlevelsup: %d offset %d",
7661 : : var->varlevelsup, levelsup);
8198 tgl@sss.pgh.pa.us 7662 :CBC 134968 : dpns = (deparse_namespace *) list_nth(context->namespaces,
7663 : : netlevelsup);
7664 : :
7665 : : /*
7666 : : * If we have a syntactic referent for the Var, and we're working from a
7667 : : * parse tree, prefer to use the syntactic referent. Otherwise, fall back
7668 : : * on the semantic referent. (Forcing use of the semantic referent when
7669 : : * printing plan trees is a design choice that's perhaps more motivated by
7670 : : * backwards compatibility than anything else. But it does have the
7671 : : * advantage of making plans more explicit.)
7672 : : */
2492 7673 [ + + + + ]: 134968 : if (var->varnosyn > 0 && dpns->plan == NULL)
7674 : : {
7675 : 24550 : varno = var->varnosyn;
7676 : 24550 : varattno = var->varattnosyn;
7677 : : }
7678 : : else
7679 : : {
7680 : 110418 : varno = var->varno;
7681 : 110418 : varattno = var->varattno;
7682 : : }
7683 : :
7684 : : /*
7685 : : * Try to find the relevant RTE in this rtable. In a plan tree, it's
7686 : : * likely that varno is OUTER_VAR or INNER_VAR, in which case we must dig
7687 : : * down into the subplans, or INDEX_VAR, which is resolved similarly. Also
7688 : : * find the aliases previously assigned for this RTE.
7689 : : */
7690 [ + + + - ]: 134968 : if (varno >= 1 && varno <= list_length(dpns->rtable))
7691 : : {
7692 : : /*
7693 : : * We might have been asked to map child Vars to some parent relation.
7694 : : */
2521 7695 [ + + + + ]: 96402 : if (context->appendparents && dpns->appendrels)
7696 : : {
1877 7697 : 2577 : int pvarno = varno;
2521 7698 : 2577 : AttrNumber pvarattno = varattno;
7699 : 2577 : AppendRelInfo *appinfo = dpns->appendrels[pvarno];
7700 : 2577 : bool found = false;
7701 : :
7702 : : /* Only map up to inheritance parents, not UNION ALL appendrels */
7703 [ + + ]: 5196 : while (appinfo &&
7704 : 2850 : rt_fetch(appinfo->parent_relid,
7705 [ + + ]: 2850 : dpns->rtable)->rtekind == RTE_RELATION)
7706 : : {
7707 : 2619 : found = false;
7708 [ + + ]: 2619 : if (pvarattno > 0) /* system columns stay as-is */
7709 : : {
7710 [ - + ]: 2467 : if (pvarattno > appinfo->num_child_cols)
2521 tgl@sss.pgh.pa.us 7711 :UBC 0 : break; /* safety check */
2521 tgl@sss.pgh.pa.us 7712 :CBC 2467 : pvarattno = appinfo->parent_colnos[pvarattno - 1];
7713 [ - + ]: 2467 : if (pvarattno == 0)
2521 tgl@sss.pgh.pa.us 7714 :UBC 0 : break; /* Var is local to child */
7715 : : }
7716 : :
2521 tgl@sss.pgh.pa.us 7717 :CBC 2619 : pvarno = appinfo->parent_relid;
7718 : 2619 : found = true;
7719 : :
7720 : : /* If the parent is itself a child, continue up. */
7721 [ + - - + ]: 2619 : Assert(pvarno > 0 && pvarno <= list_length(dpns->rtable));
7722 : 2619 : appinfo = dpns->appendrels[pvarno];
7723 : : }
7724 : :
7725 : : /*
7726 : : * If we found an ancestral rel, and that rel is included in
7727 : : * appendparents, print that column not the original one.
7728 : : */
7729 [ + + + + ]: 2577 : if (found && bms_is_member(pvarno, context->appendparents))
7730 : : {
7731 : 2094 : varno = pvarno;
7732 : 2094 : varattno = pvarattno;
7733 : : }
7734 : : }
7735 : :
7736 : 96402 : rte = rt_fetch(varno, dpns->rtable);
7737 : :
7738 : : /* might be returning old/new column value */
658 dean.a.rasheed@gmail 7739 [ + + ]: 96402 : if (var->varreturningtype == VAR_RETURNING_OLD)
7740 : 298 : refname = dpns->ret_old_alias;
7741 [ + + ]: 96104 : else if (var->varreturningtype == VAR_RETURNING_NEW)
7742 : 281 : refname = dpns->ret_new_alias;
7743 : : else
7744 : 95823 : refname = (char *) list_nth(dpns->rtable_names, varno - 1);
7745 : :
2521 tgl@sss.pgh.pa.us 7746 : 96402 : colinfo = deparse_columns_fetch(varno, dpns);
7747 : 96402 : attnum = varattno;
7748 : : }
7749 : : else
7750 : : {
7751 : 38566 : resolve_special_varno((Node *) var, context,
7752 : : get_special_variable, NULL);
3844 rhaas@postgresql.org 7753 : 38566 : return NULL;
7754 : : }
7755 : :
7756 : : /*
7757 : : * The planner will sometimes emit Vars referencing resjunk elements of a
7758 : : * subquery's target list (this is currently only possible if it chooses
7759 : : * to generate a "physical tlist" for a SubqueryScan or CteScan node).
7760 : : * Although we prefer to print subquery-referencing Vars using the
7761 : : * subquery's alias, that's not possible for resjunk items since they have
7762 : : * no alias. So in that case, drill down to the subplan and print the
7763 : : * contents of the referenced tlist item. This works because in a plan
7764 : : * tree, such Vars can only occur in a SubqueryScan or CteScan node, and
7765 : : * we'll have set dpns->inner_plan to reference the child plan node.
7766 : : */
5963 tgl@sss.pgh.pa.us 7767 [ + + + + : 99649 : if ((rte->rtekind == RTE_SUBQUERY || rte->rtekind == RTE_CTE) &&
+ + ]
7768 : 3247 : attnum > list_length(rte->eref->colnames) &&
2521 7769 [ + - ]: 1 : dpns->inner_plan)
7770 : : {
7771 : : TargetEntry *tle;
7772 : : deparse_namespace save_dpns;
7773 : :
7774 : 1 : tle = get_tle_by_resno(dpns->inner_tlist, attnum);
5963 7775 [ - + ]: 1 : if (!tle)
3779 tgl@sss.pgh.pa.us 7776 [ # # ]:UBC 0 : elog(ERROR, "invalid attnum %d for relation \"%s\"",
7777 : : attnum, rte->eref->aliasname);
7778 : :
5963 tgl@sss.pgh.pa.us 7779 [ - + ]:CBC 1 : Assert(netlevelsup == 0);
2521 7780 : 1 : push_child_plan(dpns, dpns->inner_plan, &save_dpns);
7781 : :
7782 : : /*
7783 : : * Force parentheses because our caller probably assumed a Var is a
7784 : : * simple expression.
7785 : : */
5963 7786 [ - + ]: 1 : if (!IsA(tle->expr, Var))
5963 tgl@sss.pgh.pa.us 7787 :UBC 0 : appendStringInfoChar(buf, '(');
5963 tgl@sss.pgh.pa.us 7788 :CBC 1 : get_rule_expr((Node *) tle->expr, context, true);
7789 [ - + ]: 1 : if (!IsA(tle->expr, Var))
5963 tgl@sss.pgh.pa.us 7790 :UBC 0 : appendStringInfoChar(buf, ')');
7791 : :
5959 tgl@sss.pgh.pa.us 7792 :CBC 1 : pop_child_plan(dpns, &save_dpns);
5963 7793 : 1 : return NULL;
7794 : : }
7795 : :
7796 : : /*
7797 : : * If it's an unnamed join, look at the expansion of the alias variable.
7798 : : * If it's a simple reference to one of the input vars, then recursively
7799 : : * print the name of that var instead. When it's not a simple reference,
7800 : : * we have to just print the unqualified join column name. (This can only
7801 : : * happen with "dangerous" merged columns in a JOIN USING; we took pains
7802 : : * previously to make the unqualified column name unique in such cases.)
7803 : : *
7804 : : * This wouldn't work in decompiling plan trees, because we don't store
7805 : : * joinaliasvars lists after planning; but a plan tree should never
7806 : : * contain a join alias variable.
7807 : : */
5158 7808 [ + + + + ]: 96401 : if (rte->rtekind == RTE_JOIN && rte->alias == NULL)
7809 : : {
7810 [ - + ]: 72 : if (rte->joinaliasvars == NIL)
5158 tgl@sss.pgh.pa.us 7811 [ # # ]:UBC 0 : elog(ERROR, "cannot decompile join alias var in plan tree");
5158 tgl@sss.pgh.pa.us 7812 [ + - ]:CBC 72 : if (attnum > 0)
7813 : : {
7814 : : Var *aliasvar;
7815 : :
7816 : 72 : aliasvar = (Var *) list_nth(rte->joinaliasvars, attnum - 1);
7817 : : /* we intentionally don't strip implicit coercions here */
4853 7818 [ + - - + ]: 72 : if (aliasvar && IsA(aliasvar, Var))
7819 : : {
5158 tgl@sss.pgh.pa.us 7820 :UBC 0 : return get_variable(aliasvar, var->varlevelsup + levelsup,
7821 : : istoplevel, context);
7822 : : }
7823 : : }
7824 : :
7825 : : /*
7826 : : * Unnamed join has no refname. (Note: since it's unnamed, there is
7827 : : * no way the user could have referenced it to create a whole-row Var
7828 : : * for it. So we don't have to cover that case below.)
7829 : : */
5158 tgl@sss.pgh.pa.us 7830 [ - + ]:CBC 72 : Assert(refname == NULL);
7831 : : }
7832 : :
7516 7833 [ + + ]: 96401 : if (attnum == InvalidAttrNumber)
7195 7834 : 752 : attname = NULL;
5057 7835 [ + + ]: 95649 : else if (attnum > 0)
7836 : : {
7837 : : /* Get column name to use from the colinfo struct */
3779 7838 [ - + ]: 94421 : if (attnum > colinfo->num_cols)
3779 tgl@sss.pgh.pa.us 7839 [ # # ]:UBC 0 : elog(ERROR, "invalid attnum %d for relation \"%s\"",
7840 : : attnum, rte->eref->aliasname);
5057 tgl@sss.pgh.pa.us 7841 :CBC 94421 : attname = colinfo->colnames[attnum - 1];
7842 : :
7843 : : /*
7844 : : * If we find a Var referencing a dropped column, it seems better to
7845 : : * print something (anything) than to fail. In general this should
7846 : : * not happen, but it used to be possible for some cases involving
7847 : : * functions returning named composite types, and perhaps there are
7848 : : * still bugs out there.
7849 : : */
1568 7850 [ + + ]: 94421 : if (attname == NULL)
7851 : 4 : attname = "?dropped?column?";
7852 : : }
7853 : : else
7854 : : {
7855 : : /* System column - name is fixed, get it from the catalog */
7195 7856 : 1228 : attname = get_rte_attribute_name(rte, attnum);
7857 : : }
7858 : :
658 dean.a.rasheed@gmail 7859 [ + + + + ]: 140296 : need_prefix = (context->varprefix || attname == NULL ||
7860 [ + + ]: 43895 : var->varreturningtype != VAR_RETURNING_DEFAULT);
7861 : :
7862 : : /*
7863 : : * If we're considering a plain Var in an ORDER BY (but not GROUP BY)
7864 : : * clause, we may need to add a table-name prefix to prevent
7865 : : * findTargetlistEntrySQL92 from misinterpreting the name as an
7866 : : * output-column name. To avoid cluttering the output with unnecessary
7867 : : * prefixes, do so only if there is a name match to a SELECT tlist item
7868 : : * that is different from the Var.
7869 : : */
798 tgl@sss.pgh.pa.us 7870 [ + + + + : 96401 : if (context->varInOrderBy && !context->inGroupBy && !need_prefix)
+ + ]
7871 : : {
7872 : 179 : int colno = 0;
7873 : :
7874 [ + + + + : 661 : foreach_node(TargetEntry, tle, context->targetList)
+ + ]
7875 : : {
7876 : : char *colname;
7877 : :
7878 [ - + ]: 311 : if (tle->resjunk)
798 tgl@sss.pgh.pa.us 7879 :UBC 0 : continue; /* ignore junk entries */
798 tgl@sss.pgh.pa.us 7880 :CBC 311 : colno++;
7881 : :
7882 : : /* This must match colname-choosing logic in get_target_list() */
7883 [ + - + - ]: 311 : if (context->resultDesc && colno <= context->resultDesc->natts)
7884 : 311 : colname = NameStr(TupleDescAttr(context->resultDesc,
7885 : : colno - 1)->attname);
7886 : : else
798 tgl@sss.pgh.pa.us 7887 :UBC 0 : colname = tle->resname;
7888 : :
798 tgl@sss.pgh.pa.us 7889 [ + - + + ]:CBC 311 : if (colname && strcmp(colname, attname) == 0 &&
7890 [ + + ]: 106 : !equal(var, tle->expr))
7891 : : {
7892 : 8 : need_prefix = true;
7893 : 8 : break;
7894 : : }
7895 : : }
7896 : : }
7897 : :
7898 [ + + + + ]: 96401 : if (refname && need_prefix)
7899 : : {
6209 7900 : 52455 : appendStringInfoString(buf, quote_identifier(refname));
5305 7901 : 52455 : appendStringInfoChar(buf, '.');
7902 : : }
7195 7903 [ + + ]: 96401 : if (attname)
7904 : 95649 : appendStringInfoString(buf, quote_identifier(attname));
7905 : : else
7906 : : {
7907 : 752 : appendStringInfoChar(buf, '*');
5305 7908 [ + + ]: 752 : if (istoplevel)
7909 : 56 : appendStringInfo(buf, "::%s",
7910 : : format_type_with_typemod(var->vartype,
7911 : : var->vartypmod));
7912 : : }
7913 : :
7195 7914 : 96401 : return attname;
7915 : : }
7916 : :
7917 : : /*
7918 : : * Deparse a Var which references OUTER_VAR, INNER_VAR, or INDEX_VAR. This
7919 : : * routine is actually a callback for resolve_special_varno, which handles
7920 : : * finding the correct TargetEntry. We get the expression contained in that
7921 : : * TargetEntry and just need to deparse it, a job we can throw back on
7922 : : * get_rule_expr.
7923 : : */
7924 : : static void
2521 7925 : 38566 : get_special_variable(Node *node, deparse_context *context, void *callback_arg)
7926 : : {
3844 rhaas@postgresql.org 7927 : 38566 : StringInfo buf = context->buf;
7928 : :
7929 : : /*
7930 : : * For a non-Var referent, force parentheses because our caller probably
7931 : : * assumed a Var is a simple expression.
7932 : : */
7933 [ + + ]: 38566 : if (!IsA(node, Var))
7934 : 4321 : appendStringInfoChar(buf, '(');
7935 : 38566 : get_rule_expr(node, context, true);
7936 [ + + ]: 38566 : if (!IsA(node, Var))
7937 : 4321 : appendStringInfoChar(buf, ')');
7938 : 38566 : }
7939 : :
7940 : : /*
7941 : : * Chase through plan references to special varnos (OUTER_VAR, INNER_VAR,
7942 : : * INDEX_VAR) until we find a real Var or some kind of non-Var node; then,
7943 : : * invoke the callback provided.
7944 : : */
7945 : : static void
2521 tgl@sss.pgh.pa.us 7946 : 108783 : resolve_special_varno(Node *node, deparse_context *context,
7947 : : rsv_callback callback, void *callback_arg)
7948 : : {
7949 : : Var *var;
7950 : : deparse_namespace *dpns;
7951 : :
7952 : : /* This function is recursive, so let's be paranoid. */
7953 : 108783 : check_stack_depth();
7954 : :
7955 : : /* If it's not a Var, invoke the callback. */
3844 rhaas@postgresql.org 7956 [ + + ]: 108783 : if (!IsA(node, Var))
7957 : : {
2521 tgl@sss.pgh.pa.us 7958 : 4909 : (*callback) (node, context, callback_arg);
3844 rhaas@postgresql.org 7959 : 4909 : return;
7960 : : }
7961 : :
7962 : : /* Find appropriate nesting depth */
7963 : 103874 : var = (Var *) node;
7964 : 103874 : dpns = (deparse_namespace *) list_nth(context->namespaces,
7965 : 103874 : var->varlevelsup);
7966 : :
7967 : : /*
7968 : : * If varno is special, recurse. (Don't worry about varnosyn; if we're
7969 : : * here, we already decided not to use that.)
7970 : : */
7971 [ + + + - ]: 103874 : if (var->varno == OUTER_VAR && dpns->outer_tlist)
7972 : : {
7973 : : TargetEntry *tle;
7974 : : deparse_namespace save_dpns;
7975 : : Bitmapset *save_appendparents;
7976 : :
7977 : 52934 : tle = get_tle_by_resno(dpns->outer_tlist, var->varattno);
7978 [ - + ]: 52934 : if (!tle)
3844 rhaas@postgresql.org 7979 [ # # ]:UBC 0 : elog(ERROR, "bogus varattno for OUTER_VAR var: %d", var->varattno);
7980 : :
7981 : : /*
7982 : : * If we're descending to the first child of an Append or MergeAppend,
7983 : : * update appendparents. This will affect deparsing of all Vars
7984 : : * appearing within the eventually-resolved subexpression.
7985 : : */
2521 tgl@sss.pgh.pa.us 7986 :CBC 52934 : save_appendparents = context->appendparents;
7987 : :
7988 [ + + ]: 52934 : if (IsA(dpns->plan, Append))
7989 : 3135 : context->appendparents = bms_union(context->appendparents,
7990 : 3135 : ((Append *) dpns->plan)->apprelids);
7991 [ + + ]: 49799 : else if (IsA(dpns->plan, MergeAppend))
7992 : 425 : context->appendparents = bms_union(context->appendparents,
7993 : 425 : ((MergeAppend *) dpns->plan)->apprelids);
7994 : :
7995 : 52934 : push_child_plan(dpns, dpns->outer_plan, &save_dpns);
7996 : 52934 : resolve_special_varno((Node *) tle->expr, context,
7997 : : callback, callback_arg);
3844 rhaas@postgresql.org 7998 : 52934 : pop_child_plan(dpns, &save_dpns);
2521 tgl@sss.pgh.pa.us 7999 : 52934 : context->appendparents = save_appendparents;
3844 rhaas@postgresql.org 8000 : 52934 : return;
8001 : : }
8002 [ + + + - ]: 50940 : else if (var->varno == INNER_VAR && dpns->inner_tlist)
8003 : : {
8004 : : TargetEntry *tle;
8005 : : deparse_namespace save_dpns;
8006 : :
8007 : 12948 : tle = get_tle_by_resno(dpns->inner_tlist, var->varattno);
8008 [ - + ]: 12948 : if (!tle)
3844 rhaas@postgresql.org 8009 [ # # ]:UBC 0 : elog(ERROR, "bogus varattno for INNER_VAR var: %d", var->varattno);
8010 : :
2521 tgl@sss.pgh.pa.us 8011 :CBC 12948 : push_child_plan(dpns, dpns->inner_plan, &save_dpns);
8012 : 12948 : resolve_special_varno((Node *) tle->expr, context,
8013 : : callback, callback_arg);
3844 rhaas@postgresql.org 8014 : 12948 : pop_child_plan(dpns, &save_dpns);
8015 : 12948 : return;
8016 : : }
8017 [ + + + - ]: 37992 : else if (var->varno == INDEX_VAR && dpns->index_tlist)
8018 : : {
8019 : : TargetEntry *tle;
8020 : :
8021 : 3747 : tle = get_tle_by_resno(dpns->index_tlist, var->varattno);
8022 [ - + ]: 3747 : if (!tle)
3844 rhaas@postgresql.org 8023 [ # # ]:UBC 0 : elog(ERROR, "bogus varattno for INDEX_VAR var: %d", var->varattno);
8024 : :
2521 tgl@sss.pgh.pa.us 8025 :CBC 3747 : resolve_special_varno((Node *) tle->expr, context,
8026 : : callback, callback_arg);
3844 rhaas@postgresql.org 8027 : 3747 : return;
8028 : : }
8029 [ + - - + ]: 34245 : else if (var->varno < 1 || var->varno > list_length(dpns->rtable))
3844 rhaas@postgresql.org 8030 [ # # ]:UBC 0 : elog(ERROR, "bogus varno: %d", var->varno);
8031 : :
8032 : : /* Not special. Just invoke the callback. */
2521 tgl@sss.pgh.pa.us 8033 :CBC 34245 : (*callback) (node, context, callback_arg);
8034 : : }
8035 : :
8036 : : /*
8037 : : * Get the name of a field of an expression of composite type. The
8038 : : * expression is usually a Var, but we handle other cases too.
8039 : : *
8040 : : * levelsup is an extra offset to interpret the Var's varlevelsup correctly.
8041 : : *
8042 : : * This is fairly straightforward when the expression has a named composite
8043 : : * type; we need only look up the type in the catalogs. However, the type
8044 : : * could also be RECORD. Since no actual table or view column is allowed to
8045 : : * have type RECORD, a Var of type RECORD must refer to a JOIN or FUNCTION RTE
8046 : : * or to a subquery output. We drill down to find the ultimate defining
8047 : : * expression and attempt to infer the field name from it. We ereport if we
8048 : : * can't determine the name.
8049 : : *
8050 : : * Similarly, a PARAM of type RECORD has to refer to some expression of
8051 : : * a determinable composite type.
8052 : : */
8053 : : static const char *
7828 8054 : 1058 : get_name_for_var_field(Var *var, int fieldno,
8055 : : int levelsup, deparse_context *context)
8056 : : {
8057 : : RangeTblEntry *rte;
8058 : : AttrNumber attnum;
8059 : : int netlevelsup;
8060 : : deparse_namespace *dpns;
8061 : : int varno;
8062 : : AttrNumber varattno;
8063 : : TupleDesc tupleDesc;
8064 : : Node *expr;
8065 : :
8066 : : /*
8067 : : * If it's a RowExpr that was expanded from a whole-row Var, use the
8068 : : * column names attached to it. (We could let get_expr_result_tupdesc()
8069 : : * handle this, but it's much cheaper to just pull out the name we need.)
8070 : : */
6604 8071 [ + + ]: 1058 : if (IsA(var, RowExpr))
8072 : : {
6356 bruce@momjian.us 8073 : 24 : RowExpr *r = (RowExpr *) var;
8074 : :
6604 tgl@sss.pgh.pa.us 8075 [ + - + - ]: 24 : if (fieldno > 0 && fieldno <= list_length(r->colnames))
8076 : 24 : return strVal(list_nth(r->colnames, fieldno - 1));
8077 : : }
8078 : :
8079 : : /*
8080 : : * If it's a Param of type RECORD, try to find what the Param refers to.
8081 : : */
5538 8082 [ + + ]: 1034 : if (IsA(var, Param))
8083 : : {
8084 : 12 : Param *param = (Param *) var;
8085 : : ListCell *ancestor_cell;
8086 : :
8087 : 12 : expr = find_param_referent(param, context, &dpns, &ancestor_cell);
8088 [ + - ]: 12 : if (expr)
8089 : : {
8090 : : /* Found a match, so recurse to decipher the field name */
8091 : : deparse_namespace save_dpns;
8092 : : const char *result;
8093 : :
8094 : 12 : push_ancestor_plan(dpns, ancestor_cell, &save_dpns);
8095 : 12 : result = get_name_for_var_field((Var *) expr, fieldno,
8096 : : 0, context);
8097 : 12 : pop_ancestor_plan(dpns, &save_dpns);
8098 : 12 : return result;
8099 : : }
8100 : : }
8101 : :
8102 : : /*
8103 : : * If it's a Var of type RECORD, we have to find what the Var refers to;
8104 : : * if not, we can use get_expr_result_tupdesc().
8105 : : */
7195 8106 [ + + ]: 1022 : if (!IsA(var, Var) ||
8107 [ + + ]: 961 : var->vartype != RECORDOID)
8108 : : {
3297 8109 : 858 : tupleDesc = get_expr_result_tupdesc((Node *) var, false);
8110 : : /* Got the tupdesc, so we can extract the field name */
7195 8111 [ + - - + ]: 858 : Assert(fieldno >= 1 && fieldno <= tupleDesc->natts);
3364 andres@anarazel.de 8112 : 858 : return NameStr(TupleDescAttr(tupleDesc, fieldno - 1)->attname);
8113 : : }
8114 : :
8115 : : /* Find appropriate nesting depth */
7195 tgl@sss.pgh.pa.us 8116 : 164 : netlevelsup = var->varlevelsup + levelsup;
8117 [ - + ]: 164 : if (netlevelsup >= list_length(context->namespaces))
7195 tgl@sss.pgh.pa.us 8118 [ # # ]:UBC 0 : elog(ERROR, "bogus varlevelsup: %d offset %d",
8119 : : var->varlevelsup, levelsup);
7195 tgl@sss.pgh.pa.us 8120 :CBC 164 : dpns = (deparse_namespace *) list_nth(context->namespaces,
8121 : : netlevelsup);
8122 : :
8123 : : /*
8124 : : * If we have a syntactic referent for the Var, and we're working from a
8125 : : * parse tree, prefer to use the syntactic referent. Otherwise, fall back
8126 : : * on the semantic referent. (See comments in get_variable().)
8127 : : */
2492 8128 [ + + + + ]: 164 : if (var->varnosyn > 0 && dpns->plan == NULL)
8129 : : {
8130 : 64 : varno = var->varnosyn;
8131 : 64 : varattno = var->varattnosyn;
8132 : : }
8133 : : else
8134 : : {
8135 : 100 : varno = var->varno;
8136 : 100 : varattno = var->varattno;
8137 : : }
8138 : :
8139 : : /*
8140 : : * Try to find the relevant RTE in this rtable. In a plan tree, it's
8141 : : * likely that varno is OUTER_VAR or INNER_VAR, in which case we must dig
8142 : : * down into the subplans, or INDEX_VAR, which is resolved similarly.
8143 : : *
8144 : : * Note: unlike get_variable and resolve_special_varno, we need not worry
8145 : : * about inheritance mapping: a child Var should have the same datatype as
8146 : : * its parent, and here we're really only interested in the Var's type.
8147 : : */
8148 [ + + + - ]: 164 : if (varno >= 1 && varno <= list_length(dpns->rtable))
8149 : : {
8150 : 112 : rte = rt_fetch(varno, dpns->rtable);
8151 : 112 : attnum = varattno;
8152 : : }
8153 [ + + + - ]: 52 : else if (varno == OUTER_VAR && dpns->outer_tlist)
8154 : : {
8155 : : TargetEntry *tle;
8156 : : deparse_namespace save_dpns;
8157 : : const char *result;
8158 : :
8159 : 40 : tle = get_tle_by_resno(dpns->outer_tlist, varattno);
7195 8160 [ - + ]: 40 : if (!tle)
2492 tgl@sss.pgh.pa.us 8161 [ # # ]:UBC 0 : elog(ERROR, "bogus varattno for OUTER_VAR var: %d", varattno);
8162 : :
7195 tgl@sss.pgh.pa.us 8163 [ - + ]:CBC 40 : Assert(netlevelsup == 0);
2521 8164 : 40 : push_child_plan(dpns, dpns->outer_plan, &save_dpns);
8165 : :
7195 8166 : 40 : result = get_name_for_var_field((Var *) tle->expr, fieldno,
8167 : : levelsup, context);
8168 : :
5959 8169 : 40 : pop_child_plan(dpns, &save_dpns);
7195 8170 : 40 : return result;
8171 : : }
2492 8172 [ + - + - ]: 12 : else if (varno == INNER_VAR && dpns->inner_tlist)
8173 : : {
8174 : : TargetEntry *tle;
8175 : : deparse_namespace save_dpns;
8176 : : const char *result;
8177 : :
8178 : 12 : tle = get_tle_by_resno(dpns->inner_tlist, varattno);
7195 8179 [ - + ]: 12 : if (!tle)
2492 tgl@sss.pgh.pa.us 8180 [ # # ]:UBC 0 : elog(ERROR, "bogus varattno for INNER_VAR var: %d", varattno);
8181 : :
7195 tgl@sss.pgh.pa.us 8182 [ - + ]:CBC 12 : Assert(netlevelsup == 0);
2521 8183 : 12 : push_child_plan(dpns, dpns->inner_plan, &save_dpns);
8184 : :
7195 8185 : 12 : result = get_name_for_var_field((Var *) tle->expr, fieldno,
8186 : : levelsup, context);
8187 : :
5959 8188 : 12 : pop_child_plan(dpns, &save_dpns);
7195 8189 : 12 : return result;
8190 : : }
2492 tgl@sss.pgh.pa.us 8191 [ # # # # ]:UBC 0 : else if (varno == INDEX_VAR && dpns->index_tlist)
8192 : : {
8193 : : TargetEntry *tle;
8194 : : const char *result;
8195 : :
8196 : 0 : tle = get_tle_by_resno(dpns->index_tlist, varattno);
5504 8197 [ # # ]: 0 : if (!tle)
2492 8198 [ # # ]: 0 : elog(ERROR, "bogus varattno for INDEX_VAR var: %d", varattno);
8199 : :
5504 8200 [ # # ]: 0 : Assert(netlevelsup == 0);
8201 : :
8202 : 0 : result = get_name_for_var_field((Var *) tle->expr, fieldno,
8203 : : levelsup, context);
8204 : :
8205 : 0 : return result;
8206 : : }
8207 : : else
8208 : : {
2492 8209 [ # # ]: 0 : elog(ERROR, "bogus varno: %d", varno);
8210 : : return NULL; /* keep compiler quiet */
8211 : : }
8212 : :
7828 tgl@sss.pgh.pa.us 8213 [ + + ]:CBC 112 : if (attnum == InvalidAttrNumber)
8214 : : {
8215 : : /* Var is whole-row reference to RTE, so select the right field */
8216 : 16 : return get_rte_attribute_name(rte, fieldno);
8217 : : }
8218 : :
8219 : : /*
8220 : : * This part has essentially the same logic as the parser's
8221 : : * expandRecordVariable() function, but we are dealing with a different
8222 : : * representation of the input context, and we only need one field name
8223 : : * not a TupleDesc. Also, we need special cases for finding subquery and
8224 : : * CTE subplans when deparsing Plan trees.
8225 : : */
8226 : 96 : expr = (Node *) var; /* default if we can't drill down */
8227 : :
8228 [ - + - - : 96 : switch (rte->rtekind)
+ - - ]
8229 : : {
7828 tgl@sss.pgh.pa.us 8230 :UBC 0 : case RTE_RELATION:
8231 : : case RTE_VALUES:
8232 : : case RTE_NAMEDTUPLESTORE:
8233 : : case RTE_RESULT:
8234 : :
8235 : : /*
8236 : : * This case should not occur: a column of a table, values list,
8237 : : * or ENR shouldn't have type RECORD. Fall through and fail (most
8238 : : * likely) at the bottom.
8239 : : */
8240 : 0 : break;
7828 tgl@sss.pgh.pa.us 8241 :CBC 48 : case RTE_SUBQUERY:
8242 : : /* Subselect-in-FROM: examine sub-select's output expr */
8243 : : {
7195 8244 [ + + ]: 48 : if (rte->subquery)
8245 : : {
8246 : 28 : TargetEntry *ste = get_tle_by_resno(rte->subquery->targetList,
8247 : : attnum);
8248 : :
8249 [ + - - + ]: 28 : if (ste == NULL || ste->resjunk)
7195 tgl@sss.pgh.pa.us 8250 [ # # ]:UBC 0 : elog(ERROR, "subquery %s does not have attribute %d",
8251 : : rte->eref->aliasname, attnum);
7195 tgl@sss.pgh.pa.us 8252 :CBC 28 : expr = (Node *) ste->expr;
8253 [ + + ]: 28 : if (IsA(expr, Var))
8254 : : {
8255 : : /*
8256 : : * Recurse into the sub-select to see what its Var
8257 : : * refers to. We have to build an additional level of
8258 : : * namespace to keep in step with varlevelsup in the
8259 : : * subselect; furthermore, the subquery RTE might be
8260 : : * from an outer query level, in which case the
8261 : : * namespace for the subselect must have that outer
8262 : : * level as parent namespace.
8263 : : */
1147 8264 : 12 : List *save_nslist = context->namespaces;
8265 : : List *parent_namespaces;
8266 : : deparse_namespace mydpns;
8267 : : const char *result;
8268 : :
8269 : 12 : parent_namespaces = list_copy_tail(context->namespaces,
8270 : : netlevelsup);
8271 : :
5057 8272 : 12 : set_deparse_for_query(&mydpns, rte->subquery,
8273 : : parent_namespaces);
8274 : :
1147 8275 : 12 : context->namespaces = lcons(&mydpns, parent_namespaces);
8276 : :
7195 8277 : 12 : result = get_name_for_var_field((Var *) expr, fieldno,
8278 : : 0, context);
8279 : :
1147 8280 : 12 : context->namespaces = save_nslist;
8281 : :
7195 8282 : 12 : return result;
8283 : : }
8284 : : /* else fall through to inspect the expression */
8285 : : }
8286 : : else
8287 : : {
8288 : : /*
8289 : : * We're deparsing a Plan tree so we don't have complete
8290 : : * RTE entries (in particular, rte->subquery is NULL). But
8291 : : * the only place we'd normally see a Var directly
8292 : : * referencing a SUBQUERY RTE is in a SubqueryScan plan
8293 : : * node, and we can look into the child plan's tlist
8294 : : * instead. An exception occurs if the subquery was
8295 : : * proven empty and optimized away: then we'd find such a
8296 : : * Var in a childless Result node, and there's nothing in
8297 : : * the plan tree that would let us figure out what it had
8298 : : * originally referenced. In that case, fall back on
8299 : : * printing "fN", analogously to the default column names
8300 : : * for RowExprs.
8301 : : */
8302 : : TargetEntry *tle;
8303 : : deparse_namespace save_dpns;
8304 : : const char *result;
8305 : :
2521 8306 [ + + ]: 20 : if (!dpns->inner_plan)
8307 : : {
818 8308 : 8 : char *dummy_name = palloc(32);
8309 : :
816 8310 [ + - - + ]: 8 : Assert(dpns->plan && IsA(dpns->plan, Result));
818 8311 : 8 : snprintf(dummy_name, 32, "f%d", fieldno);
8312 : 8 : return dummy_name;
8313 : : }
816 8314 [ + - - + ]: 12 : Assert(dpns->plan && IsA(dpns->plan, SubqueryScan));
8315 : :
5504 8316 : 12 : tle = get_tle_by_resno(dpns->inner_tlist, attnum);
7195 8317 [ - + ]: 12 : if (!tle)
7195 tgl@sss.pgh.pa.us 8318 [ # # ]:UBC 0 : elog(ERROR, "bogus varattno for subquery var: %d",
8319 : : attnum);
7195 tgl@sss.pgh.pa.us 8320 [ - + ]:CBC 12 : Assert(netlevelsup == 0);
2521 8321 : 12 : push_child_plan(dpns, dpns->inner_plan, &save_dpns);
8322 : :
7195 8323 : 12 : result = get_name_for_var_field((Var *) tle->expr, fieldno,
8324 : : levelsup, context);
8325 : :
5959 8326 : 12 : pop_child_plan(dpns, &save_dpns);
7828 8327 : 12 : return result;
8328 : : }
8329 : : }
8330 : 16 : break;
7828 tgl@sss.pgh.pa.us 8331 :UBC 0 : case RTE_JOIN:
8332 : : /* Join RTE --- recursively inspect the alias variable */
7105 8333 [ # # ]: 0 : if (rte->joinaliasvars == NIL)
8334 [ # # ]: 0 : elog(ERROR, "cannot decompile join alias var in plan tree");
7828 8335 [ # # # # ]: 0 : Assert(attnum > 0 && attnum <= list_length(rte->joinaliasvars));
8336 : 0 : expr = (Node *) list_nth(rte->joinaliasvars, attnum - 1);
4853 8337 [ # # ]: 0 : Assert(expr != NULL);
8338 : : /* we intentionally don't strip implicit coercions here */
7828 8339 [ # # ]: 0 : if (IsA(expr, Var))
8340 : 0 : return get_name_for_var_field((Var *) expr, fieldno,
8341 : 0 : var->varlevelsup + levelsup,
8342 : : context);
8343 : : /* else fall through to inspect the expression */
8344 : 0 : break;
8345 : 0 : case RTE_FUNCTION:
8346 : : case RTE_TABLEFUNC:
8347 : :
8348 : : /*
8349 : : * We couldn't get here unless a function is declared with one of
8350 : : * its result columns as RECORD, which is not allowed.
8351 : : */
8352 : 0 : break;
6606 tgl@sss.pgh.pa.us 8353 :CBC 48 : case RTE_CTE:
8354 : : /* CTE reference: examine subquery's output expr */
8355 : : {
6604 8356 : 48 : CommonTableExpr *cte = NULL;
8357 : : Index ctelevelsup;
8358 : : ListCell *lc;
8359 : :
8360 : : /*
8361 : : * Try to find the referenced CTE using the namespace stack.
8362 : : */
8363 : 48 : ctelevelsup = rte->ctelevelsup + netlevelsup;
8364 [ + + ]: 48 : if (ctelevelsup >= list_length(context->namespaces))
8365 : 8 : lc = NULL;
8366 : : else
8367 : : {
8368 : : deparse_namespace *ctedpns;
8369 : :
8370 : : ctedpns = (deparse_namespace *)
8371 : 40 : list_nth(context->namespaces, ctelevelsup);
8372 [ + + + - : 44 : foreach(lc, ctedpns->ctes)
+ + ]
8373 : : {
8374 : 24 : cte = (CommonTableExpr *) lfirst(lc);
8375 [ + + ]: 24 : if (strcmp(cte->ctename, rte->ctename) == 0)
8376 : 20 : break;
8377 : : }
8378 : : }
8379 [ + + ]: 48 : if (lc != NULL)
8380 : : {
8381 : 20 : Query *ctequery = (Query *) cte->ctequery;
5688 bruce@momjian.us 8382 [ - + + - ]: 20 : TargetEntry *ste = get_tle_by_resno(GetCTETargetList(cte),
8383 : : attnum);
8384 : :
6604 tgl@sss.pgh.pa.us 8385 [ + - - + ]: 20 : if (ste == NULL || ste->resjunk)
1147 tgl@sss.pgh.pa.us 8386 [ # # ]:UBC 0 : elog(ERROR, "CTE %s does not have attribute %d",
8387 : : rte->eref->aliasname, attnum);
6604 tgl@sss.pgh.pa.us 8388 :CBC 20 : expr = (Node *) ste->expr;
8389 [ + + ]: 20 : if (IsA(expr, Var))
8390 : : {
8391 : : /*
8392 : : * Recurse into the CTE to see what its Var refers to.
8393 : : * We have to build an additional level of namespace
8394 : : * to keep in step with varlevelsup in the CTE;
8395 : : * furthermore it could be an outer CTE (compare
8396 : : * SUBQUERY case above).
8397 : : */
8398 : 12 : List *save_nslist = context->namespaces;
8399 : : List *parent_namespaces;
8400 : : deparse_namespace mydpns;
8401 : : const char *result;
8402 : :
1147 8403 : 12 : parent_namespaces = list_copy_tail(context->namespaces,
8404 : : ctelevelsup);
8405 : :
5057 8406 : 12 : set_deparse_for_query(&mydpns, ctequery,
8407 : : parent_namespaces);
8408 : :
1147 8409 : 12 : context->namespaces = lcons(&mydpns, parent_namespaces);
8410 : :
6604 8411 : 12 : result = get_name_for_var_field((Var *) expr, fieldno,
8412 : : 0, context);
8413 : :
8414 : 12 : context->namespaces = save_nslist;
8415 : :
8416 : 12 : return result;
8417 : : }
8418 : : /* else fall through to inspect the expression */
8419 : : }
8420 : : else
8421 : : {
8422 : : /*
8423 : : * We're deparsing a Plan tree so we don't have a CTE
8424 : : * list. But the only places we'd normally see a Var
8425 : : * directly referencing a CTE RTE are in CteScan or
8426 : : * WorkTableScan plan nodes. For those cases,
8427 : : * set_deparse_plan arranged for dpns->inner_plan to be
8428 : : * the plan node that emits the CTE or RecursiveUnion
8429 : : * result, and we can look at its tlist instead. As
8430 : : * above, this can fail if the CTE has been proven empty,
8431 : : * in which case fall back to "fN".
8432 : : */
8433 : : TargetEntry *tle;
8434 : : deparse_namespace save_dpns;
8435 : : const char *result;
8436 : :
2521 8437 [ + + ]: 28 : if (!dpns->inner_plan)
8438 : : {
818 8439 : 4 : char *dummy_name = palloc(32);
8440 : :
816 8441 [ + - - + ]: 4 : Assert(dpns->plan && IsA(dpns->plan, Result));
818 8442 : 4 : snprintf(dummy_name, 32, "f%d", fieldno);
8443 : 4 : return dummy_name;
8444 : : }
816 8445 [ + - + + : 24 : Assert(dpns->plan && (IsA(dpns->plan, CteScan) ||
- + ]
8446 : : IsA(dpns->plan, WorkTableScan)));
8447 : :
5504 8448 : 24 : tle = get_tle_by_resno(dpns->inner_tlist, attnum);
6604 8449 [ - + ]: 24 : if (!tle)
6604 tgl@sss.pgh.pa.us 8450 [ # # ]:UBC 0 : elog(ERROR, "bogus varattno for subquery var: %d",
8451 : : attnum);
6604 tgl@sss.pgh.pa.us 8452 [ - + ]:CBC 24 : Assert(netlevelsup == 0);
2521 8453 : 24 : push_child_plan(dpns, dpns->inner_plan, &save_dpns);
8454 : :
6604 8455 : 24 : result = get_name_for_var_field((Var *) tle->expr, fieldno,
8456 : : levelsup, context);
8457 : :
5959 8458 : 24 : pop_child_plan(dpns, &save_dpns);
6604 8459 : 24 : return result;
8460 : : }
8461 : : }
6606 8462 : 8 : break;
786 rguo@postgresql.org 8463 :UBC 0 : case RTE_GROUP:
8464 : :
8465 : : /*
8466 : : * We couldn't get here: any Vars that reference the RTE_GROUP RTE
8467 : : * should have been replaced with the underlying grouping
8468 : : * expressions.
8469 : : */
8470 : 0 : break;
8471 : : }
8472 : :
8473 : : /*
8474 : : * We now have an expression we can't expand any more, so see if
8475 : : * get_expr_result_tupdesc() can do anything with it.
8476 : : */
3297 tgl@sss.pgh.pa.us 8477 :CBC 24 : tupleDesc = get_expr_result_tupdesc(expr, false);
8478 : : /* Got the tupdesc, so we can extract the field name */
7828 8479 [ + - - + ]: 24 : Assert(fieldno >= 1 && fieldno <= tupleDesc->natts);
3364 andres@anarazel.de 8480 : 24 : return NameStr(TupleDescAttr(tupleDesc, fieldno - 1)->attname);
8481 : : }
8482 : :
8483 : : /*
8484 : : * Try to find the referenced expression for a PARAM_EXEC Param that might
8485 : : * reference a parameter supplied by an upper NestLoop or SubPlan plan node.
8486 : : *
8487 : : * If successful, return the expression and set *dpns_p and *ancestor_cell_p
8488 : : * appropriately for calling push_ancestor_plan(). If no referent can be
8489 : : * found, return NULL.
8490 : : */
8491 : : static Node *
5538 tgl@sss.pgh.pa.us 8492 : 5662 : find_param_referent(Param *param, deparse_context *context,
8493 : : deparse_namespace **dpns_p, ListCell **ancestor_cell_p)
8494 : : {
8495 : : /* Initialize output parameters to prevent compiler warnings */
8496 : 5662 : *dpns_p = NULL;
8497 : 5662 : *ancestor_cell_p = NULL;
8498 : :
8499 : : /*
8500 : : * If it's a PARAM_EXEC parameter, look for a matching NestLoopParam or
8501 : : * SubPlan argument. This will necessarily be in some ancestor of the
8502 : : * current expression's Plan node.
8503 : : */
5959 8504 [ + + ]: 5662 : if (param->paramkind == PARAM_EXEC)
8505 : : {
8506 : : deparse_namespace *dpns;
8507 : : Plan *child_plan;
8508 : : ListCell *lc;
8509 : :
8510 : 5061 : dpns = (deparse_namespace *) linitial(context->namespaces);
2521 8511 : 5061 : child_plan = dpns->plan;
8512 : :
5959 8513 [ + + + + : 9004 : foreach(lc, dpns->ancestors)
+ + ]
8514 : : {
2521 8515 : 7818 : Node *ancestor = (Node *) lfirst(lc);
8516 : : ListCell *lc2;
8517 : :
8518 : : /*
8519 : : * NestLoops transmit params to their inner child only.
8520 : : */
8521 [ + + ]: 7818 : if (IsA(ancestor, NestLoop) &&
1450 8522 [ + + ]: 3665 : child_plan == innerPlan(ancestor))
8523 : : {
2521 8524 : 3519 : NestLoop *nl = (NestLoop *) ancestor;
8525 : :
5959 8526 [ + + + + : 4370 : foreach(lc2, nl->nestParams)
+ + ]
8527 : : {
5688 bruce@momjian.us 8528 : 4169 : NestLoopParam *nlp = (NestLoopParam *) lfirst(lc2);
8529 : :
5959 tgl@sss.pgh.pa.us 8530 [ + + ]: 4169 : if (nlp->paramno == param->paramid)
8531 : : {
8532 : : /* Found a match, so return it */
5538 8533 : 3318 : *dpns_p = dpns;
8534 : 3318 : *ancestor_cell_p = lc;
8535 : 3318 : return (Node *) nlp->paramval;
8536 : : }
8537 : : }
8538 : : }
8539 : :
8540 : : /*
8541 : : * If ancestor is a SubPlan, check the arguments it provides.
8542 : : */
2521 8543 [ + + ]: 4500 : if (IsA(ancestor, SubPlan))
5959 8544 : 345 : {
2521 8545 : 902 : SubPlan *subplan = (SubPlan *) ancestor;
8546 : : ListCell *lc3;
8547 : : ListCell *lc4;
8548 : :
5959 8549 [ + + + + : 1149 : forboth(lc3, subplan->parParam, lc4, subplan->args)
+ + + + +
+ + - +
+ ]
8550 : : {
5688 bruce@momjian.us 8551 : 804 : int paramid = lfirst_int(lc3);
8552 : 804 : Node *arg = (Node *) lfirst(lc4);
8553 : :
5959 tgl@sss.pgh.pa.us 8554 [ + + ]: 804 : if (paramid == param->paramid)
8555 : : {
8556 : : /*
8557 : : * Found a match, so return it. But, since Vars in
8558 : : * the arg are to be evaluated in the surrounding
8559 : : * context, we have to point to the next ancestor item
8560 : : * that is *not* a SubPlan.
8561 : : */
8562 : : ListCell *rest;
8563 : :
2521 8564 [ + - + - : 557 : for_each_cell(rest, dpns->ancestors,
+ - ]
8565 : : lnext(dpns->ancestors, lc))
8566 : : {
8567 : 557 : Node *ancestor2 = (Node *) lfirst(rest);
8568 : :
8569 [ + - ]: 557 : if (!IsA(ancestor2, SubPlan))
8570 : : {
8571 : 557 : *dpns_p = dpns;
8572 : 557 : *ancestor_cell_p = rest;
8573 : 557 : return arg;
8574 : : }
8575 : : }
2521 tgl@sss.pgh.pa.us 8576 [ # # ]:UBC 0 : elog(ERROR, "SubPlan cannot be outermost ancestor");
8577 : : }
8578 : : }
8579 : :
8580 : : /* SubPlan isn't a kind of Plan, so skip the rest */
2521 tgl@sss.pgh.pa.us 8581 :CBC 345 : continue;
8582 : : }
8583 : :
8584 : : /*
8585 : : * We need not consider the ancestor's initPlan list, since
8586 : : * initplans never have any parParams.
8587 : : */
8588 : :
8589 : : /* No luck, crawl up to next ancestor */
8590 : 3598 : child_plan = (Plan *) ancestor;
8591 : : }
8592 : : }
8593 : :
8594 : : /* No referent found */
5538 8595 : 1787 : return NULL;
8596 : : }
8597 : :
8598 : : /*
8599 : : * Try to find a subplan/initplan that emits the value for a PARAM_EXEC Param.
8600 : : *
8601 : : * If successful, return the generating subplan/initplan and set *column_p
8602 : : * to the subplan's 0-based output column number.
8603 : : * Otherwise, return NULL.
8604 : : */
8605 : : static SubPlan *
961 8606 : 1787 : find_param_generator(Param *param, deparse_context *context, int *column_p)
8607 : : {
8608 : : /* Initialize output parameter to prevent compiler warnings */
8609 : 1787 : *column_p = 0;
8610 : :
8611 : : /*
8612 : : * If it's a PARAM_EXEC parameter, search the current plan node as well as
8613 : : * ancestor nodes looking for a subplan or initplan that emits the value
8614 : : * for the Param. It could appear in the setParams of an initplan or
8615 : : * MULTIEXPR_SUBLINK subplan, or in the paramIds of an ancestral SubPlan.
8616 : : */
8617 [ + + ]: 1787 : if (param->paramkind == PARAM_EXEC)
8618 : : {
8619 : : SubPlan *result;
8620 : : deparse_namespace *dpns;
8621 : : ListCell *lc;
8622 : :
8623 : 1186 : dpns = (deparse_namespace *) linitial(context->namespaces);
8624 : :
8625 : : /* First check the innermost plan node's initplans */
8626 : 1186 : result = find_param_generator_initplan(param, dpns->plan, column_p);
8627 [ + + ]: 1186 : if (result)
8628 : 307 : return result;
8629 : :
8630 : : /*
8631 : : * The plan's targetlist might contain MULTIEXPR_SUBLINK SubPlans,
8632 : : * which can be referenced by Params elsewhere in the targetlist.
8633 : : * (Such Params should always be in the same targetlist, so there's no
8634 : : * need to do this work at upper plan nodes.)
8635 : : */
8636 [ + + + + : 4431 : foreach_node(TargetEntry, tle, dpns->plan->targetlist)
+ + ]
8637 : : {
8638 [ + - + + ]: 2741 : if (tle->expr && IsA(tle->expr, SubPlan))
8639 : : {
8640 : 66 : SubPlan *subplan = (SubPlan *) tle->expr;
8641 : :
8642 [ + + ]: 66 : if (subplan->subLinkType == MULTIEXPR_SUBLINK)
8643 : : {
8644 [ + - + - : 51 : foreach_int(paramid, subplan->setParam)
+ - ]
8645 : : {
8646 [ + + ]: 51 : if (paramid == param->paramid)
8647 : : {
8648 : : /* Found a match, so return it. */
8649 : 34 : *column_p = foreach_current_index(paramid);
8650 : 34 : return subplan;
8651 : : }
8652 : : }
8653 : : }
8654 : : }
8655 : : }
8656 : :
8657 : : /* No luck, so check the ancestor nodes */
8658 [ + - + - : 1116 : foreach(lc, dpns->ancestors)
+ - ]
8659 : : {
8660 : 1116 : Node *ancestor = (Node *) lfirst(lc);
8661 : :
8662 : : /*
8663 : : * If ancestor is a SubPlan, check the paramIds it provides.
8664 : : */
8665 [ + + ]: 1116 : if (IsA(ancestor, SubPlan))
961 tgl@sss.pgh.pa.us 8666 :UBC 0 : {
961 tgl@sss.pgh.pa.us 8667 :CBC 225 : SubPlan *subplan = (SubPlan *) ancestor;
8668 : :
8669 [ + - + - : 250 : foreach_int(paramid, subplan->paramIds)
+ - ]
8670 : : {
8671 [ + + ]: 250 : if (paramid == param->paramid)
8672 : : {
8673 : : /* Found a match, so return it. */
8674 : 225 : *column_p = foreach_current_index(paramid);
8675 : 225 : return subplan;
8676 : : }
8677 : : }
8678 : :
8679 : : /* SubPlan isn't a kind of Plan, so skip the rest */
961 tgl@sss.pgh.pa.us 8680 :UBC 0 : continue;
8681 : : }
8682 : :
8683 : : /*
8684 : : * Otherwise, it's some kind of Plan node, so check its initplans.
8685 : : */
961 tgl@sss.pgh.pa.us 8686 :CBC 891 : result = find_param_generator_initplan(param, (Plan *) ancestor,
8687 : : column_p);
8688 [ + + ]: 891 : if (result)
8689 : 620 : return result;
8690 : :
8691 : : /* No luck, crawl up to next ancestor */
8692 : : }
8693 : : }
8694 : :
8695 : : /* No generator found */
8696 : 601 : return NULL;
8697 : : }
8698 : :
8699 : : /*
8700 : : * Subroutine for find_param_generator: search one Plan node's initplans
8701 : : */
8702 : : static SubPlan *
8703 : 2077 : find_param_generator_initplan(Param *param, Plan *plan, int *column_p)
8704 : : {
8705 [ + + + - : 3327 : foreach_node(SubPlan, subplan, plan->initPlan)
+ + ]
8706 : : {
8707 [ + - + + : 1231 : foreach_int(paramid, subplan->setParam)
+ + ]
8708 : : {
8709 [ + + ]: 1031 : if (paramid == param->paramid)
8710 : : {
8711 : : /* Found a match, so return it. */
8712 : 927 : *column_p = foreach_current_index(paramid);
8713 : 927 : return subplan;
8714 : : }
8715 : : }
8716 : : }
8717 : 1150 : return NULL;
8718 : : }
8719 : :
8720 : : /*
8721 : : * Display a Param appropriately.
8722 : : */
8723 : : static void
5538 8724 : 5650 : get_parameter(Param *param, deparse_context *context)
8725 : : {
8726 : : Node *expr;
8727 : : deparse_namespace *dpns;
8728 : : ListCell *ancestor_cell;
8729 : : SubPlan *subplan;
8730 : : int column;
8731 : :
8732 : : /*
8733 : : * If it's a PARAM_EXEC parameter, try to locate the expression from which
8734 : : * the parameter was computed. This stanza handles only cases in which
8735 : : * the Param represents an input to the subplan we are currently in.
8736 : : */
8737 : 5650 : expr = find_param_referent(param, context, &dpns, &ancestor_cell);
8738 [ + + ]: 5650 : if (expr)
8739 : : {
8740 : : /* Found a match, so print it */
8741 : : deparse_namespace save_dpns;
8742 : : bool save_varprefix;
8743 : : bool need_paren;
8744 : :
8745 : : /* Switch attention to the ancestor plan node */
8746 : 3863 : push_ancestor_plan(dpns, ancestor_cell, &save_dpns);
8747 : :
8748 : : /*
8749 : : * Force prefixing of Vars, since they won't belong to the relation
8750 : : * being scanned in the original plan node.
8751 : : */
8752 : 3863 : save_varprefix = context->varprefix;
8753 : 3863 : context->varprefix = true;
8754 : :
8755 : : /*
8756 : : * A Param's expansion is typically a Var, Aggref, GroupingFunc, or
8757 : : * upper-level Param, which wouldn't need extra parentheses.
8758 : : * Otherwise, insert parens to ensure the expression looks atomic.
8759 : : */
8760 [ + + ]: 3891 : need_paren = !(IsA(expr, Var) ||
8761 [ + + ]: 28 : IsA(expr, Aggref) ||
1690 8762 [ + + ]: 20 : IsA(expr, GroupingFunc) ||
5538 8763 [ - + ]: 16 : IsA(expr, Param));
8764 [ - + ]: 3863 : if (need_paren)
5538 tgl@sss.pgh.pa.us 8765 :UBC 0 : appendStringInfoChar(context->buf, '(');
8766 : :
5538 tgl@sss.pgh.pa.us 8767 :CBC 3863 : get_rule_expr(expr, context, false);
8768 : :
8769 [ - + ]: 3863 : if (need_paren)
5538 tgl@sss.pgh.pa.us 8770 :UBC 0 : appendStringInfoChar(context->buf, ')');
8771 : :
5538 tgl@sss.pgh.pa.us 8772 :CBC 3863 : context->varprefix = save_varprefix;
8773 : :
8774 : 3863 : pop_ancestor_plan(dpns, &save_dpns);
8775 : :
8776 : 3863 : return;
8777 : : }
8778 : :
8779 : : /*
8780 : : * Alternatively, maybe it's a subplan output, which we print as a
8781 : : * reference to the subplan. (We could drill down into the subplan and
8782 : : * print the relevant targetlist expression, but that has been deemed too
8783 : : * confusing since it would violate normal SQL scope rules. Also, we're
8784 : : * relying on this reference to show that the testexpr containing the
8785 : : * Param has anything to do with that subplan at all.)
8786 : : */
961 8787 : 1787 : subplan = find_param_generator(param, context, &column);
8788 [ + + ]: 1787 : if (subplan)
8789 : : {
8790 : : const char *nameprefix;
8791 : :
394 rhaas@postgresql.org 8792 [ + + ]: 1186 : if (subplan->isInitPlan)
8793 : 927 : nameprefix = "InitPlan ";
8794 : : else
8795 : 259 : nameprefix = "SubPlan ";
8796 : :
8797 : 1186 : appendStringInfo(context->buf, "(%s%s%s).col%d",
961 tgl@sss.pgh.pa.us 8798 [ + + ]: 1186 : subplan->useHashTable ? "hashed " : "",
8799 : : nameprefix,
8800 : : subplan->plan_name, column + 1);
8801 : :
8802 : 1186 : return;
8803 : : }
8804 : :
8805 : : /*
8806 : : * If it's an external parameter, see if the outermost namespace provides
8807 : : * function argument names.
8808 : : */
1814 8809 [ + - + - ]: 601 : if (param->paramkind == PARAM_EXTERN && context->namespaces != NIL)
8810 : : {
8811 : 601 : dpns = llast(context->namespaces);
8812 [ + + ]: 601 : if (dpns->argnames &&
8813 [ + - ]: 45 : param->paramid > 0 &&
8814 [ + - ]: 45 : param->paramid <= dpns->numargs)
8815 : : {
2038 peter@eisentraut.org 8816 : 45 : char *argname = dpns->argnames[param->paramid - 1];
8817 : :
8818 [ + - ]: 45 : if (argname)
8819 : : {
8820 : 45 : bool should_qualify = false;
8821 : : ListCell *lc;
8822 : :
8823 : : /*
8824 : : * Qualify the parameter name if there are any other deparse
8825 : : * namespaces with range tables. This avoids qualifying in
8826 : : * trivial cases like "RETURN a + b", but makes it safe in all
8827 : : * other cases.
8828 : : */
8829 [ + - + + : 103 : foreach(lc, context->namespaces)
+ + ]
8830 : : {
1492 drowley@postgresql.o 8831 : 78 : deparse_namespace *depns = lfirst(lc);
8832 : :
8833 [ + + ]: 78 : if (depns->rtable_names != NIL)
8834 : : {
2038 peter@eisentraut.org 8835 : 20 : should_qualify = true;
8836 : 20 : break;
8837 : : }
8838 : : }
8839 [ + + ]: 45 : if (should_qualify)
8840 : : {
8841 : 20 : appendStringInfoString(context->buf, quote_identifier(dpns->funcname));
8842 : 20 : appendStringInfoChar(context->buf, '.');
8843 : : }
8844 : :
8845 : 45 : appendStringInfoString(context->buf, quote_identifier(argname));
8846 : 45 : return;
8847 : : }
8848 : : }
8849 : : }
8850 : :
8851 : : /*
8852 : : * Not PARAM_EXEC, or couldn't find referent: just print $N.
8853 : : *
8854 : : * It's a bug if we get here for anything except PARAM_EXTERN Params, but
8855 : : * in production builds printing $N seems more useful than failing.
8856 : : */
961 tgl@sss.pgh.pa.us 8857 [ - + ]: 556 : Assert(param->paramkind == PARAM_EXTERN);
8858 : :
5538 8859 : 556 : appendStringInfo(context->buf, "$%d", param->paramid);
8860 : : }
8861 : :
8862 : : /*
8863 : : * get_simple_binary_op_name
8864 : : *
8865 : : * helper function for isSimpleNode
8866 : : * will return single char binary operator name, or NULL if it's not
8867 : : */
8868 : : static const char *
8490 bruce@momjian.us 8869 : 100 : get_simple_binary_op_name(OpExpr *expr)
8870 : : {
8499 tgl@sss.pgh.pa.us 8871 : 100 : List *args = expr->args;
8872 : :
8194 neilc@samurai.com 8873 [ + - ]: 100 : if (list_length(args) == 2)
8874 : : {
8875 : : /* binary operator */
8198 8876 : 100 : Node *arg1 = (Node *) linitial(args);
8499 tgl@sss.pgh.pa.us 8877 : 100 : Node *arg2 = (Node *) lsecond(args);
8878 : : const char *op;
8879 : :
8880 : 100 : op = generate_operator_name(expr->opno, exprType(arg1), exprType(arg2));
8881 [ + - ]: 100 : if (strlen(op) == 1)
8494 bruce@momjian.us 8882 : 100 : return op;
8883 : : }
8499 tgl@sss.pgh.pa.us 8884 :UBC 0 : return NULL;
8885 : : }
8886 : :
8887 : :
8888 : : /*
8889 : : * isSimpleNode - check if given node is simple (doesn't need parenthesizing)
8890 : : *
8891 : : * true : simple in the context of parent node's type
8892 : : * false : not simple
8893 : : */
8894 : : static bool
8499 tgl@sss.pgh.pa.us 8895 :CBC 3897 : isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
8896 : : {
8494 bruce@momjian.us 8897 [ - + ]: 3897 : if (!node)
8499 tgl@sss.pgh.pa.us 8898 :UBC 0 : return false;
8899 : :
8494 bruce@momjian.us 8900 [ + + - + :CBC 3897 : switch (nodeTag(node))
- + + + -
- - + + +
+ + + ]
8901 : : {
8499 tgl@sss.pgh.pa.us 8902 : 3246 : case T_Var:
8903 : : case T_Const:
8904 : : case T_Param:
8905 : : case T_CoerceToDomainValue:
8906 : : case T_SetToDefault:
8907 : : case T_CurrentOfExpr:
8908 : : /* single words: always simple */
8909 : 3246 : return true;
8910 : :
2834 alvherre@alvh.no-ip. 8911 : 335 : case T_SubscriptingRef:
8912 : : case T_ArrayExpr:
8913 : : case T_RowExpr:
8914 : : case T_CoalesceExpr:
8915 : : case T_MinMaxExpr:
8916 : : case T_SQLValueFunction:
8917 : : case T_NextValueExpr:
8918 : : case T_NullIfExpr:
8919 : : case T_Aggref:
8920 : : case T_GroupingFunc:
8921 : : case T_WindowFunc:
8922 : : case T_MergeSupportFunc:
8923 : : case T_FuncExpr:
8924 : : case T_JsonConstructorExpr:
8925 : : case T_JsonExpr:
8926 : : /* function-like: name(..) or name[..] */
8499 tgl@sss.pgh.pa.us 8927 : 335 : return true;
8928 : :
8929 : : /* CASE keywords act as parentheses */
8499 tgl@sss.pgh.pa.us 8930 :UBC 0 : case T_CaseExpr:
8931 : 0 : return true;
8932 : :
8499 tgl@sss.pgh.pa.us 8933 :CBC 48 : case T_FieldSelect:
8934 : :
8935 : : /*
8936 : : * appears simple since . has top precedence, unless parent is
8937 : : * T_FieldSelect itself!
8938 : : */
1884 michael@paquier.xyz 8939 : 48 : return !IsA(parentNode, FieldSelect);
8940 : :
8184 tgl@sss.pgh.pa.us 8941 :UBC 0 : case T_FieldStore:
8942 : :
8943 : : /*
8944 : : * treat like FieldSelect (probably doesn't matter)
8945 : : */
1884 michael@paquier.xyz 8946 : 0 : return !IsA(parentNode, FieldStore);
8947 : :
8499 tgl@sss.pgh.pa.us 8948 :CBC 12 : case T_CoerceToDomain:
8949 : : /* maybe simple, check args */
8494 bruce@momjian.us 8950 : 12 : return isSimpleNode((Node *) ((CoerceToDomain *) node)->arg,
8951 : : node, prettyFlags);
8499 tgl@sss.pgh.pa.us 8952 : 12 : case T_RelabelType:
8494 bruce@momjian.us 8953 : 12 : return isSimpleNode((Node *) ((RelabelType *) node)->arg,
8954 : : node, prettyFlags);
7093 tgl@sss.pgh.pa.us 8955 : 16 : case T_CoerceViaIO:
8956 : 16 : return isSimpleNode((Node *) ((CoerceViaIO *) node)->arg,
8957 : : node, prettyFlags);
7163 tgl@sss.pgh.pa.us 8958 :UBC 0 : case T_ArrayCoerceExpr:
8959 : 0 : return isSimpleNode((Node *) ((ArrayCoerceExpr *) node)->arg,
8960 : : node, prettyFlags);
7999 8961 : 0 : case T_ConvertRowtypeExpr:
8962 : 0 : return isSimpleNode((Node *) ((ConvertRowtypeExpr *) node)->arg,
8963 : : node, prettyFlags);
658 dean.a.rasheed@gmail 8964 : 0 : case T_ReturningExpr:
8965 : 0 : return isSimpleNode((Node *) ((ReturningExpr *) node)->retexpr,
8966 : : node, prettyFlags);
8967 : :
8499 tgl@sss.pgh.pa.us 8968 :CBC 184 : case T_OpExpr:
8969 : : {
8970 : : /* depends on parent node type; needs further checking */
8494 bruce@momjian.us 8971 [ + - + + ]: 184 : if (prettyFlags & PRETTYFLAG_PAREN && IsA(parentNode, OpExpr))
8972 : : {
8973 : : const char *op;
8974 : : const char *parentOp;
8975 : : bool is_lopriop;
8976 : : bool is_hipriop;
8977 : : bool is_lopriparent;
8978 : : bool is_hipriparent;
8979 : :
8980 : 52 : op = get_simple_binary_op_name((OpExpr *) node);
8981 [ - + ]: 52 : if (!op)
8494 bruce@momjian.us 8982 :UBC 0 : return false;
8983 : :
8984 : : /* We know only the basic operators + - and * / % */
8494 bruce@momjian.us 8985 :CBC 52 : is_lopriop = (strchr("+-", *op) != NULL);
8986 : 52 : is_hipriop = (strchr("*/%", *op) != NULL);
8987 [ + + + + ]: 52 : if (!(is_lopriop || is_hipriop))
8988 : 4 : return false;
8989 : :
8990 : 48 : parentOp = get_simple_binary_op_name((OpExpr *) parentNode);
8991 [ - + ]: 48 : if (!parentOp)
8494 bruce@momjian.us 8992 :UBC 0 : return false;
8993 : :
8494 bruce@momjian.us 8994 :CBC 48 : is_lopriparent = (strchr("+-", *parentOp) != NULL);
8995 : 48 : is_hipriparent = (strchr("*/%", *parentOp) != NULL);
8996 [ + + - + ]: 48 : if (!(is_lopriparent || is_hipriparent))
8494 bruce@momjian.us 8997 :UBC 0 : return false;
8998 : :
8494 bruce@momjian.us 8999 [ + + + - ]:CBC 48 : if (is_hipriop && is_lopriparent)
9000 : 8 : return true; /* op binds tighter than parent */
9001 : :
9002 [ + - + + ]: 40 : if (is_lopriop && is_hipriparent)
9003 : 32 : return false;
9004 : :
9005 : : /*
9006 : : * Operators are same priority --- can skip parens only if
9007 : : * we have (a - b) - c, not a - (b - c).
9008 : : */
8198 neilc@samurai.com 9009 [ + + ]: 8 : if (node == (Node *) linitial(((OpExpr *) parentNode)->args))
8494 bruce@momjian.us 9010 : 4 : return true;
9011 : :
9012 : 4 : return false;
9013 : : }
9014 : : /* else do the same stuff as for T_SubLink et al. */
9015 : : }
9016 : : pg_fallthrough;
9017 : :
9018 : : case T_XmlExpr:
9019 : :
9020 : : /*
9021 : : * Function-like (name(..)), except "x IS DOCUMENT", which is a
9022 : : * postfix test like IS NULL. (The IsA check is needed because
9023 : : * T_OpExpr falls through to here.)
9024 : : */
16 tgl@sss.pgh.pa.us 9025 [ + + - + ]: 136 : if (IsA(node, XmlExpr) && ((XmlExpr *) node)->op != IS_DOCUMENT)
16 tgl@sss.pgh.pa.us 9026 :UBC 0 : return true;
9027 : : pg_fallthrough;
9028 : :
9029 : : case T_SubLink:
9030 : : case T_NullTest:
9031 : : case T_BooleanTest:
9032 : : case T_DistinctExpr:
9033 : : case T_JsonIsPredicate:
8499 tgl@sss.pgh.pa.us 9034 [ + + + + ]:CBC 152 : switch (nodeTag(parentNode))
9035 : : {
9036 : 24 : case T_FuncExpr:
9037 : : {
9038 : : /* special handling for casts and COERCE_SQL_SYNTAX */
8494 bruce@momjian.us 9039 : 24 : CoercionForm type = ((FuncExpr *) parentNode)->funcformat;
9040 : :
9041 [ + + + + ]: 24 : if (type == COERCE_EXPLICIT_CAST ||
1435 tgl@sss.pgh.pa.us 9042 [ + - ]: 4 : type == COERCE_IMPLICIT_CAST ||
9043 : : type == COERCE_SQL_SYNTAX)
8494 bruce@momjian.us 9044 : 24 : return false;
8494 bruce@momjian.us 9045 :UBC 0 : return true; /* own parentheses */
9046 : : }
3424 tgl@sss.pgh.pa.us 9047 :CBC 92 : case T_BoolExpr: /* lower precedence */
9048 : : case T_SubscriptingRef: /* other separators */
9049 : : case T_ArrayExpr: /* other separators */
9050 : : case T_RowExpr: /* other separators */
9051 : : case T_CoalesceExpr: /* own parentheses */
9052 : : case T_MinMaxExpr: /* own parentheses */
9053 : : case T_NullIfExpr: /* other separators */
9054 : : case T_Aggref: /* own parentheses */
9055 : : case T_GroupingFunc: /* own parentheses */
9056 : : case T_WindowFunc: /* own parentheses */
9057 : : case T_CaseExpr: /* other separators */
8499 9058 : 92 : return true;
16 9059 : 4 : case T_XmlExpr: /* own parentheses, except IS DOCUMENT */
9060 : 4 : return ((XmlExpr *) parentNode)->op != IS_DOCUMENT;
8499 9061 : 32 : default:
9062 : 32 : return false;
9063 : : }
9064 : :
9065 : 16 : case T_BoolExpr:
9066 [ + - - - : 16 : switch (nodeTag(parentNode))
+ ]
9067 : : {
9068 : 12 : case T_BoolExpr:
9069 [ + - ]: 12 : if (prettyFlags & PRETTYFLAG_PAREN)
9070 : : {
9071 : : BoolExprType type;
9072 : : BoolExprType parentType;
9073 : :
8494 bruce@momjian.us 9074 : 12 : type = ((BoolExpr *) node)->boolop;
9075 : 12 : parentType = ((BoolExpr *) parentNode)->boolop;
8499 tgl@sss.pgh.pa.us 9076 [ + + - ]: 12 : switch (type)
9077 : : {
9078 : 8 : case NOT_EXPR:
9079 : : case AND_EXPR:
9080 [ + + + - ]: 8 : if (parentType == AND_EXPR || parentType == OR_EXPR)
9081 : 8 : return true;
8499 tgl@sss.pgh.pa.us 9082 :UBC 0 : break;
8499 tgl@sss.pgh.pa.us 9083 :CBC 4 : case OR_EXPR:
9084 [ - + ]: 4 : if (parentType == OR_EXPR)
8499 tgl@sss.pgh.pa.us 9085 :UBC 0 : return true;
8499 tgl@sss.pgh.pa.us 9086 :CBC 4 : break;
9087 : : }
9088 : : }
9089 : 4 : return false;
8499 tgl@sss.pgh.pa.us 9090 :UBC 0 : case T_FuncExpr:
9091 : : {
9092 : : /* special handling for casts and COERCE_SQL_SYNTAX */
8494 bruce@momjian.us 9093 : 0 : CoercionForm type = ((FuncExpr *) parentNode)->funcformat;
9094 : :
9095 [ # # # # ]: 0 : if (type == COERCE_EXPLICIT_CAST ||
1435 tgl@sss.pgh.pa.us 9096 [ # # ]: 0 : type == COERCE_IMPLICIT_CAST ||
9097 : : type == COERCE_SQL_SYNTAX)
8494 bruce@momjian.us 9098 : 0 : return false;
9099 : 0 : return true; /* own parentheses */
9100 : : }
2834 alvherre@alvh.no-ip. 9101 : 0 : case T_SubscriptingRef: /* other separators */
9102 : : case T_ArrayExpr: /* other separators */
9103 : : case T_RowExpr: /* other separators */
9104 : : case T_CoalesceExpr: /* own parentheses */
9105 : : case T_MinMaxExpr: /* own parentheses */
9106 : : case T_NullIfExpr: /* other separators */
9107 : : case T_Aggref: /* own parentheses */
9108 : : case T_GroupingFunc: /* own parentheses */
9109 : : case T_WindowFunc: /* own parentheses */
9110 : : case T_CaseExpr: /* other separators */
9111 : : case T_JsonExpr: /* own parentheses */
8499 tgl@sss.pgh.pa.us 9112 : 0 : return true;
16 9113 : 0 : case T_XmlExpr: /* own parentheses, except IS DOCUMENT */
9114 : 0 : return ((XmlExpr *) parentNode)->op != IS_DOCUMENT;
8499 tgl@sss.pgh.pa.us 9115 :CBC 4 : default:
9116 : 4 : return false;
9117 : : }
9118 : :
1317 alvherre@alvh.no-ip. 9119 : 4 : case T_JsonValueExpr:
9120 : : /* maybe simple, check args */
9121 : 4 : return isSimpleNode((Node *) ((JsonValueExpr *) node)->raw_expr,
9122 : : node, prettyFlags);
9123 : :
8499 tgl@sss.pgh.pa.us 9124 : 4 : default:
9125 : 4 : break;
9126 : : }
9127 : : /* those we don't know: in dubio complexo */
8494 bruce@momjian.us 9128 : 4 : return false;
9129 : : }
9130 : :
9131 : :
9132 : : /*
9133 : : * appendContextKeyword - append a keyword to buffer
9134 : : *
9135 : : * If prettyPrint is enabled, perform a line break, and adjust indentation.
9136 : : * Otherwise, just append the keyword.
9137 : : */
9138 : : static void
8499 tgl@sss.pgh.pa.us 9139 : 19728 : appendContextKeyword(deparse_context *context, const char *str,
9140 : : int indentBefore, int indentAfter, int indentPlus)
9141 : : {
4742 9142 : 19728 : StringInfo buf = context->buf;
9143 : :
8494 bruce@momjian.us 9144 [ + + ]: 19728 : if (PRETTY_INDENT(context))
9145 : : {
9146 : : int indentAmount;
9147 : :
8499 tgl@sss.pgh.pa.us 9148 : 18739 : context->indentLevel += indentBefore;
9149 : :
9150 : : /* remove any trailing spaces currently in the buffer ... */
4742 9151 : 18739 : removeStringInfoSpaces(buf);
9152 : : /* ... then add a newline and some spaces */
9153 : 18739 : appendStringInfoChar(buf, '\n');
9154 : :
4572 9155 [ + - ]: 18739 : if (context->indentLevel < PRETTYINDENT_LIMIT)
9156 : 18739 : indentAmount = Max(context->indentLevel, 0) + indentPlus;
9157 : : else
9158 : : {
9159 : : /*
9160 : : * If we're indented more than PRETTYINDENT_LIMIT characters, try
9161 : : * to conserve horizontal space by reducing the per-level
9162 : : * indentation. For best results the scale factor here should
9163 : : * divide all the indent amounts that get added to indentLevel
9164 : : * (PRETTYINDENT_STD, etc). It's important that the indentation
9165 : : * not grow unboundedly, else deeply-nested trees use O(N^2)
9166 : : * whitespace; so we also wrap modulo PRETTYINDENT_LIMIT.
9167 : : */
4572 tgl@sss.pgh.pa.us 9168 :UBC 0 : indentAmount = PRETTYINDENT_LIMIT +
9169 : 0 : (context->indentLevel - PRETTYINDENT_LIMIT) /
9170 : : (PRETTYINDENT_STD / 2);
9171 : 0 : indentAmount %= PRETTYINDENT_LIMIT;
9172 : : /* scale/wrap logic affects indentLevel, but not indentPlus */
9173 : 0 : indentAmount += indentPlus;
9174 : : }
4572 tgl@sss.pgh.pa.us 9175 :CBC 18739 : appendStringInfoSpaces(buf, indentAmount);
9176 : :
4742 9177 : 18739 : appendStringInfoString(buf, str);
9178 : :
8499 9179 : 18739 : context->indentLevel += indentAfter;
9180 [ - + ]: 18739 : if (context->indentLevel < 0)
8499 tgl@sss.pgh.pa.us 9181 :UBC 0 : context->indentLevel = 0;
9182 : : }
9183 : : else
4742 tgl@sss.pgh.pa.us 9184 :CBC 989 : appendStringInfoString(buf, str);
8499 9185 : 19728 : }
9186 : :
9187 : : /*
9188 : : * removeStringInfoSpaces - delete trailing spaces from a buffer.
9189 : : *
9190 : : * Possibly this should move to stringinfo.c at some point.
9191 : : */
9192 : : static void
4742 9193 : 19114 : removeStringInfoSpaces(StringInfo str)
9194 : : {
9195 [ + + + + ]: 29827 : while (str->len > 0 && str->data[str->len - 1] == ' ')
9196 : 10713 : str->data[--(str->len)] = '\0';
9197 : 19114 : }
9198 : :
9199 : :
9200 : : /*
9201 : : * get_rule_expr_paren - deparse expr using get_rule_expr,
9202 : : * embracing the string with parentheses if necessary for prettyPrint.
9203 : : *
9204 : : * Never embrace if prettyFlags=0, because it's done in the calling node.
9205 : : *
9206 : : * Any node that does *not* embrace its argument node by sql syntax (with
9207 : : * parentheses, non-operator keywords like CASE/WHEN/ON, or comma etc) should
9208 : : * use get_rule_expr_paren instead of get_rule_expr so parentheses can be
9209 : : * added.
9210 : : */
9211 : : static void
8494 bruce@momjian.us 9212 : 113586 : get_rule_expr_paren(Node *node, deparse_context *context,
9213 : : bool showimplicit, Node *parentNode)
9214 : : {
9215 : : bool need_paren;
9216 : :
8499 tgl@sss.pgh.pa.us 9217 [ + + ]: 117439 : need_paren = PRETTY_PAREN(context) &&
9218 [ + + ]: 3853 : !isSimpleNode(node, parentNode, context->prettyFlags);
9219 : :
9220 [ + + ]: 113586 : if (need_paren)
8494 bruce@momjian.us 9221 : 112 : appendStringInfoChar(context->buf, '(');
9222 : :
8499 tgl@sss.pgh.pa.us 9223 : 113586 : get_rule_expr(node, context, showimplicit);
9224 : :
9225 [ + + ]: 113586 : if (need_paren)
8494 bruce@momjian.us 9226 : 112 : appendStringInfoChar(context->buf, ')');
8499 tgl@sss.pgh.pa.us 9227 : 113586 : }
9228 : :
9229 : : static void
959 amitlan@postgresql.o 9230 : 72 : get_json_behavior(JsonBehavior *behavior, deparse_context *context,
9231 : : const char *on)
9232 : : {
9233 : : /*
9234 : : * The order of array elements must correspond to the order of
9235 : : * JsonBehaviorType members.
9236 : : */
9237 : 72 : const char *behavior_names[] =
9238 : : {
9239 : : " NULL",
9240 : : " ERROR",
9241 : : " EMPTY",
9242 : : " TRUE",
9243 : : " FALSE",
9244 : : " UNKNOWN",
9245 : : " EMPTY ARRAY",
9246 : : " EMPTY OBJECT",
9247 : : " DEFAULT "
9248 : : };
9249 : :
9250 [ + - - + ]: 72 : if ((int) behavior->btype < 0 || behavior->btype >= lengthof(behavior_names))
959 amitlan@postgresql.o 9251 [ # # ]:UBC 0 : elog(ERROR, "invalid json behavior type: %d", behavior->btype);
9252 : :
959 amitlan@postgresql.o 9253 :CBC 72 : appendStringInfoString(context->buf, behavior_names[behavior->btype]);
9254 : :
9255 [ + + ]: 72 : if (behavior->btype == JSON_BEHAVIOR_DEFAULT)
9256 : 12 : get_rule_expr(behavior->expr, context, false);
9257 : :
9258 : 72 : appendStringInfo(context->buf, " ON %s", on);
9259 : 72 : }
9260 : :
9261 : : /*
9262 : : * get_json_expr_options
9263 : : *
9264 : : * Parse back common options for JSON_QUERY, JSON_VALUE, JSON_EXISTS and
9265 : : * JSON_TABLE columns.
9266 : : */
9267 : : static void
9268 : 436 : get_json_expr_options(JsonExpr *jsexpr, deparse_context *context,
9269 : : JsonBehaviorType default_behavior)
9270 : : {
9271 [ + + ]: 436 : if (jsexpr->op == JSON_QUERY_OP)
9272 : : {
9273 [ + + ]: 192 : if (jsexpr->wrapper == JSW_CONDITIONAL)
939 drowley@postgresql.o 9274 : 8 : appendStringInfoString(context->buf, " WITH CONDITIONAL WRAPPER");
959 amitlan@postgresql.o 9275 [ + + ]: 184 : else if (jsexpr->wrapper == JSW_UNCONDITIONAL)
939 drowley@postgresql.o 9276 : 24 : appendStringInfoString(context->buf, " WITH UNCONDITIONAL WRAPPER");
9277 : : /* The default */
941 amitlan@postgresql.o 9278 [ + + + - ]: 160 : else if (jsexpr->wrapper == JSW_NONE || jsexpr->wrapper == JSW_UNSPEC)
939 drowley@postgresql.o 9279 : 160 : appendStringInfoString(context->buf, " WITHOUT WRAPPER");
9280 : :
959 amitlan@postgresql.o 9281 [ + + ]: 192 : if (jsexpr->omit_quotes)
939 drowley@postgresql.o 9282 : 36 : appendStringInfoString(context->buf, " OMIT QUOTES");
9283 : : /* The default */
9284 : : else
9285 : 156 : appendStringInfoString(context->buf, " KEEP QUOTES");
9286 : : }
9287 : :
959 amitlan@postgresql.o 9288 [ + + + + ]: 436 : if (jsexpr->on_empty && jsexpr->on_empty->btype != default_behavior)
9289 : 24 : get_json_behavior(jsexpr->on_empty, context, "EMPTY");
9290 : :
9291 [ + - + + ]: 436 : if (jsexpr->on_error && jsexpr->on_error->btype != default_behavior)
9292 : 40 : get_json_behavior(jsexpr->on_error, context, "ERROR");
9293 : 436 : }
9294 : :
9295 : : /* ----------
9296 : : * get_rule_expr - Parse back an expression
9297 : : *
9298 : : * Note: showimplicit determines whether we display any implicit cast that
9299 : : * is present at the top of the expression tree. It is a passed argument,
9300 : : * not a field of the context struct, because we change the value as we
9301 : : * recurse down into the expression. In general we suppress implicit casts
9302 : : * when the result type is known with certainty (eg, the arguments of an
9303 : : * OR must be boolean). We display implicit casts for arguments of functions
9304 : : * and operators, since this is needed to be certain that the same function
9305 : : * or operator will be chosen when the expression is re-parsed.
9306 : : * ----------
9307 : : */
9308 : : static void
8813 tgl@sss.pgh.pa.us 9309 : 251897 : get_rule_expr(Node *node, deparse_context *context,
9310 : : bool showimplicit)
9311 : : {
9895 9312 : 251897 : StringInfo buf = context->buf;
9313 : :
10292 bruce@momjian.us 9314 [ + + ]: 251897 : if (node == NULL)
9896 tgl@sss.pgh.pa.us 9315 : 72 : return;
9316 : :
9317 : : /* Guard against excessively long or deeply-nested queries */
4572 9318 [ - + ]: 251825 : CHECK_FOR_INTERRUPTS();
9319 : 251825 : check_stack_depth();
9320 : :
9321 : : /*
9322 : : * Each level of get_rule_expr must emit an indivisible term
9323 : : * (parenthesized if necessary) to ensure result is reparsed into the same
9324 : : * expression tree. The only exception is that when the input is a List,
9325 : : * we emit the component items comma-separated with no surrounding
9326 : : * decoration; this is convenient for most callers.
9327 : : */
10292 bruce@momjian.us 9328 [ + + + + : 251825 : switch (nodeTag(node))
+ + + + +
+ + + + +
+ + + - +
+ + + + +
+ + - + +
+ + + + +
+ + + + +
+ - + + +
+ + + + +
+ - ]
9329 : : {
9931 tgl@sss.pgh.pa.us 9330 : 123848 : case T_Var:
5305 9331 : 123848 : (void) get_variable((Var *) node, 0, false, context);
10292 bruce@momjian.us 9332 : 123848 : break;
9333 : :
8729 tgl@sss.pgh.pa.us 9334 : 43173 : case T_Const:
6878 9335 : 43173 : get_const_expr((Const *) node, context, 0);
8729 9336 : 43173 : break;
9337 : :
9338 : 5650 : case T_Param:
5959 9339 : 5650 : get_parameter((Param *) node, context);
10292 bruce@momjian.us 9340 : 5650 : break;
9341 : :
9931 tgl@sss.pgh.pa.us 9342 : 2761 : case T_Aggref:
3844 rhaas@postgresql.org 9343 : 2761 : get_agg_expr((Aggref *) node, context, (Aggref *) node);
9931 tgl@sss.pgh.pa.us 9344 : 2761 : break;
9345 : :
4191 andres@anarazel.de 9346 : 74 : case T_GroupingFunc:
9347 : : {
9348 : 74 : GroupingFunc *gexpr = (GroupingFunc *) node;
9349 : :
9350 : 74 : appendStringInfoString(buf, "GROUPING(");
9351 : 74 : get_rule_expr((Node *) gexpr->args, context, true);
9352 : 74 : appendStringInfoChar(buf, ')');
9353 : : }
9354 : 74 : break;
9355 : :
6521 tgl@sss.pgh.pa.us 9356 : 254 : case T_WindowFunc:
9357 : 254 : get_windowfunc_expr((WindowFunc *) node, context);
9358 : 254 : break;
9359 : :
963 dean.a.rasheed@gmail 9360 : 4 : case T_MergeSupportFunc:
9361 : 4 : appendStringInfoString(buf, "MERGE_ACTION()");
9362 : 4 : break;
9363 : :
2834 alvherre@alvh.no-ip. 9364 : 236 : case T_SubscriptingRef:
9365 : : {
9366 : 236 : SubscriptingRef *sbsref = (SubscriptingRef *) node;
9367 : : bool need_parens;
9368 : :
9369 : : /*
9370 : : * If the argument is a CaseTestExpr, we must be inside a
9371 : : * FieldStore, ie, we are assigning to an element of an array
9372 : : * within a composite column. Since we already punted on
9373 : : * displaying the FieldStore's target information, just punt
9374 : : * here too, and display only the assignment source
9375 : : * expression.
9376 : : */
9377 [ - + ]: 236 : if (IsA(sbsref->refexpr, CaseTestExpr))
9378 : : {
2834 alvherre@alvh.no-ip. 9379 [ # # ]:UBC 0 : Assert(sbsref->refassgnexpr);
9380 : 0 : get_rule_expr((Node *) sbsref->refassgnexpr,
9381 : : context, showimplicit);
6104 tgl@sss.pgh.pa.us 9382 : 0 : break;
9383 : : }
9384 : :
9385 : : /*
9386 : : * Parenthesize the argument unless it's a simple Var or a
9387 : : * FieldSelect. (In particular, if it's another
9388 : : * SubscriptingRef, we *must* parenthesize to avoid
9389 : : * confusion.)
9390 : : */
2834 alvherre@alvh.no-ip. 9391 [ + + ]:CBC 366 : need_parens = !IsA(sbsref->refexpr, Var) &&
9392 [ + + ]: 130 : !IsA(sbsref->refexpr, FieldSelect);
8612 tgl@sss.pgh.pa.us 9393 [ + + ]: 236 : if (need_parens)
9394 : 60 : appendStringInfoChar(buf, '(');
2834 alvherre@alvh.no-ip. 9395 : 236 : get_rule_expr((Node *) sbsref->refexpr, context, showimplicit);
8612 tgl@sss.pgh.pa.us 9396 [ + + ]: 236 : if (need_parens)
9397 : 60 : appendStringInfoChar(buf, ')');
9398 : :
9399 : : /*
9400 : : * If there's a refassgnexpr, we want to print the node in the
9401 : : * format "container[subscripts] := refassgnexpr". This is
9402 : : * not legal SQL, so decompilation of INSERT or UPDATE
9403 : : * statements should always use processIndirection as part of
9404 : : * the statement-level syntax. We should only see this when
9405 : : * EXPLAIN tries to print the targetlist of a plan resulting
9406 : : * from such a statement.
9407 : : */
2834 alvherre@alvh.no-ip. 9408 [ + + ]: 236 : if (sbsref->refassgnexpr)
9409 : : {
9410 : : Node *refassgnexpr;
9411 : :
9412 : : /*
9413 : : * Use processIndirection to print this node's subscripts
9414 : : * as well as any additional field selections or
9415 : : * subscripting in immediate descendants. It returns the
9416 : : * RHS expr that is actually being "assigned".
9417 : : */
3746 tgl@sss.pgh.pa.us 9418 : 8 : refassgnexpr = processIndirection(node, context);
6104 9419 : 8 : appendStringInfoString(buf, " := ");
9420 : 8 : get_rule_expr(refassgnexpr, context, showimplicit);
9421 : : }
9422 : : else
9423 : : {
9424 : : /* Just an ordinary container fetch, so print subscripts */
2834 alvherre@alvh.no-ip. 9425 : 228 : printSubscripts(sbsref, context);
9426 : : }
9427 : : }
8729 tgl@sss.pgh.pa.us 9428 : 236 : break;
9429 : :
9430 : 8185 : case T_FuncExpr:
9431 : 8185 : get_func_expr((FuncExpr *) node, context, showimplicit);
9432 : 8185 : break;
9433 : :
6237 9434 : 20 : case T_NamedArgExpr:
9435 : : {
9436 : 20 : NamedArgExpr *na = (NamedArgExpr *) node;
9437 : :
4206 rhaas@postgresql.org 9438 : 20 : appendStringInfo(buf, "%s => ", quote_identifier(na->name));
6237 tgl@sss.pgh.pa.us 9439 : 20 : get_rule_expr((Node *) na->arg, context, showimplicit);
9440 : : }
9441 : 20 : break;
9442 : :
8729 9443 : 42639 : case T_OpExpr:
9444 : 42639 : get_oper_expr((OpExpr *) node, context);
9445 : 42639 : break;
9446 : :
9447 : 16 : case T_DistinctExpr:
9448 : : {
9449 : 16 : DistinctExpr *expr = (DistinctExpr *) node;
9450 : 16 : List *args = expr->args;
8198 neilc@samurai.com 9451 : 16 : Node *arg1 = (Node *) linitial(args);
8530 tgl@sss.pgh.pa.us 9452 : 16 : Node *arg2 = (Node *) lsecond(args);
9453 : :
8499 9454 [ + + ]: 16 : if (!PRETTY_PAREN(context))
8494 bruce@momjian.us 9455 : 12 : appendStringInfoChar(buf, '(');
8499 tgl@sss.pgh.pa.us 9456 : 16 : get_rule_expr_paren(arg1, context, true, node);
4753 rhaas@postgresql.org 9457 : 16 : appendStringInfoString(buf, " IS DISTINCT FROM ");
8499 tgl@sss.pgh.pa.us 9458 : 16 : get_rule_expr_paren(arg2, context, true, node);
9459 [ + + ]: 16 : if (!PRETTY_PAREN(context))
8494 bruce@momjian.us 9460 : 12 : appendStringInfoChar(buf, ')');
9461 : : }
8530 tgl@sss.pgh.pa.us 9462 : 16 : break;
9463 : :
5710 9464 : 140 : case T_NullIfExpr:
9465 : : {
9466 : 140 : NullIfExpr *nullifexpr = (NullIfExpr *) node;
9467 : :
4753 rhaas@postgresql.org 9468 : 140 : appendStringInfoString(buf, "NULLIF(");
5710 tgl@sss.pgh.pa.us 9469 : 140 : get_rule_expr((Node *) nullifexpr->args, context, true);
9470 : 140 : appendStringInfoChar(buf, ')');
9471 : : }
9472 : 140 : break;
9473 : :
8530 9474 : 2058 : case T_ScalarArrayOpExpr:
9475 : : {
9476 : 2058 : ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
9477 : 2058 : List *args = expr->args;
8198 neilc@samurai.com 9478 : 2058 : Node *arg1 = (Node *) linitial(args);
8530 tgl@sss.pgh.pa.us 9479 : 2058 : Node *arg2 = (Node *) lsecond(args);
9480 : :
8499 9481 [ + + ]: 2058 : if (!PRETTY_PAREN(context))
8494 bruce@momjian.us 9482 : 2050 : appendStringInfoChar(buf, '(');
8499 tgl@sss.pgh.pa.us 9483 : 2058 : get_rule_expr_paren(arg1, context, true, node);
8530 9484 : 2058 : appendStringInfo(buf, " %s %s (",
9485 : : generate_operator_name(expr->opno,
9486 : : exprType(arg1),
9487 : : get_base_element_type(exprType(arg2))),
9488 [ + + ]: 2058 : expr->useOr ? "ANY" : "ALL");
8499 9489 : 2058 : get_rule_expr_paren(arg2, context, true, node);
9490 : :
9491 : : /*
9492 : : * There's inherent ambiguity in "x op ANY/ALL (y)" when y is
9493 : : * a bare sub-SELECT. Since we're here, the sub-SELECT must
9494 : : * be meant as a scalar sub-SELECT yielding an array value to
9495 : : * be used in ScalarArrayOpExpr; but the grammar will
9496 : : * preferentially interpret such a construct as an ANY/ALL
9497 : : * SubLink. To prevent misparsing the output that way, insert
9498 : : * a dummy coercion (which will be stripped by parse analysis,
9499 : : * so no inefficiency is added in dump and reload). This is
9500 : : * indeed most likely what the user wrote to get the construct
9501 : : * accepted in the first place.
9502 : : */
3850 9503 [ + + ]: 2058 : if (IsA(arg2, SubLink) &&
9504 [ + - ]: 4 : ((SubLink *) arg2)->subLinkType == EXPR_SUBLINK)
9505 : 4 : appendStringInfo(buf, "::%s",
9506 : : format_type_with_typemod(exprType(arg2),
9507 : : exprTypmod(arg2)));
8499 9508 : 2058 : appendStringInfoChar(buf, ')');
9509 [ + + ]: 2058 : if (!PRETTY_PAREN(context))
8494 bruce@momjian.us 9510 : 2050 : appendStringInfoChar(buf, ')');
9511 : : }
10292 9512 : 2058 : break;
9513 : :
8729 tgl@sss.pgh.pa.us 9514 : 7677 : case T_BoolExpr:
9515 : : {
9516 : 7677 : BoolExpr *expr = (BoolExpr *) node;
8198 neilc@samurai.com 9517 : 7677 : Node *first_arg = linitial(expr->args);
9518 : : ListCell *arg;
9519 : :
8729 tgl@sss.pgh.pa.us 9520 [ + + + - ]: 7677 : switch (expr->boolop)
9521 : : {
9522 : 6120 : case AND_EXPR:
8499 9523 [ + + ]: 6120 : if (!PRETTY_PAREN(context))
8494 bruce@momjian.us 9524 : 6084 : appendStringInfoChar(buf, '(');
8198 neilc@samurai.com 9525 : 6120 : get_rule_expr_paren(first_arg, context,
9526 : : false, node);
2229 tgl@sss.pgh.pa.us 9527 [ + - + + : 13893 : for_each_from(arg, expr->args, 1)
+ + ]
9528 : : {
4753 rhaas@postgresql.org 9529 : 7773 : appendStringInfoString(buf, " AND ");
8198 neilc@samurai.com 9530 : 7773 : get_rule_expr_paren((Node *) lfirst(arg), context,
9531 : : false, node);
9532 : : }
8499 tgl@sss.pgh.pa.us 9533 [ + + ]: 6120 : if (!PRETTY_PAREN(context))
8494 bruce@momjian.us 9534 : 6084 : appendStringInfoChar(buf, ')');
8729 tgl@sss.pgh.pa.us 9535 : 6120 : break;
9536 : :
9537 : 1258 : case OR_EXPR:
8499 9538 [ + + ]: 1258 : if (!PRETTY_PAREN(context))
8494 bruce@momjian.us 9539 : 1246 : appendStringInfoChar(buf, '(');
8198 neilc@samurai.com 9540 : 1258 : get_rule_expr_paren(first_arg, context,
9541 : : false, node);
2229 tgl@sss.pgh.pa.us 9542 [ + - + + : 2991 : for_each_from(arg, expr->args, 1)
+ + ]
9543 : : {
4753 rhaas@postgresql.org 9544 : 1733 : appendStringInfoString(buf, " OR ");
8198 neilc@samurai.com 9545 : 1733 : get_rule_expr_paren((Node *) lfirst(arg), context,
9546 : : false, node);
9547 : : }
8499 tgl@sss.pgh.pa.us 9548 [ + + ]: 1258 : if (!PRETTY_PAREN(context))
8494 bruce@momjian.us 9549 : 1246 : appendStringInfoChar(buf, ')');
8729 tgl@sss.pgh.pa.us 9550 : 1258 : break;
9551 : :
9552 : 299 : case NOT_EXPR:
8499 9553 [ + + ]: 299 : if (!PRETTY_PAREN(context))
8494 bruce@momjian.us 9554 : 291 : appendStringInfoChar(buf, '(');
4753 rhaas@postgresql.org 9555 : 299 : appendStringInfoString(buf, "NOT ");
8198 neilc@samurai.com 9556 : 299 : get_rule_expr_paren(first_arg, context,
9557 : : false, node);
8499 tgl@sss.pgh.pa.us 9558 [ + + ]: 299 : if (!PRETTY_PAREN(context))
8494 bruce@momjian.us 9559 : 291 : appendStringInfoChar(buf, ')');
8729 tgl@sss.pgh.pa.us 9560 : 299 : break;
9561 : :
8729 tgl@sss.pgh.pa.us 9562 :UBC 0 : default:
8502 9563 [ # # ]: 0 : elog(ERROR, "unrecognized boolop: %d",
9564 : : (int) expr->boolop);
9565 : : }
9566 : : }
8729 tgl@sss.pgh.pa.us 9567 :CBC 7677 : break;
9568 : :
9569 : 306 : case T_SubLink:
9570 : 306 : get_sublink_expr((SubLink *) node, context);
9571 : 306 : break;
9572 : :
8727 9573 : 761 : case T_SubPlan:
9574 : : {
6356 bruce@momjian.us 9575 : 761 : SubPlan *subplan = (SubPlan *) node;
9576 : :
9577 : : /*
9578 : : * We cannot see an already-planned subplan in rule deparsing,
9579 : : * only while EXPLAINing a query plan. We don't try to
9580 : : * reconstruct the original SQL, just reference the subplan
9581 : : * that appears elsewhere in EXPLAIN's result. It does seem
9582 : : * useful to show the subLinkType and testexpr (if any), and
9583 : : * we also note whether the subplan will be hashed.
9584 : : */
961 tgl@sss.pgh.pa.us 9585 [ + + + + : 761 : switch (subplan->subLinkType)
+ + + -
- ]
9586 : : {
9587 : 70 : case EXISTS_SUBLINK:
9588 : 70 : appendStringInfoString(buf, "EXISTS(");
9589 [ - + ]: 70 : Assert(subplan->testexpr == NULL);
9590 : 70 : break;
9591 : 12 : case ALL_SUBLINK:
9592 : 12 : appendStringInfoString(buf, "(ALL ");
9593 [ - + ]: 12 : Assert(subplan->testexpr != NULL);
9594 : 12 : break;
9595 : 188 : case ANY_SUBLINK:
9596 : 188 : appendStringInfoString(buf, "(ANY ");
9597 [ - + ]: 188 : Assert(subplan->testexpr != NULL);
9598 : 188 : break;
9599 : 4 : case ROWCOMPARE_SUBLINK:
9600 : : /* Parenthesizing the testexpr seems sufficient */
9601 : 4 : appendStringInfoChar(buf, '(');
9602 [ - + ]: 4 : Assert(subplan->testexpr != NULL);
9603 : 4 : break;
9604 : 462 : case EXPR_SUBLINK:
9605 : : /* No need to decorate these subplan references */
9606 : 462 : appendStringInfoChar(buf, '(');
9607 [ - + ]: 462 : Assert(subplan->testexpr == NULL);
9608 : 462 : break;
9609 : 17 : case MULTIEXPR_SUBLINK:
9610 : : /* MULTIEXPR isn't executed in the normal way */
9611 : 17 : appendStringInfoString(buf, "(rescan ");
9612 [ - + ]: 17 : Assert(subplan->testexpr == NULL);
9613 : 17 : break;
9614 : 8 : case ARRAY_SUBLINK:
9615 : 8 : appendStringInfoString(buf, "ARRAY(");
9616 [ - + ]: 8 : Assert(subplan->testexpr == NULL);
9617 : 8 : break;
961 tgl@sss.pgh.pa.us 9618 :UBC 0 : case CTE_SUBLINK:
9619 : : /* This case is unreachable within expressions */
9620 : 0 : appendStringInfoString(buf, "CTE(");
9621 [ # # ]: 0 : Assert(subplan->testexpr == NULL);
9622 : 0 : break;
9623 : : }
9624 : :
961 tgl@sss.pgh.pa.us 9625 [ + + ]:CBC 761 : if (subplan->testexpr != NULL)
9626 : : {
9627 : : deparse_namespace *dpns;
9628 : :
9629 : : /*
9630 : : * Push SubPlan into ancestors list while deparsing
9631 : : * testexpr, so that we can handle PARAM_EXEC references
9632 : : * to the SubPlan's paramIds. (This makes it look like
9633 : : * the SubPlan is an "ancestor" of the current plan node,
9634 : : * which is a little weird, but it does no harm.) In this
9635 : : * path, we don't need to mention the SubPlan explicitly,
9636 : : * because the referencing Params will show its existence.
9637 : : */
9638 : 204 : dpns = (deparse_namespace *) linitial(context->namespaces);
9639 : 204 : dpns->ancestors = lcons(subplan, dpns->ancestors);
9640 : :
9641 : 204 : get_rule_expr(subplan->testexpr, context, showimplicit);
9642 : 204 : appendStringInfoChar(buf, ')');
9643 : :
9644 : 204 : dpns->ancestors = list_delete_first(dpns->ancestors);
9645 : : }
9646 : : else
9647 : : {
9648 : : const char *nameprefix;
9649 : :
9650 : : /* No referencing Params, so show the SubPlan's name */
394 rhaas@postgresql.org 9651 [ - + ]: 557 : if (subplan->isInitPlan)
394 rhaas@postgresql.org 9652 :UBC 0 : nameprefix = "InitPlan ";
9653 : : else
394 rhaas@postgresql.org 9654 :CBC 557 : nameprefix = "SubPlan ";
961 tgl@sss.pgh.pa.us 9655 [ - + ]: 557 : if (subplan->useHashTable)
394 rhaas@postgresql.org 9656 :UBC 0 : appendStringInfo(buf, "hashed %s%s)",
9657 : : nameprefix, subplan->plan_name);
9658 : : else
394 rhaas@postgresql.org 9659 :CBC 557 : appendStringInfo(buf, "%s%s)",
9660 : : nameprefix, subplan->plan_name);
9661 : : }
9662 : : }
8729 tgl@sss.pgh.pa.us 9663 : 761 : break;
9664 : :
6649 tgl@sss.pgh.pa.us 9665 :UBC 0 : case T_AlternativeSubPlan:
9666 : : {
6423 9667 : 0 : AlternativeSubPlan *asplan = (AlternativeSubPlan *) node;
9668 : : ListCell *lc;
9669 : :
9670 : : /*
9671 : : * This case cannot be reached in normal usage, since no
9672 : : * AlternativeSubPlan can appear either in parsetrees or
9673 : : * finished plan trees. We keep it just in case somebody
9674 : : * wants to use this code to print planner data structures.
9675 : : */
4753 rhaas@postgresql.org 9676 : 0 : appendStringInfoString(buf, "(alternatives: ");
6423 tgl@sss.pgh.pa.us 9677 [ # # # # : 0 : foreach(lc, asplan->subplans)
# # ]
9678 : : {
3496 9679 : 0 : SubPlan *splan = lfirst_node(SubPlan, lc);
9680 : : const char *nameprefix;
9681 : :
394 rhaas@postgresql.org 9682 [ # # ]: 0 : if (splan->isInitPlan)
9683 : 0 : nameprefix = "InitPlan ";
9684 : : else
9685 : 0 : nameprefix = "SubPlan ";
6423 tgl@sss.pgh.pa.us 9686 [ # # ]: 0 : if (splan->useHashTable)
394 rhaas@postgresql.org 9687 : 0 : appendStringInfo(buf, "hashed %s%s", nameprefix,
9688 : : splan->plan_name);
9689 : : else
9690 : 0 : appendStringInfo(buf, "%s%s", nameprefix,
9691 : : splan->plan_name);
2670 tgl@sss.pgh.pa.us 9692 [ # # ]: 0 : if (lnext(asplan->subplans, lc))
4753 rhaas@postgresql.org 9693 : 0 : appendStringInfoString(buf, " or ");
9694 : : }
9695 : 0 : appendStringInfoChar(buf, ')');
9696 : : }
6649 tgl@sss.pgh.pa.us 9697 : 0 : break;
9698 : :
9585 tgl@sss.pgh.pa.us 9699 :CBC 934 : case T_FieldSelect:
9700 : : {
9701 : 934 : FieldSelect *fselect = (FieldSelect *) node;
7828 9702 : 934 : Node *arg = (Node *) fselect->arg;
9703 : 934 : int fno = fselect->fieldnum;
9704 : : const char *fieldname;
9705 : : bool need_parens;
9706 : :
9707 : : /*
9708 : : * Parenthesize the argument unless it's a SubscriptingRef or
9709 : : * another FieldSelect. Note in particular that it would be
9710 : : * WRONG to not parenthesize a Var argument; simplicity is not
9711 : : * the issue here, having the right number of names is.
9712 : : */
2834 alvherre@alvh.no-ip. 9713 [ + + ]: 1844 : need_parens = !IsA(arg, SubscriptingRef) &&
9714 [ + - ]: 910 : !IsA(arg, FieldSelect);
8184 tgl@sss.pgh.pa.us 9715 [ + + ]: 934 : if (need_parens)
9716 : 910 : appendStringInfoChar(buf, '(');
7828 9717 : 934 : get_rule_expr(arg, context, true);
8184 9718 [ + + ]: 934 : if (need_parens)
9719 : 910 : appendStringInfoChar(buf, ')');
9720 : :
9721 : : /*
9722 : : * Get and print the field name.
9723 : : */
7195 9724 : 934 : fieldname = get_name_for_var_field((Var *) arg, fno,
9725 : : 0, context);
8499 9726 : 934 : appendStringInfo(buf, ".%s", quote_identifier(fieldname));
9727 : : }
9585 9728 : 934 : break;
9729 : :
8184 9730 : 4 : case T_FieldStore:
9731 : : {
6104 9732 : 4 : FieldStore *fstore = (FieldStore *) node;
9733 : : bool need_parens;
9734 : :
9735 : : /*
9736 : : * There is no good way to represent a FieldStore as real SQL,
9737 : : * so decompilation of INSERT or UPDATE statements should
9738 : : * always use processIndirection as part of the
9739 : : * statement-level syntax. We should only get here when
9740 : : * EXPLAIN tries to print the targetlist of a plan resulting
9741 : : * from such a statement. The plan case is even harder than
9742 : : * ordinary rules would be, because the planner tries to
9743 : : * collapse multiple assignments to the same field or subfield
9744 : : * into one FieldStore; so we can see a list of target fields
9745 : : * not just one, and the arguments could be FieldStores
9746 : : * themselves. We don't bother to try to print the target
9747 : : * field names; we just print the source arguments, with a
9748 : : * ROW() around them if there's more than one. This isn't
9749 : : * terribly complete, but it's probably good enough for
9750 : : * EXPLAIN's purposes; especially since anything more would be
9751 : : * either hopelessly confusing or an even poorer
9752 : : * representation of what the plan is actually doing.
9753 : : */
9754 : 4 : need_parens = (list_length(fstore->newvals) != 1);
9755 [ + - ]: 4 : if (need_parens)
9756 : 4 : appendStringInfoString(buf, "ROW(");
9757 : 4 : get_rule_expr((Node *) fstore->newvals, context, showimplicit);
9758 [ + - ]: 4 : if (need_parens)
9759 : 4 : appendStringInfoChar(buf, ')');
9760 : : }
8184 9761 : 4 : break;
9762 : :
9755 9763 : 1949 : case T_RelabelType:
9764 : : {
9765 : 1949 : RelabelType *relabel = (RelabelType *) node;
8494 bruce@momjian.us 9766 : 1949 : Node *arg = (Node *) relabel->arg;
9767 : :
8813 tgl@sss.pgh.pa.us 9768 [ + + ]: 1949 : if (relabel->relabelformat == COERCE_IMPLICIT_CAST &&
9769 [ + + ]: 1782 : !showimplicit)
9770 : : {
9771 : : /* don't show the implicit cast */
8177 9772 : 48 : get_rule_expr_paren(arg, context, false, node);
9773 : : }
9774 : : else
9775 : : {
7173 9776 : 1901 : get_coercion_expr(arg, context,
9777 : : relabel->resulttype,
9778 : : relabel->resulttypmod,
9779 : : node);
9780 : : }
9781 : : }
9755 9782 : 1949 : break;
9783 : :
7093 9784 : 527 : case T_CoerceViaIO:
9785 : : {
9786 : 527 : CoerceViaIO *iocoerce = (CoerceViaIO *) node;
9787 : 527 : Node *arg = (Node *) iocoerce->arg;
9788 : :
9789 [ + + ]: 527 : if (iocoerce->coerceformat == COERCE_IMPLICIT_CAST &&
9790 [ + - ]: 24 : !showimplicit)
9791 : : {
9792 : : /* don't show the implicit cast */
9793 : 24 : get_rule_expr_paren(arg, context, false, node);
9794 : : }
9795 : : else
9796 : : {
9797 : 503 : get_coercion_expr(arg, context,
9798 : : iocoerce->resulttype,
9799 : : -1,
9800 : : node);
9801 : : }
9802 : : }
9803 : 527 : break;
9804 : :
7163 9805 : 43 : case T_ArrayCoerceExpr:
9806 : : {
9807 : 43 : ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
9808 : 43 : Node *arg = (Node *) acoerce->arg;
9809 : :
9810 [ + + ]: 43 : if (acoerce->coerceformat == COERCE_IMPLICIT_CAST &&
9811 [ + + ]: 37 : !showimplicit)
9812 : : {
9813 : : /* don't show the implicit cast */
9814 : 4 : get_rule_expr_paren(arg, context, false, node);
9815 : : }
9816 : : else
9817 : : {
9818 : 39 : get_coercion_expr(arg, context,
9819 : : acoerce->resulttype,
9820 : : acoerce->resulttypmod,
9821 : : node);
9822 : : }
9823 : : }
9824 : 43 : break;
9825 : :
7999 9826 : 53 : case T_ConvertRowtypeExpr:
9827 : : {
9828 : 53 : ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) node;
9829 : 53 : Node *arg = (Node *) convert->arg;
9830 : :
9831 [ + + ]: 53 : if (convert->convertformat == COERCE_IMPLICIT_CAST &&
9832 [ + + ]: 49 : !showimplicit)
9833 : : {
9834 : : /* don't show the implicit cast */
9835 : 12 : get_rule_expr_paren(arg, context, false, node);
9836 : : }
9837 : : else
9838 : : {
7173 9839 : 41 : get_coercion_expr(arg, context,
9840 : : convert->resulttype, -1,
9841 : : node);
9842 : : }
9843 : : }
7999 9844 : 53 : break;
9845 : :
5718 9846 : 60 : case T_CollateExpr:
9847 : : {
9848 : 60 : CollateExpr *collate = (CollateExpr *) node;
9849 : 60 : Node *arg = (Node *) collate->arg;
9850 : :
9851 [ + + ]: 60 : if (!PRETTY_PAREN(context))
9852 : 56 : appendStringInfoChar(buf, '(');
9853 : 60 : get_rule_expr_paren(arg, context, showimplicit, node);
9854 : 60 : appendStringInfo(buf, " COLLATE %s",
9855 : : generate_collation_name(collate->collOid));
9856 [ + + ]: 60 : if (!PRETTY_PAREN(context))
9857 : 56 : appendStringInfoChar(buf, ')');
9858 : : }
9859 : 60 : break;
9860 : :
9931 9861 : 504 : case T_CaseExpr:
9862 : : {
9863 : 504 : CaseExpr *caseexpr = (CaseExpr *) node;
9864 : : ListCell *temp;
9865 : :
8499 9866 : 504 : appendContextKeyword(context, "CASE",
9867 : : 0, PRETTYINDENT_VAR, 0);
8268 9868 [ + + ]: 504 : if (caseexpr->arg)
9869 : : {
9870 : 207 : appendStringInfoChar(buf, ' ');
9871 : 207 : get_rule_expr((Node *) caseexpr->arg, context, true);
9872 : : }
9931 9873 [ + - + + : 2042 : foreach(temp, caseexpr->args)
+ + ]
9874 : : {
9875 : 1538 : CaseWhen *when = (CaseWhen *) lfirst(temp);
7635 9876 : 1538 : Node *w = (Node *) when->expr;
9877 : :
8268 9878 [ + + ]: 1538 : if (caseexpr->arg)
9879 : : {
9880 : : /*
9881 : : * The parser should have produced WHEN clauses of the
9882 : : * form "CaseTestExpr = RHS", possibly with an
9883 : : * implicit coercion inserted above the CaseTestExpr.
9884 : : * For accurate decompilation of rules it's essential
9885 : : * that we show just the RHS. However in an
9886 : : * expression that's been through the optimizer, the
9887 : : * WHEN clause could be almost anything (since the
9888 : : * equality operator could have been expanded into an
9889 : : * inline function). If we don't recognize the form
9890 : : * of the WHEN clause, just punt and display it as-is.
9891 : : */
7635 9892 [ + - ]: 639 : if (IsA(w, OpExpr))
9893 : : {
6462 9894 : 639 : List *args = ((OpExpr *) w)->args;
9895 : :
5642 9896 [ + - ]: 639 : if (list_length(args) == 2 &&
9897 [ + - ]: 639 : IsA(strip_implicit_coercions(linitial(args)),
9898 : : CaseTestExpr))
9899 : 639 : w = (Node *) lsecond(args);
9900 : : }
9901 : : }
9902 : :
9903 [ + + ]: 1538 : if (!PRETTY_INDENT(context))
9904 : 120 : appendStringInfoChar(buf, ' ');
9905 : 1538 : appendContextKeyword(context, "WHEN ",
9906 : : 0, 0, 0);
9907 : 1538 : get_rule_expr(w, context, false);
4753 rhaas@postgresql.org 9908 : 1538 : appendStringInfoString(buf, " THEN ");
8729 tgl@sss.pgh.pa.us 9909 : 1538 : get_rule_expr((Node *) when->result, context, true);
9910 : : }
8499 9911 [ + + ]: 504 : if (!PRETTY_INDENT(context))
8494 bruce@momjian.us 9912 : 115 : appendStringInfoChar(buf, ' ');
8499 tgl@sss.pgh.pa.us 9913 : 504 : appendContextKeyword(context, "ELSE ",
9914 : : 0, 0, 0);
8729 9915 : 504 : get_rule_expr((Node *) caseexpr->defresult, context, true);
8499 9916 [ + + ]: 504 : if (!PRETTY_INDENT(context))
8494 bruce@momjian.us 9917 : 115 : appendStringInfoChar(buf, ' ');
8499 tgl@sss.pgh.pa.us 9918 : 504 : appendContextKeyword(context, "END",
9919 : : -PRETTYINDENT_VAR, 0, 0);
9920 : : }
10292 bruce@momjian.us 9921 : 504 : break;
9922 : :
5642 tgl@sss.pgh.pa.us 9923 :UBC 0 : case T_CaseTestExpr:
9924 : : {
9925 : : /*
9926 : : * Normally we should never get here, since for expressions
9927 : : * that can contain this node type we attempt to avoid
9928 : : * recursing to it. But in an optimized expression we might
9929 : : * be unable to avoid that (see comments for CaseExpr). If we
9930 : : * do see one, print it as CASE_TEST_EXPR.
9931 : : */
4753 rhaas@postgresql.org 9932 : 0 : appendStringInfoString(buf, "CASE_TEST_EXPR");
9933 : : }
5642 tgl@sss.pgh.pa.us 9934 : 0 : break;
9935 : :
8612 tgl@sss.pgh.pa.us 9936 :CBC 372 : case T_ArrayExpr:
9937 : : {
8494 bruce@momjian.us 9938 : 372 : ArrayExpr *arrayexpr = (ArrayExpr *) node;
9939 : :
4753 rhaas@postgresql.org 9940 : 372 : appendStringInfoString(buf, "ARRAY[");
8064 tgl@sss.pgh.pa.us 9941 : 372 : get_rule_expr((Node *) arrayexpr->elements, context, true);
9942 : 372 : appendStringInfoChar(buf, ']');
9943 : :
9944 : : /*
9945 : : * If the array isn't empty, we assume its elements are
9946 : : * coerced to the desired type. If it's empty, though, we
9947 : : * need an explicit coercion to the array type.
9948 : : */
6530 9949 [ + + ]: 372 : if (arrayexpr->elements == NIL)
9950 : 4 : appendStringInfo(buf, "::%s",
9951 : : format_type_with_typemod(arrayexpr->array_typeid, -1));
9952 : : }
8612 9953 : 372 : break;
9954 : :
8214 9955 : 230 : case T_RowExpr:
9956 : : {
8103 bruce@momjian.us 9957 : 230 : RowExpr *rowexpr = (RowExpr *) node;
8115 tgl@sss.pgh.pa.us 9958 : 230 : TupleDesc tupdesc = NULL;
9959 : : ListCell *arg;
9960 : : int i;
9961 : : char *sep;
9962 : :
9963 : : /*
9964 : : * If it's a named type and not RECORD, we may have to skip
9965 : : * dropped columns and/or claim there are NULLs for added
9966 : : * columns.
9967 : : */
9968 [ + + ]: 230 : if (rowexpr->row_typeid != RECORDOID)
9969 : : {
9970 : 44 : tupdesc = lookup_rowtype_tupdesc(rowexpr->row_typeid, -1);
9971 [ - + ]: 44 : Assert(list_length(rowexpr->args) <= tupdesc->natts);
9972 : : }
9973 : :
9974 : : /*
9975 : : * SQL99 allows "ROW" to be omitted when there is more than
9976 : : * one column, but for simplicity we always print it.
9977 : : */
4753 rhaas@postgresql.org 9978 : 230 : appendStringInfoString(buf, "ROW(");
8214 tgl@sss.pgh.pa.us 9979 : 230 : sep = "";
8115 9980 : 230 : i = 0;
8214 9981 [ + - + + : 728 : foreach(arg, rowexpr->args)
+ + ]
9982 : : {
9983 : 498 : Node *e = (Node *) lfirst(arg);
9984 : :
8115 9985 [ + + ]: 498 : if (tupdesc == NULL ||
379 drowley@postgresql.o 9986 [ + - ]: 104 : !TupleDescCompactAttr(tupdesc, i)->attisdropped)
9987 : : {
7859 neilc@samurai.com 9988 : 498 : appendStringInfoString(buf, sep);
9989 : : /* Whole-row Vars need special treatment here */
4008 tgl@sss.pgh.pa.us 9990 : 498 : get_rule_expr_toplevel(e, context, true);
8115 9991 : 498 : sep = ", ";
9992 : : }
9993 : 498 : i++;
9994 : : }
9995 [ + + ]: 230 : if (tupdesc != NULL)
9996 : : {
9997 [ - + ]: 44 : while (i < tupdesc->natts)
9998 : : {
379 drowley@postgresql.o 9999 [ # # ]:UBC 0 : if (!TupleDescCompactAttr(tupdesc, i)->attisdropped)
10000 : : {
7859 neilc@samurai.com 10001 : 0 : appendStringInfoString(buf, sep);
4753 rhaas@postgresql.org 10002 : 0 : appendStringInfoString(buf, "NULL");
8115 tgl@sss.pgh.pa.us 10003 : 0 : sep = ", ";
10004 : : }
10005 : 0 : i++;
10006 : : }
10007 : :
7447 tgl@sss.pgh.pa.us 10008 [ + - ]:CBC 44 : ReleaseTupleDesc(tupdesc);
10009 : : }
4753 rhaas@postgresql.org 10010 : 230 : appendStringInfoChar(buf, ')');
8214 tgl@sss.pgh.pa.us 10011 [ + + ]: 230 : if (rowexpr->row_format == COERCE_EXPLICIT_CAST)
10012 : 24 : appendStringInfo(buf, "::%s",
10013 : : format_type_with_typemod(rowexpr->row_typeid, -1));
10014 : : }
10015 : 230 : break;
10016 : :
7617 10017 : 100 : case T_RowCompareExpr:
10018 : : {
10019 : 100 : RowCompareExpr *rcexpr = (RowCompareExpr *) node;
10020 : :
10021 : : /*
10022 : : * SQL99 allows "ROW" to be omitted when there is more than
10023 : : * one column, but for simplicity we always print it. Within
10024 : : * a ROW expression, whole-row Vars need special treatment, so
10025 : : * use get_rule_list_toplevel.
10026 : : */
4753 rhaas@postgresql.org 10027 : 100 : appendStringInfoString(buf, "(ROW(");
1757 tgl@sss.pgh.pa.us 10028 : 100 : get_rule_list_toplevel(rcexpr->largs, context, true);
10029 : :
10030 : : /*
10031 : : * We assume that the name of the first-column operator will
10032 : : * do for all the rest too. This is definitely open to
10033 : : * failure, eg if some but not all operators were renamed
10034 : : * since the construct was parsed, but there seems no way to
10035 : : * be perfect.
10036 : : */
7617 10037 : 100 : appendStringInfo(buf, ") %s ROW(",
3424 10038 : 100 : generate_operator_name(linitial_oid(rcexpr->opnos),
10039 : 100 : exprType(linitial(rcexpr->largs)),
10040 : 100 : exprType(linitial(rcexpr->rargs))));
1757 10041 : 100 : get_rule_list_toplevel(rcexpr->rargs, context, true);
4753 rhaas@postgresql.org 10042 : 100 : appendStringInfoString(buf, "))");
10043 : : }
7617 tgl@sss.pgh.pa.us 10044 : 100 : break;
10045 : :
8663 10046 : 1021 : case T_CoalesceExpr:
10047 : : {
10048 : 1021 : CoalesceExpr *coalesceexpr = (CoalesceExpr *) node;
10049 : :
4753 rhaas@postgresql.org 10050 : 1021 : appendStringInfoString(buf, "COALESCE(");
8064 tgl@sss.pgh.pa.us 10051 : 1021 : get_rule_expr((Node *) coalesceexpr->args, context, true);
10052 : 1021 : appendStringInfoChar(buf, ')');
10053 : : }
8663 10054 : 1021 : break;
10055 : :
7802 10056 : 28 : case T_MinMaxExpr:
10057 : : {
10058 : 28 : MinMaxExpr *minmaxexpr = (MinMaxExpr *) node;
10059 : :
10060 [ + + - ]: 28 : switch (minmaxexpr->op)
10061 : : {
10062 : 8 : case IS_GREATEST:
4753 rhaas@postgresql.org 10063 : 8 : appendStringInfoString(buf, "GREATEST(");
7802 tgl@sss.pgh.pa.us 10064 : 8 : break;
10065 : 20 : case IS_LEAST:
4753 rhaas@postgresql.org 10066 : 20 : appendStringInfoString(buf, "LEAST(");
7802 tgl@sss.pgh.pa.us 10067 : 20 : break;
10068 : : }
10069 : 28 : get_rule_expr((Node *) minmaxexpr->args, context, true);
10070 : 28 : appendStringInfoChar(buf, ')');
10071 : : }
10072 : 28 : break;
10073 : :
1268 michael@paquier.xyz 10074 : 455 : case T_SQLValueFunction:
10075 : : {
10076 : 455 : SQLValueFunction *svf = (SQLValueFunction *) node;
10077 : :
10078 : : /*
10079 : : * Note: this code knows that typmod for time, timestamp, and
10080 : : * timestamptz just prints as integer.
10081 : : */
10082 [ + + + + : 455 : switch (svf->op)
+ + + + +
+ + + + +
+ - ]
10083 : : {
10084 : 69 : case SVFOP_CURRENT_DATE:
10085 : 69 : appendStringInfoString(buf, "CURRENT_DATE");
10086 : 69 : break;
10087 : 8 : case SVFOP_CURRENT_TIME:
10088 : 8 : appendStringInfoString(buf, "CURRENT_TIME");
10089 : 8 : break;
10090 : 8 : case SVFOP_CURRENT_TIME_N:
10091 : 8 : appendStringInfo(buf, "CURRENT_TIME(%d)", svf->typmod);
10092 : 8 : break;
10093 : 8 : case SVFOP_CURRENT_TIMESTAMP:
10094 : 8 : appendStringInfoString(buf, "CURRENT_TIMESTAMP");
10095 : 8 : break;
10096 : 77 : case SVFOP_CURRENT_TIMESTAMP_N:
10097 : 77 : appendStringInfo(buf, "CURRENT_TIMESTAMP(%d)",
10098 : : svf->typmod);
10099 : 77 : break;
10100 : 8 : case SVFOP_LOCALTIME:
10101 : 8 : appendStringInfoString(buf, "LOCALTIME");
10102 : 8 : break;
10103 : 8 : case SVFOP_LOCALTIME_N:
10104 : 8 : appendStringInfo(buf, "LOCALTIME(%d)", svf->typmod);
10105 : 8 : break;
10106 : 20 : case SVFOP_LOCALTIMESTAMP:
10107 : 20 : appendStringInfoString(buf, "LOCALTIMESTAMP");
10108 : 20 : break;
10109 : 12 : case SVFOP_LOCALTIMESTAMP_N:
10110 : 12 : appendStringInfo(buf, "LOCALTIMESTAMP(%d)",
10111 : : svf->typmod);
10112 : 12 : break;
10113 : 8 : case SVFOP_CURRENT_ROLE:
10114 : 8 : appendStringInfoString(buf, "CURRENT_ROLE");
10115 : 8 : break;
10116 : 185 : case SVFOP_CURRENT_USER:
10117 : 185 : appendStringInfoString(buf, "CURRENT_USER");
10118 : 185 : break;
10119 : 8 : case SVFOP_USER:
10120 : 8 : appendStringInfoString(buf, "USER");
10121 : 8 : break;
10122 : 20 : case SVFOP_SESSION_USER:
10123 : 20 : appendStringInfoString(buf, "SESSION_USER");
10124 : 20 : break;
10125 : 8 : case SVFOP_CURRENT_CATALOG:
10126 : 8 : appendStringInfoString(buf, "CURRENT_CATALOG");
10127 : 8 : break;
10128 : 8 : case SVFOP_CURRENT_SCHEMA:
10129 : 8 : appendStringInfoString(buf, "CURRENT_SCHEMA");
10130 : 8 : break;
10131 : : }
10132 : : }
10133 : 455 : break;
10134 : :
7256 tgl@sss.pgh.pa.us 10135 : 111 : case T_XmlExpr:
10136 : : {
6930 bruce@momjian.us 10137 : 111 : XmlExpr *xexpr = (XmlExpr *) node;
10138 : 111 : bool needcomma = false;
10139 : : ListCell *arg;
10140 : : ListCell *narg;
10141 : : Const *con;
10142 : :
7256 tgl@sss.pgh.pa.us 10143 [ + + + + : 111 : switch (xexpr->op)
+ + + +
- ]
10144 : : {
10145 : 9 : case IS_XMLCONCAT:
10146 : 9 : appendStringInfoString(buf, "XMLCONCAT(");
10147 : 9 : break;
10148 : 18 : case IS_XMLELEMENT:
10149 : 18 : appendStringInfoString(buf, "XMLELEMENT(");
10150 : 18 : break;
10151 : 9 : case IS_XMLFOREST:
10152 : 9 : appendStringInfoString(buf, "XMLFOREST(");
10153 : 9 : break;
10154 : 9 : case IS_XMLPARSE:
10155 : 9 : appendStringInfoString(buf, "XMLPARSE(");
10156 : 9 : break;
10157 : 9 : case IS_XMLPI:
10158 : 9 : appendStringInfoString(buf, "XMLPI(");
10159 : 9 : break;
10160 : 9 : case IS_XMLROOT:
10161 : 9 : appendStringInfoString(buf, "XMLROOT(");
10162 : 9 : break;
7215 peter_e@gmx.net 10163 : 36 : case IS_XMLSERIALIZE:
10164 : 36 : appendStringInfoString(buf, "XMLSERIALIZE(");
10165 : 36 : break;
7235 10166 : 12 : case IS_DOCUMENT:
10167 : : /* postfix test, like IS NULL: not self-delimiting */
16 tgl@sss.pgh.pa.us 10168 [ - + ]: 12 : if (!PRETTY_PAREN(context))
16 tgl@sss.pgh.pa.us 10169 :UBC 0 : appendStringInfoChar(buf, '(');
7235 peter_e@gmx.net 10170 :CBC 12 : break;
10171 : : }
7215 10172 [ + + + + ]: 111 : if (xexpr->op == IS_XMLPARSE || xexpr->op == IS_XMLSERIALIZE)
10173 : : {
10174 [ + + ]: 45 : if (xexpr->xmloption == XMLOPTION_DOCUMENT)
10175 : 18 : appendStringInfoString(buf, "DOCUMENT ");
10176 : : else
10177 : 27 : appendStringInfoString(buf, "CONTENT ");
10178 : : }
7256 tgl@sss.pgh.pa.us 10179 [ + + ]: 111 : if (xexpr->name)
10180 : : {
10181 : 27 : appendStringInfo(buf, "NAME %s",
7251 peter_e@gmx.net 10182 : 27 : quote_identifier(map_xml_name_to_sql_identifier(xexpr->name)));
7256 tgl@sss.pgh.pa.us 10183 : 27 : needcomma = true;
10184 : : }
10185 [ + + ]: 111 : if (xexpr->named_args)
10186 : : {
10187 [ + + ]: 18 : if (xexpr->op != IS_XMLFOREST)
10188 : : {
10189 [ + - ]: 9 : if (needcomma)
10190 : 9 : appendStringInfoString(buf, ", ");
10191 : 9 : appendStringInfoString(buf, "XMLATTRIBUTES(");
10192 : 9 : needcomma = false;
10193 : : }
10194 [ + - + + : 63 : forboth(arg, xexpr->named_args, narg, xexpr->arg_names)
+ - + + +
+ + - +
+ ]
10195 : : {
6930 bruce@momjian.us 10196 : 45 : Node *e = (Node *) lfirst(arg);
10197 : 45 : char *argname = strVal(lfirst(narg));
10198 : :
7256 tgl@sss.pgh.pa.us 10199 [ + + ]: 45 : if (needcomma)
10200 : 27 : appendStringInfoString(buf, ", ");
338 peter@eisentraut.org 10201 : 45 : get_rule_expr(e, context, true);
7256 tgl@sss.pgh.pa.us 10202 : 45 : appendStringInfo(buf, " AS %s",
7251 peter_e@gmx.net 10203 : 45 : quote_identifier(map_xml_name_to_sql_identifier(argname)));
7256 tgl@sss.pgh.pa.us 10204 : 45 : needcomma = true;
10205 : : }
10206 [ + + ]: 18 : if (xexpr->op != IS_XMLFOREST)
10207 : 9 : appendStringInfoChar(buf, ')');
10208 : : }
10209 [ + + ]: 111 : if (xexpr->args)
10210 : : {
10211 [ + + ]: 102 : if (needcomma)
10212 : 27 : appendStringInfoString(buf, ", ");
10213 [ + + + + : 102 : switch (xexpr->op)
- ]
10214 : : {
10215 : 72 : case IS_XMLCONCAT:
10216 : : case IS_XMLELEMENT:
10217 : : case IS_XMLFOREST:
10218 : : case IS_XMLPI:
10219 : : case IS_XMLSERIALIZE:
10220 : : /* no extra decoration needed */
10221 : 72 : get_rule_expr((Node *) xexpr->args, context, true);
10222 : 72 : break;
10223 : 9 : case IS_XMLPARSE:
7215 peter_e@gmx.net 10224 [ - + ]: 9 : Assert(list_length(xexpr->args) == 2);
10225 : :
7256 tgl@sss.pgh.pa.us 10226 : 9 : get_rule_expr((Node *) linitial(xexpr->args),
10227 : : context, true);
10228 : :
3496 10229 : 9 : con = lsecond_node(Const, xexpr->args);
7256 10230 [ - + ]: 9 : Assert(!con->constisnull);
10231 [ - + ]: 9 : if (DatumGetBool(con->constvalue))
7256 tgl@sss.pgh.pa.us 10232 :UBC 0 : appendStringInfoString(buf,
10233 : : " PRESERVE WHITESPACE");
10234 : : else
7256 tgl@sss.pgh.pa.us 10235 :CBC 9 : appendStringInfoString(buf,
10236 : : " STRIP WHITESPACE");
10237 : 9 : break;
10238 : 9 : case IS_XMLROOT:
10239 [ - + ]: 9 : Assert(list_length(xexpr->args) == 3);
10240 : :
10241 : 9 : get_rule_expr((Node *) linitial(xexpr->args),
10242 : : context, true);
10243 : :
10244 : 9 : appendStringInfoString(buf, ", VERSION ");
10245 : 9 : con = (Const *) lsecond(xexpr->args);
10246 [ + - ]: 9 : if (IsA(con, Const) &&
10247 [ + - ]: 9 : con->constisnull)
10248 : 9 : appendStringInfoString(buf, "NO VALUE");
10249 : : else
7256 tgl@sss.pgh.pa.us 10250 :UBC 0 : get_rule_expr((Node *) con, context, false);
10251 : :
3496 tgl@sss.pgh.pa.us 10252 :CBC 9 : con = lthird_node(Const, xexpr->args);
7256 10253 [ + - ]: 9 : if (con->constisnull)
10254 : : /* suppress STANDALONE NO VALUE */ ;
10255 : : else
10256 : : {
7215 peter_e@gmx.net 10257 [ + - - - ]: 9 : switch (DatumGetInt32(con->constvalue))
10258 : : {
10259 : 9 : case XML_STANDALONE_YES:
10260 : 9 : appendStringInfoString(buf,
10261 : : ", STANDALONE YES");
10262 : 9 : break;
7215 peter_e@gmx.net 10263 :UBC 0 : case XML_STANDALONE_NO:
10264 : 0 : appendStringInfoString(buf,
10265 : : ", STANDALONE NO");
10266 : 0 : break;
10267 : 0 : case XML_STANDALONE_NO_VALUE:
10268 : 0 : appendStringInfoString(buf,
10269 : : ", STANDALONE NO VALUE");
10270 : 0 : break;
10271 : 0 : default:
10272 : 0 : break;
10273 : : }
10274 : : }
7256 tgl@sss.pgh.pa.us 10275 :CBC 9 : break;
7235 peter_e@gmx.net 10276 : 12 : case IS_DOCUMENT:
16 tgl@sss.pgh.pa.us 10277 [ - + ]: 12 : Assert(list_length(xexpr->args) == 1);
10278 : 12 : get_rule_expr_paren((Node *) linitial(xexpr->args),
10279 : : context, false, node);
7235 peter_e@gmx.net 10280 : 12 : break;
10281 : : }
10282 : : }
7215 10283 [ + + ]: 111 : if (xexpr->op == IS_XMLSERIALIZE)
10284 : : {
5710 tgl@sss.pgh.pa.us 10285 : 36 : appendStringInfo(buf, " AS %s",
10286 : : format_type_with_typemod(xexpr->type,
10287 : : xexpr->typmod));
622 michael@paquier.xyz 10288 [ + + ]: 36 : if (xexpr->indent)
10289 : 9 : appendStringInfoString(buf, " INDENT");
10290 : : else
10291 : 27 : appendStringInfoString(buf, " NO INDENT");
10292 : : }
10293 : :
7235 peter_e@gmx.net 10294 [ + + ]: 111 : if (xexpr->op == IS_DOCUMENT)
10295 : : {
10296 : 12 : appendStringInfoString(buf, " IS DOCUMENT");
16 tgl@sss.pgh.pa.us 10297 [ - + ]: 12 : if (!PRETTY_PAREN(context))
16 tgl@sss.pgh.pa.us 10298 :UBC 0 : appendStringInfoChar(buf, ')');
10299 : : }
10300 : : else
7235 peter_e@gmx.net 10301 :CBC 99 : appendStringInfoChar(buf, ')');
10302 : : }
7256 tgl@sss.pgh.pa.us 10303 : 111 : break;
10304 : :
9270 10305 : 2030 : case T_NullTest:
10306 : : {
9142 bruce@momjian.us 10307 : 2030 : NullTest *ntest = (NullTest *) node;
10308 : :
8499 tgl@sss.pgh.pa.us 10309 [ + + ]: 2030 : if (!PRETTY_PAREN(context))
8494 bruce@momjian.us 10310 : 1990 : appendStringInfoChar(buf, '(');
8499 tgl@sss.pgh.pa.us 10311 : 2030 : get_rule_expr_paren((Node *) ntest->arg, context, true, node);
10312 : :
10313 : : /*
10314 : : * For scalar inputs, we prefer to print as IS [NOT] NULL,
10315 : : * which is shorter and traditional. If it's a rowtype input
10316 : : * but we're applying a scalar test, must print IS [NOT]
10317 : : * DISTINCT FROM NULL to be semantically correct.
10318 : : */
3752 10319 [ + + ]: 2030 : if (ntest->argisrow ||
10320 [ + + ]: 1932 : !type_is_rowtype(exprType((Node *) ntest->arg)))
10321 : : {
10322 [ + + - ]: 2010 : switch (ntest->nulltesttype)
10323 : : {
10324 : 684 : case IS_NULL:
10325 : 684 : appendStringInfoString(buf, " IS NULL");
10326 : 684 : break;
10327 : 1326 : case IS_NOT_NULL:
10328 : 1326 : appendStringInfoString(buf, " IS NOT NULL");
10329 : 1326 : break;
3752 tgl@sss.pgh.pa.us 10330 :UBC 0 : default:
10331 [ # # ]: 0 : elog(ERROR, "unrecognized nulltesttype: %d",
10332 : : (int) ntest->nulltesttype);
10333 : : }
10334 : : }
10335 : : else
10336 : : {
3752 tgl@sss.pgh.pa.us 10337 [ + + - ]:CBC 20 : switch (ntest->nulltesttype)
10338 : : {
10339 : 8 : case IS_NULL:
10340 : 8 : appendStringInfoString(buf, " IS NOT DISTINCT FROM NULL");
10341 : 8 : break;
10342 : 12 : case IS_NOT_NULL:
10343 : 12 : appendStringInfoString(buf, " IS DISTINCT FROM NULL");
10344 : 12 : break;
3752 tgl@sss.pgh.pa.us 10345 :UBC 0 : default:
10346 [ # # ]: 0 : elog(ERROR, "unrecognized nulltesttype: %d",
10347 : : (int) ntest->nulltesttype);
10348 : : }
10349 : : }
8499 tgl@sss.pgh.pa.us 10350 [ + + ]:CBC 2030 : if (!PRETTY_PAREN(context))
8494 bruce@momjian.us 10351 : 1990 : appendStringInfoChar(buf, ')');
10352 : : }
9270 tgl@sss.pgh.pa.us 10353 : 2030 : break;
10354 : :
10355 : 212 : case T_BooleanTest:
10356 : : {
9142 bruce@momjian.us 10357 : 212 : BooleanTest *btest = (BooleanTest *) node;
10358 : :
8499 tgl@sss.pgh.pa.us 10359 [ + - ]: 212 : if (!PRETTY_PAREN(context))
8494 bruce@momjian.us 10360 : 212 : appendStringInfoChar(buf, '(');
8499 tgl@sss.pgh.pa.us 10361 : 212 : get_rule_expr_paren((Node *) btest->arg, context, false, node);
9142 bruce@momjian.us 10362 [ + + - + : 212 : switch (btest->booltesttype)
+ + - ]
10363 : : {
10364 : 28 : case IS_TRUE:
4753 rhaas@postgresql.org 10365 : 28 : appendStringInfoString(buf, " IS TRUE");
9270 tgl@sss.pgh.pa.us 10366 : 28 : break;
9142 bruce@momjian.us 10367 : 92 : case IS_NOT_TRUE:
4753 rhaas@postgresql.org 10368 : 92 : appendStringInfoString(buf, " IS NOT TRUE");
9270 tgl@sss.pgh.pa.us 10369 : 92 : break;
9142 bruce@momjian.us 10370 :UBC 0 : case IS_FALSE:
4753 rhaas@postgresql.org 10371 : 0 : appendStringInfoString(buf, " IS FALSE");
9270 tgl@sss.pgh.pa.us 10372 : 0 : break;
9142 bruce@momjian.us 10373 :CBC 36 : case IS_NOT_FALSE:
4753 rhaas@postgresql.org 10374 : 36 : appendStringInfoString(buf, " IS NOT FALSE");
9270 tgl@sss.pgh.pa.us 10375 : 36 : break;
9142 bruce@momjian.us 10376 : 20 : case IS_UNKNOWN:
4753 rhaas@postgresql.org 10377 : 20 : appendStringInfoString(buf, " IS UNKNOWN");
9270 tgl@sss.pgh.pa.us 10378 : 20 : break;
9142 bruce@momjian.us 10379 : 36 : case IS_NOT_UNKNOWN:
4753 rhaas@postgresql.org 10380 : 36 : appendStringInfoString(buf, " IS NOT UNKNOWN");
9270 tgl@sss.pgh.pa.us 10381 : 36 : break;
9142 bruce@momjian.us 10382 :UBC 0 : default:
8502 tgl@sss.pgh.pa.us 10383 [ # # ]: 0 : elog(ERROR, "unrecognized booltesttype: %d",
10384 : : (int) btest->booltesttype);
10385 : : }
8499 tgl@sss.pgh.pa.us 10386 [ + - ]:CBC 212 : if (!PRETTY_PAREN(context))
8494 bruce@momjian.us 10387 : 212 : appendStringInfoChar(buf, ')');
10388 : : }
9270 tgl@sss.pgh.pa.us 10389 : 212 : break;
10390 : :
8676 10391 : 87 : case T_CoerceToDomain:
10392 : : {
10393 : 87 : CoerceToDomain *ctest = (CoerceToDomain *) node;
8494 bruce@momjian.us 10394 : 87 : Node *arg = (Node *) ctest->arg;
10395 : :
8676 tgl@sss.pgh.pa.us 10396 [ + + ]: 87 : if (ctest->coercionformat == COERCE_IMPLICIT_CAST &&
10397 [ + + ]: 36 : !showimplicit)
10398 : : {
10399 : : /* don't show the implicit cast */
10400 : 28 : get_rule_expr(arg, context, false);
10401 : : }
10402 : : else
10403 : : {
7173 10404 : 59 : get_coercion_expr(arg, context,
10405 : : ctest->resulttype,
10406 : : ctest->resulttypmod,
10407 : : node);
10408 : : }
10409 : : }
8832 10410 : 87 : break;
10411 : :
8676 10412 : 266 : case T_CoerceToDomainValue:
4753 rhaas@postgresql.org 10413 : 266 : appendStringInfoString(buf, "VALUE");
9004 tgl@sss.pgh.pa.us 10414 : 266 : break;
10415 : :
8526 10416 : 44 : case T_SetToDefault:
4753 rhaas@postgresql.org 10417 : 44 : appendStringInfoString(buf, "DEFAULT");
8526 tgl@sss.pgh.pa.us 10418 : 44 : break;
10419 : :
7087 10420 : 16 : case T_CurrentOfExpr:
10421 : : {
10422 : 16 : CurrentOfExpr *cexpr = (CurrentOfExpr *) node;
10423 : :
10424 [ + - ]: 16 : if (cexpr->cursor_name)
10425 : 16 : appendStringInfo(buf, "CURRENT OF %s",
10426 : 16 : quote_identifier(cexpr->cursor_name));
10427 : : else
7087 tgl@sss.pgh.pa.us 10428 :UBC 0 : appendStringInfo(buf, "CURRENT OF $%d",
10429 : : cexpr->cursor_param);
10430 : : }
7087 tgl@sss.pgh.pa.us 10431 :CBC 16 : break;
10432 : :
3401 tgl@sss.pgh.pa.us 10433 :UBC 0 : case T_NextValueExpr:
10434 : : {
10435 : 0 : NextValueExpr *nvexpr = (NextValueExpr *) node;
10436 : :
10437 : : /*
10438 : : * This isn't exactly nextval(), but that seems close enough
10439 : : * for EXPLAIN's purposes.
10440 : : */
10441 : 0 : appendStringInfoString(buf, "nextval(");
10442 : 0 : simple_quote_literal(buf,
10443 : 0 : generate_relation_name(nvexpr->seqid,
10444 : : NIL));
10445 : 0 : appendStringInfoChar(buf, ')');
10446 : : }
10447 : 0 : break;
10448 : :
4188 andres@anarazel.de 10449 :CBC 20 : case T_InferenceElem:
10450 : : {
4184 bruce@momjian.us 10451 : 20 : InferenceElem *iexpr = (InferenceElem *) node;
10452 : : bool save_varprefix;
10453 : : bool need_parens;
10454 : :
10455 : : /*
10456 : : * InferenceElem can only refer to target relation, so a
10457 : : * prefix is not useful, and indeed would cause parse errors.
10458 : : */
3924 tgl@sss.pgh.pa.us 10459 : 20 : save_varprefix = context->varprefix;
4188 andres@anarazel.de 10460 : 20 : context->varprefix = false;
10461 : :
10462 : : /*
10463 : : * Parenthesize the element unless it's a simple Var or a bare
10464 : : * function call. Follows pg_get_indexdef_worker().
10465 : : */
10466 : 20 : need_parens = !IsA(iexpr->expr, Var);
10467 [ - + ]: 20 : if (IsA(iexpr->expr, FuncExpr) &&
4188 andres@anarazel.de 10468 [ # # ]:UBC 0 : ((FuncExpr *) iexpr->expr)->funcformat ==
10469 : : COERCE_EXPLICIT_CALL)
10470 : 0 : need_parens = false;
10471 : :
4188 andres@anarazel.de 10472 [ - + ]:CBC 20 : if (need_parens)
4188 andres@anarazel.de 10473 :UBC 0 : appendStringInfoChar(buf, '(');
4188 andres@anarazel.de 10474 :CBC 20 : get_rule_expr((Node *) iexpr->expr,
10475 : : context, false);
10476 [ - + ]: 20 : if (need_parens)
4188 andres@anarazel.de 10477 :UBC 0 : appendStringInfoChar(buf, ')');
10478 : :
3924 tgl@sss.pgh.pa.us 10479 :CBC 20 : context->varprefix = save_varprefix;
10480 : :
4188 andres@anarazel.de 10481 [ + + ]: 20 : if (iexpr->infercollid)
10482 : 8 : appendStringInfo(buf, " COLLATE %s",
10483 : : generate_collation_name(iexpr->infercollid));
10484 : :
10485 : : /* Add the operator class name, if not default */
10486 [ + + ]: 20 : if (iexpr->inferopclass)
10487 : : {
4184 bruce@momjian.us 10488 : 8 : Oid inferopclass = iexpr->inferopclass;
10489 : 8 : Oid inferopcinputtype = get_opclass_input_type(iexpr->inferopclass);
10490 : :
4188 andres@anarazel.de 10491 : 8 : get_opclass_name(inferopclass, inferopcinputtype, buf);
10492 : : }
10493 : : }
10494 : 20 : break;
10495 : :
658 dean.a.rasheed@gmail 10496 : 8 : case T_ReturningExpr:
10497 : : {
10498 : 8 : ReturningExpr *retExpr = (ReturningExpr *) node;
10499 : :
10500 : : /*
10501 : : * We cannot see a ReturningExpr in rule deparsing, only while
10502 : : * EXPLAINing a query plan (ReturningExpr nodes are only ever
10503 : : * adding during query rewriting). Just display the expression
10504 : : * returned (an expanded view column).
10505 : : */
10506 : 8 : get_rule_expr((Node *) retExpr->retexpr, context, showimplicit);
10507 : : }
10508 : 8 : break;
10509 : :
3620 rhaas@postgresql.org 10510 : 2417 : case T_PartitionBoundSpec:
10511 : : {
10512 : 2417 : PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
10513 : : ListCell *cell;
10514 : : char *sep;
10515 : :
3345 10516 [ + + ]: 2417 : if (spec->is_default)
10517 : : {
10518 : 89 : appendStringInfoString(buf, "DEFAULT");
10519 : 89 : break;
10520 : : }
10521 : :
3620 10522 [ + + + - ]: 2328 : switch (spec->strategy)
10523 : : {
3283 10524 : 165 : case PARTITION_STRATEGY_HASH:
10525 [ + - - + ]: 165 : Assert(spec->modulus > 0 && spec->remainder >= 0);
10526 [ - + ]: 165 : Assert(spec->modulus > spec->remainder);
10527 : :
10528 : 165 : appendStringInfoString(buf, "FOR VALUES");
10529 : 165 : appendStringInfo(buf, " WITH (modulus %d, remainder %d)",
10530 : : spec->modulus, spec->remainder);
10531 : 165 : break;
10532 : :
3620 10533 : 820 : case PARTITION_STRATEGY_LIST:
10534 [ - + ]: 820 : Assert(spec->listdatums != NIL);
10535 : :
3448 tgl@sss.pgh.pa.us 10536 : 820 : appendStringInfoString(buf, "FOR VALUES IN (");
3620 rhaas@postgresql.org 10537 : 820 : sep = "";
3572 10538 [ + - + + : 2114 : foreach(cell, spec->listdatums)
+ + ]
10539 : : {
1935 peter@eisentraut.org 10540 : 1294 : Const *val = lfirst_node(Const, cell);
10541 : :
3620 rhaas@postgresql.org 10542 : 1294 : appendStringInfoString(buf, sep);
10543 : 1294 : get_const_expr(val, context, -1);
10544 : 1294 : sep = ", ";
10545 : : }
10546 : :
3369 peter_e@gmx.net 10547 : 820 : appendStringInfoChar(buf, ')');
3620 rhaas@postgresql.org 10548 : 820 : break;
10549 : :
10550 : 1343 : case PARTITION_STRATEGY_RANGE:
10551 [ + - + - : 1343 : Assert(spec->lowerdatums != NIL &&
- + ]
10552 : : spec->upperdatums != NIL &&
10553 : : list_length(spec->lowerdatums) ==
10554 : : list_length(spec->upperdatums));
10555 : :
3374 10556 : 1343 : appendStringInfo(buf, "FOR VALUES FROM %s TO %s",
10557 : : get_range_partbound_string(spec->lowerdatums),
10558 : : get_range_partbound_string(spec->upperdatums));
3620 10559 : 1343 : break;
10560 : :
3620 rhaas@postgresql.org 10561 :UBC 0 : default:
10562 [ # # ]: 0 : elog(ERROR, "unrecognized partition strategy: %d",
10563 : : (int) spec->strategy);
10564 : : break;
10565 : : }
10566 : : }
3620 rhaas@postgresql.org 10567 :CBC 2328 : break;
10568 : :
1317 alvherre@alvh.no-ip. 10569 : 104 : case T_JsonValueExpr:
10570 : : {
10571 : 104 : JsonValueExpr *jve = (JsonValueExpr *) node;
10572 : :
10573 : 104 : get_rule_expr((Node *) jve->raw_expr, context, false);
10574 : 104 : get_json_format(jve->format, context->buf);
10575 : : }
10576 : 104 : break;
10577 : :
10578 : 188 : case T_JsonConstructorExpr:
10579 : 188 : get_json_constructor((JsonConstructorExpr *) node, context, false);
10580 : 188 : break;
10581 : :
1315 10582 : 52 : case T_JsonIsPredicate:
10583 : : {
10584 : 52 : JsonIsPredicate *pred = (JsonIsPredicate *) node;
10585 : :
10586 [ + + ]: 52 : if (!PRETTY_PAREN(context))
10587 : 20 : appendStringInfoChar(context->buf, '(');
10588 : :
10589 : 52 : get_rule_expr_paren(pred->expr, context, true, node);
10590 : :
10591 : 52 : appendStringInfoString(context->buf, " IS JSON");
10592 : :
10593 : : /* TODO: handle FORMAT clause */
10594 : :
10595 [ + + + + ]: 52 : switch (pred->item_type)
10596 : : {
10597 : 8 : case JS_TYPE_SCALAR:
10598 : 8 : appendStringInfoString(context->buf, " SCALAR");
10599 : 8 : break;
10600 : 8 : case JS_TYPE_ARRAY:
10601 : 8 : appendStringInfoString(context->buf, " ARRAY");
10602 : 8 : break;
10603 : 8 : case JS_TYPE_OBJECT:
10604 : 8 : appendStringInfoString(context->buf, " OBJECT");
10605 : 8 : break;
10606 : 28 : default:
10607 : 28 : break;
10608 : : }
10609 : :
10610 [ + + ]: 52 : if (pred->unique_keys)
10611 : 20 : appendStringInfoString(context->buf, " WITH UNIQUE KEYS");
10612 : :
10613 [ + + ]: 52 : if (!PRETTY_PAREN(context))
10614 : 20 : appendStringInfoChar(context->buf, ')');
10615 : : }
10616 : 52 : break;
10617 : :
959 amitlan@postgresql.o 10618 : 40 : case T_JsonExpr:
10619 : : {
10620 : 40 : JsonExpr *jexpr = (JsonExpr *) node;
10621 : :
10622 [ + + + - ]: 40 : switch (jexpr->op)
10623 : : {
10624 : 8 : case JSON_EXISTS_OP:
10625 : 8 : appendStringInfoString(buf, "JSON_EXISTS(");
10626 : 8 : break;
10627 : 24 : case JSON_QUERY_OP:
10628 : 24 : appendStringInfoString(buf, "JSON_QUERY(");
10629 : 24 : break;
10630 : 8 : case JSON_VALUE_OP:
10631 : 8 : appendStringInfoString(buf, "JSON_VALUE(");
10632 : 8 : break;
959 amitlan@postgresql.o 10633 :UBC 0 : default:
10634 [ # # ]: 0 : elog(ERROR, "unrecognized JsonExpr op: %d",
10635 : : (int) jexpr->op);
10636 : : }
10637 : :
959 amitlan@postgresql.o 10638 :CBC 40 : get_rule_expr(jexpr->formatted_expr, context, showimplicit);
10639 : :
10640 : 40 : appendStringInfoString(buf, ", ");
10641 : :
10642 : 40 : get_json_path_spec(jexpr->path_spec, context, showimplicit);
10643 : :
10644 [ + + ]: 40 : if (jexpr->passing_values)
10645 : : {
10646 : : ListCell *lc1,
10647 : : *lc2;
10648 : 8 : bool needcomma = false;
10649 : :
10650 : 8 : appendStringInfoString(buf, " PASSING ");
10651 : :
10652 [ + - + + : 32 : forboth(lc1, jexpr->passing_names,
+ - + + +
+ + - +
+ ]
10653 : : lc2, jexpr->passing_values)
10654 : : {
10655 [ + + ]: 24 : if (needcomma)
10656 : 16 : appendStringInfoString(buf, ", ");
10657 : 24 : needcomma = true;
10658 : :
10659 : 24 : get_rule_expr((Node *) lfirst(lc2), context, showimplicit);
10660 : 24 : appendStringInfo(buf, " AS %s",
662 dean.a.rasheed@gmail 10661 : 24 : quote_identifier(lfirst_node(String, lc1)->sval));
10662 : : }
10663 : : }
10664 : :
959 amitlan@postgresql.o 10665 [ + + ]: 40 : if (jexpr->op != JSON_EXISTS_OP ||
10666 [ - + ]: 8 : jexpr->returning->typid != BOOLOID)
10667 : 32 : get_json_returning(jexpr->returning, context->buf,
10668 : 32 : jexpr->op == JSON_QUERY_OP);
10669 : :
10670 : 40 : get_json_expr_options(jexpr, context,
10671 [ + + ]: 40 : jexpr->op != JSON_EXISTS_OP ?
10672 : : JSON_BEHAVIOR_NULL :
10673 : : JSON_BEHAVIOR_FALSE);
10674 : :
939 drowley@postgresql.o 10675 : 40 : appendStringInfoChar(buf, ')');
10676 : : }
959 amitlan@postgresql.o 10677 : 40 : break;
10678 : :
8064 tgl@sss.pgh.pa.us 10679 : 2096 : case T_List:
10680 : : {
10681 : : char *sep;
10682 : : ListCell *l;
10683 : :
10684 : 2096 : sep = "";
10685 [ + - + + : 5929 : foreach(l, (List *) node)
+ + ]
10686 : : {
7859 neilc@samurai.com 10687 : 3833 : appendStringInfoString(buf, sep);
8064 tgl@sss.pgh.pa.us 10688 : 3833 : get_rule_expr((Node *) lfirst(l), context, showimplicit);
10689 : 3833 : sep = ", ";
10690 : : }
10691 : : }
10692 : 2096 : break;
10693 : :
3529 alvherre@alvh.no-ip. 10694 : 52 : case T_TableFunc:
10695 : 52 : get_tablefunc((TableFunc *) node, context, showimplicit);
10696 : 52 : break;
10697 : :
10292 bruce@momjian.us 10698 :UBC 0 : default:
8502 tgl@sss.pgh.pa.us 10699 [ # # ]: 0 : elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
10700 : : break;
10701 : : }
10702 : : }
10703 : :
10704 : : /*
10705 : : * get_rule_expr_toplevel - Parse back a toplevel expression
10706 : : *
10707 : : * Same as get_rule_expr(), except that if the expr is just a Var, we pass
10708 : : * istoplevel = true not false to get_variable(). This causes whole-row Vars
10709 : : * to get printed with decoration that will prevent expansion of "*".
10710 : : * We need to use this in contexts such as ROW() and VALUES(), where the
10711 : : * parser would expand "foo.*" appearing at top level. (In principle we'd
10712 : : * use this in get_target_list() too, but that has additional worries about
10713 : : * whether to print AS, so it needs to invoke get_variable() directly anyway.)
10714 : : */
10715 : : static void
4008 tgl@sss.pgh.pa.us 10716 :CBC 2323 : get_rule_expr_toplevel(Node *node, deparse_context *context,
10717 : : bool showimplicit)
10718 : : {
10719 [ + - + + ]: 2323 : if (node && IsA(node, Var))
10720 : 890 : (void) get_variable((Var *) node, 0, true, context);
10721 : : else
10722 : 1433 : get_rule_expr(node, context, showimplicit);
10723 : 2323 : }
10724 : :
10725 : : /*
10726 : : * get_rule_list_toplevel - Parse back a list of toplevel expressions
10727 : : *
10728 : : * Apply get_rule_expr_toplevel() to each element of a List.
10729 : : *
10730 : : * This adds commas between the expressions, but caller is responsible
10731 : : * for printing surrounding decoration.
10732 : : */
10733 : : static void
1757 10734 : 358 : get_rule_list_toplevel(List *lst, deparse_context *context,
10735 : : bool showimplicit)
10736 : : {
10737 : : const char *sep;
10738 : : ListCell *lc;
10739 : :
10740 : 358 : sep = "";
10741 [ + - + + : 1183 : foreach(lc, lst)
+ + ]
10742 : : {
10743 : 825 : Node *e = (Node *) lfirst(lc);
10744 : :
10745 : 825 : appendStringInfoString(context->buf, sep);
10746 : 825 : get_rule_expr_toplevel(e, context, showimplicit);
10747 : 825 : sep = ", ";
10748 : : }
10749 : 358 : }
10750 : :
10751 : : /*
10752 : : * get_rule_expr_funccall - Parse back a function-call expression
10753 : : *
10754 : : * Same as get_rule_expr(), except that we guarantee that the output will
10755 : : * look like a function call, or like one of the things the grammar treats as
10756 : : * equivalent to a function call (see the func_expr_windowless production).
10757 : : * This is needed in places where the grammar uses func_expr_windowless and
10758 : : * you can't substitute a parenthesized a_expr. If what we have isn't going
10759 : : * to look like a function call, wrap it in a dummy CAST() expression, which
10760 : : * will satisfy the grammar --- and, indeed, is likely what the user wrote to
10761 : : * produce such a thing.
10762 : : */
10763 : : static void
3402 10764 : 576 : get_rule_expr_funccall(Node *node, deparse_context *context,
10765 : : bool showimplicit)
10766 : : {
10767 [ + + ]: 576 : if (looks_like_function(node))
10768 : 564 : get_rule_expr(node, context, showimplicit);
10769 : : else
10770 : : {
10771 : 12 : StringInfo buf = context->buf;
10772 : :
10773 : 12 : appendStringInfoString(buf, "CAST(");
10774 : : /* no point in showing any top-level implicit cast */
10775 : 12 : get_rule_expr(node, context, false);
10776 : 12 : appendStringInfo(buf, " AS %s)",
10777 : : format_type_with_typemod(exprType(node),
10778 : : exprTypmod(node)));
10779 : : }
10780 : 576 : }
10781 : :
10782 : : /*
10783 : : * Helper function to identify node types that satisfy func_expr_windowless.
10784 : : * If in doubt, "false" is always a safe answer.
10785 : : */
10786 : : static bool
10787 : 1520 : looks_like_function(Node *node)
10788 : : {
10789 [ - + ]: 1520 : if (node == NULL)
3402 tgl@sss.pgh.pa.us 10790 :UBC 0 : return false; /* probably shouldn't happen */
3402 tgl@sss.pgh.pa.us 10791 [ + + - + ]:CBC 1520 : switch (nodeTag(node))
10792 : : {
10793 : 588 : case T_FuncExpr:
10794 : : /* OK if it will print as a normal function call */
9 10795 [ + + ]: 588 : if (((FuncExpr *) node)->funcformat == COERCE_EXPLICIT_CALL)
10796 : 572 : return true;
10797 : : /* SQL_SYNTAX needs case-by-case analysis; all else is unsafe */
10798 [ + + ]: 16 : if (((FuncExpr *) node)->funcformat != COERCE_SQL_SYNTAX)
10799 : 4 : return false;
10800 : :
10801 : : /*
10802 : : * Most SQL-syntax functions are accepted by func_expr_windowless,
10803 : : * but get_func_sql_syntax() prints AT TIME ZONE, AT LOCAL,
10804 : : * OVERLAPS and IS xxx NORMALIZED in forms that aren't accepted.
10805 : : * All of the associated functions must be listed here.
10806 : : */
10807 [ + + ]: 12 : switch (((FuncExpr *) node)->funcid)
10808 : : {
10809 : : /* AT TIME ZONE: */
10810 : 4 : case F_TIMEZONE_INTERVAL_TIMESTAMP:
10811 : : case F_TIMEZONE_INTERVAL_TIMESTAMPTZ:
10812 : : case F_TIMEZONE_INTERVAL_TIMETZ:
10813 : : case F_TIMEZONE_TEXT_TIMESTAMP:
10814 : : case F_TIMEZONE_TEXT_TIMESTAMPTZ:
10815 : : case F_TIMEZONE_TEXT_TIMETZ:
10816 : : /* AT LOCAL: */
10817 : : case F_TIMEZONE_TIMESTAMP:
10818 : : case F_TIMEZONE_TIMESTAMPTZ:
10819 : : case F_TIMEZONE_TIMETZ:
10820 : : /* OVERLAPS: */
10821 : : case F_OVERLAPS_TIMESTAMPTZ_INTERVAL_TIMESTAMPTZ_INTERVAL:
10822 : : case F_OVERLAPS_TIMESTAMPTZ_INTERVAL_TIMESTAMPTZ_TIMESTAMPTZ:
10823 : : case F_OVERLAPS_TIMESTAMPTZ_TIMESTAMPTZ_TIMESTAMPTZ_INTERVAL:
10824 : : case F_OVERLAPS_TIMESTAMPTZ_TIMESTAMPTZ_TIMESTAMPTZ_TIMESTAMPTZ:
10825 : : case F_OVERLAPS_TIMESTAMP_INTERVAL_TIMESTAMP_INTERVAL:
10826 : : case F_OVERLAPS_TIMESTAMP_INTERVAL_TIMESTAMP_TIMESTAMP:
10827 : : case F_OVERLAPS_TIMESTAMP_TIMESTAMP_TIMESTAMP_INTERVAL:
10828 : : case F_OVERLAPS_TIMESTAMP_TIMESTAMP_TIMESTAMP_TIMESTAMP:
10829 : : case F_OVERLAPS_TIMETZ_TIMETZ_TIMETZ_TIMETZ:
10830 : : case F_OVERLAPS_TIME_INTERVAL_TIME_INTERVAL:
10831 : : case F_OVERLAPS_TIME_INTERVAL_TIME_TIME:
10832 : : case F_OVERLAPS_TIME_TIME_TIME_INTERVAL:
10833 : : case F_OVERLAPS_TIME_TIME_TIME_TIME:
10834 : : /* IS xxx NORMALIZED: */
10835 : : case F_IS_NORMALIZED:
10836 : 4 : return false;
10837 : 8 : default:
10838 : 8 : break;
10839 : : }
10840 : 8 : return true;
3402 10841 : 72 : case T_NullIfExpr:
10842 : : case T_CoalesceExpr:
10843 : : case T_MinMaxExpr:
10844 : : case T_SQLValueFunction:
10845 : : case T_JsonExpr:
10846 : : /* these are all accepted by func_expr_common_subexpr */
10847 : 72 : return true;
16 tgl@sss.pgh.pa.us 10848 :UBC 0 : case T_XmlExpr:
10849 : : /* all but IS DOCUMENT, which is a postfix test */
10850 : 0 : return ((XmlExpr *) node)->op != IS_DOCUMENT;
3402 tgl@sss.pgh.pa.us 10851 :CBC 860 : default:
10852 : 860 : break;
10853 : : }
10854 : 860 : return false;
10855 : : }
10856 : :
10857 : :
10858 : : /*
10859 : : * get_oper_expr - Parse back an OpExpr node
10860 : : */
10861 : : static void
8490 bruce@momjian.us 10862 : 42639 : get_oper_expr(OpExpr *expr, deparse_context *context)
10863 : : {
8952 tgl@sss.pgh.pa.us 10864 : 42639 : StringInfo buf = context->buf;
8729 10865 : 42639 : Oid opno = expr->opno;
8952 10866 : 42639 : List *args = expr->args;
10867 : :
8499 10868 [ + + ]: 42639 : if (!PRETTY_PAREN(context))
8494 bruce@momjian.us 10869 : 41067 : appendStringInfoChar(buf, '(');
8194 neilc@samurai.com 10870 [ + + ]: 42639 : if (list_length(args) == 2)
10871 : : {
10872 : : /* binary operator */
8198 10873 : 42603 : Node *arg1 = (Node *) linitial(args);
8828 bruce@momjian.us 10874 : 42603 : Node *arg2 = (Node *) lsecond(args);
10875 : :
8494 10876 : 42603 : get_rule_expr_paren(arg1, context, true, (Node *) expr);
8952 tgl@sss.pgh.pa.us 10877 : 42603 : appendStringInfo(buf, " %s ",
10878 : : generate_operator_name(opno,
10879 : : exprType(arg1),
10880 : : exprType(arg2)));
8494 bruce@momjian.us 10881 : 42603 : get_rule_expr_paren(arg2, context, true, (Node *) expr);
10882 : : }
10883 : : else
10884 : : {
10885 : : /* prefix operator */
8198 neilc@samurai.com 10886 : 36 : Node *arg = (Node *) linitial(args);
10887 : :
2240 tgl@sss.pgh.pa.us 10888 : 36 : appendStringInfo(buf, "%s ",
10889 : : generate_operator_name(opno,
10890 : : InvalidOid,
10891 : : exprType(arg)));
10892 : 36 : get_rule_expr_paren(arg, context, true, (Node *) expr);
10893 : : }
8499 10894 [ + + ]: 42639 : if (!PRETTY_PAREN(context))
8494 bruce@momjian.us 10895 : 41067 : appendStringInfoChar(buf, ')');
8952 tgl@sss.pgh.pa.us 10896 : 42639 : }
10897 : :
10898 : : /*
10899 : : * get_func_expr - Parse back a FuncExpr node
10900 : : */
10901 : : static void
8490 bruce@momjian.us 10902 : 8185 : get_func_expr(FuncExpr *expr, deparse_context *context,
10903 : : bool showimplicit)
10904 : : {
9895 tgl@sss.pgh.pa.us 10905 : 8185 : StringInfo buf = context->buf;
8729 10906 : 8185 : Oid funcoid = expr->funcid;
10907 : : Oid argtypes[FUNC_MAX_ARGS];
10908 : : int nargs;
10909 : : List *argnames;
10910 : : bool use_variadic;
10911 : : ListCell *l;
10912 : :
10913 : : /*
10914 : : * If the function call came from an implicit coercion, then just show the
10915 : : * first argument --- unless caller wants to see implicit coercions.
10916 : : */
10917 [ + + + + ]: 8185 : if (expr->funcformat == COERCE_IMPLICIT_CAST && !showimplicit)
10918 : : {
8198 neilc@samurai.com 10919 : 777 : get_rule_expr_paren((Node *) linitial(expr->args), context,
10920 : : false, (Node *) expr);
8814 tgl@sss.pgh.pa.us 10921 : 2080 : return;
10922 : : }
10923 : :
10924 : : /*
10925 : : * If the function call came from a cast, then show the first argument
10926 : : * plus an explicit cast operation.
10927 : : */
8729 10928 [ + + ]: 7408 : if (expr->funcformat == COERCE_EXPLICIT_CAST ||
10929 [ + + ]: 6912 : expr->funcformat == COERCE_IMPLICIT_CAST)
10930 : : {
8198 neilc@samurai.com 10931 : 1183 : Node *arg = linitial(expr->args);
8729 tgl@sss.pgh.pa.us 10932 : 1183 : Oid rettype = expr->funcresulttype;
10933 : : int32 coercedTypmod;
10934 : :
10935 : : /* Get the typmod if this is a length-coercion function */
8814 10936 : 1183 : (void) exprIsLengthCoercion((Node *) expr, &coercedTypmod);
10937 : :
7173 10938 : 1183 : get_coercion_expr(arg, context,
10939 : : rettype, coercedTypmod,
10940 : : (Node *) expr);
10941 : :
9749 10942 : 1183 : return;
10943 : : }
10944 : :
10945 : : /*
10946 : : * If the function was called using one of the SQL spec's random special
10947 : : * syntaxes, try to reproduce that. If we don't recognize the function,
10948 : : * fall through.
10949 : : */
2192 10950 [ + + ]: 6225 : if (expr->funcformat == COERCE_SQL_SYNTAX)
10951 : : {
10952 [ + + ]: 124 : if (get_func_sql_syntax(expr, context))
10953 : 120 : return;
10954 : : }
10955 : :
10956 : : /*
10957 : : * Normal function: display as proname(args). First we need to extract
10958 : : * the argument datatypes.
10959 : : */
6521 10960 [ - + ]: 6105 : if (list_length(expr->args) > FUNC_MAX_ARGS)
6521 tgl@sss.pgh.pa.us 10961 [ # # ]:UBC 0 : ereport(ERROR,
10962 : : (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
10963 : : errmsg("too many arguments")));
8952 tgl@sss.pgh.pa.us 10964 :CBC 6105 : nargs = 0;
6237 10965 : 6105 : argnames = NIL;
8952 10966 [ + + + + : 12777 : foreach(l, expr->args)
+ + ]
10967 : : {
6096 bruce@momjian.us 10968 : 6672 : Node *arg = (Node *) lfirst(l);
10969 : :
6237 tgl@sss.pgh.pa.us 10970 [ + + ]: 6672 : if (IsA(arg, NamedArgExpr))
10971 : 20 : argnames = lappend(argnames, ((NamedArgExpr *) arg)->name);
10972 : 6672 : argtypes[nargs] = exprType(arg);
8952 10973 : 6672 : nargs++;
10974 : : }
10975 : :
10976 : 6105 : appendStringInfo(buf, "%s(",
10977 : : generate_function_name(funcoid, nargs,
10978 : : argnames, argtypes,
5036 10979 : 6105 : expr->funcvariadic,
10980 : : &use_variadic,
798 10981 : 6105 : context->inGroupBy));
6686 10982 : 6105 : nargs = 0;
10983 [ + + + + : 12777 : foreach(l, expr->args)
+ + ]
10984 : : {
10985 [ + + ]: 6672 : if (nargs++ > 0)
10986 : 1332 : appendStringInfoString(buf, ", ");
2670 10987 [ + + + - ]: 6672 : if (use_variadic && lnext(expr->args, l) == NULL)
6686 10988 : 7 : appendStringInfoString(buf, "VARIADIC ");
10989 : 6672 : get_rule_expr((Node *) lfirst(l), context, true);
10990 : : }
9755 10991 : 6105 : appendStringInfoChar(buf, ')');
10992 : : }
10993 : :
10994 : : /*
10995 : : * get_agg_expr - Parse back an Aggref node
10996 : : */
10997 : : static void
1526 andrew@dunslane.net 10998 : 3321 : get_agg_expr(Aggref *aggref, deparse_context *context,
10999 : : Aggref *original_aggref)
11000 : : {
1317 alvherre@alvh.no-ip. 11001 : 3321 : get_agg_expr_helper(aggref, context, original_aggref, NULL, NULL,
11002 : : false);
11003 : 3321 : }
11004 : :
11005 : : /*
11006 : : * get_agg_expr_helper - subroutine for get_agg_expr and
11007 : : * get_json_agg_constructor
11008 : : */
11009 : : static void
11010 : 3389 : get_agg_expr_helper(Aggref *aggref, deparse_context *context,
11011 : : Aggref *original_aggref, const char *funcname,
11012 : : const char *options, bool is_json_objectagg)
11013 : : {
8974 tgl@sss.pgh.pa.us 11014 : 3389 : StringInfo buf = context->buf;
11015 : : Oid argtypes[FUNC_MAX_ARGS];
11016 : : int nargs;
1317 alvherre@alvh.no-ip. 11017 : 3389 : bool use_variadic = false;
11018 : :
11019 : : /*
11020 : : * For a combining aggregate, we look up and deparse the corresponding
11021 : : * partial aggregate instead. This is necessary because our input
11022 : : * argument list has been replaced; the new argument list always has just
11023 : : * one element, which will point to a partial Aggref that supplies us with
11024 : : * transition states to combine.
11025 : : */
3784 tgl@sss.pgh.pa.us 11026 [ + + ]: 3389 : if (DO_AGGSPLIT_COMBINE(aggref->aggsplit))
11027 : : {
11028 : : TargetEntry *tle;
11029 : :
3844 rhaas@postgresql.org 11030 [ - + ]: 560 : Assert(list_length(aggref->args) == 1);
2521 tgl@sss.pgh.pa.us 11031 : 560 : tle = linitial_node(TargetEntry, aggref->args);
11032 : 560 : resolve_special_varno((Node *) tle->expr, context,
11033 : : get_agg_combine_expr, original_aggref);
3844 rhaas@postgresql.org 11034 : 560 : return;
11035 : : }
11036 : :
11037 : : /*
11038 : : * Mark as PARTIAL, if appropriate. We look to the original aggref so as
11039 : : * to avoid printing this when recursing from the code just above.
11040 : : */
3784 tgl@sss.pgh.pa.us 11041 [ + + ]: 2829 : if (DO_AGGSPLIT_SKIPFINAL(original_aggref->aggsplit))
3844 rhaas@postgresql.org 11042 : 1224 : appendStringInfoString(buf, "PARTIAL ");
11043 : :
11044 : : /* Extract the argument types as seen by the parser */
4700 tgl@sss.pgh.pa.us 11045 : 2829 : nargs = get_aggregate_argtypes(aggref, argtypes);
11046 : :
1317 alvherre@alvh.no-ip. 11047 [ + + ]: 2829 : if (!funcname)
11048 : 2761 : funcname = generate_function_name(aggref->aggfnoid, nargs, NIL,
11049 : 2761 : argtypes, aggref->aggvariadic,
11050 : : &use_variadic,
798 tgl@sss.pgh.pa.us 11051 : 2761 : context->inGroupBy);
11052 : :
11053 : : /* Print the aggregate name, schema-qualified if needed */
1317 alvherre@alvh.no-ip. 11054 : 2829 : appendStringInfo(buf, "%s(%s", funcname,
6169 tgl@sss.pgh.pa.us 11055 [ + + ]: 2829 : (aggref->aggdistinct != NIL) ? "DISTINCT " : "");
11056 : :
4700 11057 [ + + ]: 2829 : if (AGGKIND_IS_ORDERED_SET(aggref->aggkind))
11058 : : {
11059 : : /*
11060 : : * Ordered-set aggregates do not use "*" syntax. Also, we needn't
11061 : : * worry about inserting VARIADIC. So we can just dump the direct
11062 : : * args as-is.
11063 : : */
11064 [ - + ]: 17 : Assert(!aggref->aggvariadic);
11065 : 17 : get_rule_expr((Node *) aggref->aggdirectargs, context, true);
11066 [ - + ]: 17 : Assert(aggref->aggorder != NIL);
11067 : 17 : appendStringInfoString(buf, ") WITHIN GROUP (ORDER BY ");
11068 : 17 : get_rule_orderby(aggref->aggorder, aggref->args, false, context);
11069 : : }
11070 : : else
11071 : : {
11072 : : /* aggstar can be set only in zero-argument aggregates */
11073 [ + + ]: 2812 : if (aggref->aggstar)
11074 : 833 : appendStringInfoChar(buf, '*');
11075 : : else
11076 : : {
11077 : : ListCell *l;
11078 : : int i;
11079 : :
11080 : 1979 : i = 0;
11081 [ + - + + : 4097 : foreach(l, aggref->args)
+ + ]
11082 : : {
11083 : 2118 : TargetEntry *tle = (TargetEntry *) lfirst(l);
11084 : 2118 : Node *arg = (Node *) tle->expr;
11085 : :
11086 [ - + ]: 2118 : Assert(!IsA(arg, NamedArgExpr));
11087 [ + + ]: 2118 : if (tle->resjunk)
11088 : 31 : continue;
11089 [ + + ]: 2087 : if (i++ > 0)
11090 : : {
1317 alvherre@alvh.no-ip. 11091 [ + + ]: 108 : if (is_json_objectagg)
11092 : : {
11093 : : /*
11094 : : * the ABSENT ON NULL and WITH UNIQUE args are printed
11095 : : * separately, so ignore them here
11096 : : */
11097 [ - + ]: 28 : if (i > 2)
1317 alvherre@alvh.no-ip. 11098 :UBC 0 : break;
11099 : :
1317 alvherre@alvh.no-ip. 11100 :CBC 28 : appendStringInfoString(buf, " : ");
11101 : : }
11102 : : else
11103 : 80 : appendStringInfoString(buf, ", ");
11104 : : }
4700 tgl@sss.pgh.pa.us 11105 [ + + + - ]: 2087 : if (use_variadic && i == nargs)
11106 : 4 : appendStringInfoString(buf, "VARIADIC ");
11107 : 2087 : get_rule_expr(arg, context, true);
11108 : : }
11109 : : }
11110 : :
11111 [ + + ]: 2812 : if (aggref->aggorder != NIL)
11112 : : {
11113 : 78 : appendStringInfoString(buf, " ORDER BY ");
11114 : 78 : get_rule_orderby(aggref->aggorder, aggref->args, false, context);
11115 : : }
11116 : : }
11117 : :
1317 alvherre@alvh.no-ip. 11118 [ + + ]: 2829 : if (options)
11119 : 68 : appendStringInfoString(buf, options);
11120 : :
4860 noah@leadboat.com 11121 [ + + ]: 2829 : if (aggref->aggfilter != NULL)
11122 : : {
11123 : 28 : appendStringInfoString(buf, ") FILTER (WHERE ");
11124 : 28 : get_rule_expr((Node *) aggref->aggfilter, context, false);
11125 : : }
11126 : :
8974 tgl@sss.pgh.pa.us 11127 : 2829 : appendStringInfoChar(buf, ')');
11128 : : }
11129 : :
11130 : : /*
11131 : : * This is a helper function for get_agg_expr(). It's used when we deparse
11132 : : * a combining Aggref; resolve_special_varno locates the corresponding partial
11133 : : * Aggref and then calls this.
11134 : : */
11135 : : static void
2521 11136 : 560 : get_agg_combine_expr(Node *node, deparse_context *context, void *callback_arg)
11137 : : {
11138 : : Aggref *aggref;
11139 : 560 : Aggref *original_aggref = callback_arg;
11140 : :
3844 rhaas@postgresql.org 11141 [ - + ]: 560 : if (!IsA(node, Aggref))
3844 rhaas@postgresql.org 11142 [ # # ]:UBC 0 : elog(ERROR, "combining Aggref does not point to an Aggref");
11143 : :
3844 rhaas@postgresql.org 11144 :CBC 560 : aggref = (Aggref *) node;
11145 : 560 : get_agg_expr(aggref, context, original_aggref);
11146 : 560 : }
11147 : :
11148 : : /*
11149 : : * get_windowfunc_expr - Parse back a WindowFunc node
11150 : : */
11151 : : static void
1526 andrew@dunslane.net 11152 : 254 : get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context)
11153 : : {
1317 alvherre@alvh.no-ip. 11154 : 254 : get_windowfunc_expr_helper(wfunc, context, NULL, NULL, false);
11155 : 254 : }
11156 : :
11157 : :
11158 : : /*
11159 : : * get_windowfunc_expr_helper - subroutine for get_windowfunc_expr and
11160 : : * get_json_agg_constructor
11161 : : */
11162 : : static void
11163 : 266 : get_windowfunc_expr_helper(WindowFunc *wfunc, deparse_context *context,
11164 : : const char *funcname, const char *options,
11165 : : bool is_json_objectagg)
11166 : : {
6521 tgl@sss.pgh.pa.us 11167 : 266 : StringInfo buf = context->buf;
11168 : : Oid argtypes[FUNC_MAX_ARGS];
11169 : : int nargs;
11170 : : List *argnames;
11171 : : ListCell *l;
11172 : :
11173 [ - + ]: 266 : if (list_length(wfunc->args) > FUNC_MAX_ARGS)
6521 tgl@sss.pgh.pa.us 11174 [ # # ]:UBC 0 : ereport(ERROR,
11175 : : (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
11176 : : errmsg("too many arguments")));
6521 tgl@sss.pgh.pa.us 11177 :CBC 266 : nargs = 0;
4747 11178 : 266 : argnames = NIL;
6521 11179 [ + + + + : 440 : foreach(l, wfunc->args)
+ + ]
11180 : : {
6096 bruce@momjian.us 11181 : 174 : Node *arg = (Node *) lfirst(l);
11182 : :
4747 tgl@sss.pgh.pa.us 11183 [ - + ]: 174 : if (IsA(arg, NamedArgExpr))
4747 tgl@sss.pgh.pa.us 11184 :UBC 0 : argnames = lappend(argnames, ((NamedArgExpr *) arg)->name);
6237 tgl@sss.pgh.pa.us 11185 :CBC 174 : argtypes[nargs] = exprType(arg);
6521 11186 : 174 : nargs++;
11187 : : }
11188 : :
1317 alvherre@alvh.no-ip. 11189 [ + + ]: 266 : if (!funcname)
11190 : 254 : funcname = generate_function_name(wfunc->winfnoid, nargs, argnames,
11191 : : argtypes, false, NULL,
798 tgl@sss.pgh.pa.us 11192 : 254 : context->inGroupBy);
11193 : :
1317 alvherre@alvh.no-ip. 11194 : 266 : appendStringInfo(buf, "%s(", funcname);
11195 : :
11196 : : /* winstar can be set only in zero-argument aggregates */
6521 tgl@sss.pgh.pa.us 11197 [ + + ]: 266 : if (wfunc->winstar)
11198 : 28 : appendStringInfoChar(buf, '*');
11199 : : else
11200 : : {
1317 alvherre@alvh.no-ip. 11201 [ + + ]: 238 : if (is_json_objectagg)
11202 : : {
11203 : 4 : get_rule_expr((Node *) linitial(wfunc->args), context, false);
11204 : 4 : appendStringInfoString(buf, " : ");
11205 : 4 : get_rule_expr((Node *) lsecond(wfunc->args), context, false);
11206 : : }
11207 : : else
11208 : 234 : get_rule_expr((Node *) wfunc->args, context, true);
11209 : : }
11210 : :
11211 [ + + ]: 266 : if (options)
11212 : 12 : appendStringInfoString(buf, options);
11213 : :
4860 noah@leadboat.com 11214 [ - + ]: 266 : if (wfunc->aggfilter != NULL)
11215 : : {
4860 noah@leadboat.com 11216 :UBC 0 : appendStringInfoString(buf, ") FILTER (WHERE ");
11217 : 0 : get_rule_expr((Node *) wfunc->aggfilter, context, false);
11218 : : }
11219 : :
398 ishii@postgresql.org 11220 :CBC 266 : appendStringInfoString(buf, ") ");
11221 : :
11222 [ + + ]: 266 : if (wfunc->ignore_nulls == PARSER_IGNORE_NULLS)
11223 : 4 : appendStringInfoString(buf, "IGNORE NULLS ");
11224 : :
11225 : 266 : appendStringInfoString(buf, "OVER ");
11226 : :
604 tgl@sss.pgh.pa.us 11227 [ + + ]: 266 : if (context->windowClause)
11228 : : {
11229 : : /* Query-decompilation case: search the windowClause list */
11230 [ + - + - : 40 : foreach(l, context->windowClause)
+ - ]
11231 : : {
11232 : 40 : WindowClause *wc = (WindowClause *) lfirst(l);
11233 : :
11234 [ + - ]: 40 : if (wc->winref == wfunc->winref)
11235 : : {
11236 [ + + ]: 40 : if (wc->name)
11237 : 12 : appendStringInfoString(buf, quote_identifier(wc->name));
11238 : : else
11239 : 28 : get_rule_windowspec(wc, context->targetList, context);
11240 : 40 : break;
11241 : : }
11242 : : }
11243 [ - + ]: 40 : if (l == NULL)
6521 tgl@sss.pgh.pa.us 11244 [ # # ]:UBC 0 : elog(ERROR, "could not find window clause for winref %u",
11245 : : wfunc->winref);
11246 : : }
11247 : : else
11248 : : {
11249 : : /*
11250 : : * In EXPLAIN, search the namespace stack for a matching WindowAgg
11251 : : * node (probably it's always the first entry), and print winname.
11252 : : */
604 tgl@sss.pgh.pa.us 11253 [ + - + - :CBC 226 : foreach(l, context->namespaces)
+ - ]
11254 : : {
11255 : 226 : deparse_namespace *dpns = (deparse_namespace *) lfirst(l);
11256 : :
11257 [ + - + - ]: 226 : if (dpns->plan && IsA(dpns->plan, WindowAgg))
11258 : : {
11259 : 226 : WindowAgg *wagg = (WindowAgg *) dpns->plan;
11260 : :
11261 [ + - ]: 226 : if (wagg->winref == wfunc->winref)
11262 : : {
11263 : 226 : appendStringInfoString(buf, quote_identifier(wagg->winname));
11264 : 226 : break;
11265 : : }
11266 : : }
11267 : : }
11268 [ - + ]: 226 : if (l == NULL)
604 tgl@sss.pgh.pa.us 11269 [ # # ]:UBC 0 : elog(ERROR, "could not find window clause for winref %u",
11270 : : wfunc->winref);
11271 : : }
6521 tgl@sss.pgh.pa.us 11272 :CBC 266 : }
11273 : :
11274 : : /*
11275 : : * get_func_sql_syntax - Parse back a SQL-syntax function call
11276 : : *
11277 : : * Returns true if we successfully deparsed, false if we did not
11278 : : * recognize the function.
11279 : : *
11280 : : * Most of these print as constructs that func_expr_windowless accepts.
11281 : : * But AT TIME ZONE, AT LOCAL, OVERLAPS and IS xxx NORMALIZED print as a
11282 : : * parenthesized a_expr instead, so a function added to them, or another
11283 : : * one printed that way, must also be listed in looks_like_function().
11284 : : */
11285 : : static bool
2192 11286 : 124 : get_func_sql_syntax(FuncExpr *expr, deparse_context *context)
11287 : : {
11288 : 124 : StringInfo buf = context->buf;
11289 : 124 : Oid funcoid = expr->funcid;
11290 : :
11291 [ + + + + : 124 : switch (funcoid)
+ + + + +
+ + + + +
+ - + ]
11292 : : {
11293 : 16 : case F_TIMEZONE_INTERVAL_TIMESTAMP:
11294 : : case F_TIMEZONE_INTERVAL_TIMESTAMPTZ:
11295 : : case F_TIMEZONE_INTERVAL_TIMETZ:
11296 : : case F_TIMEZONE_TEXT_TIMESTAMP:
11297 : : case F_TIMEZONE_TEXT_TIMESTAMPTZ:
11298 : : case F_TIMEZONE_TEXT_TIMETZ:
11299 : : /* AT TIME ZONE ... note reversed argument order */
11300 : 16 : appendStringInfoChar(buf, '(');
1435 11301 : 16 : get_rule_expr_paren((Node *) lsecond(expr->args), context, false,
11302 : : (Node *) expr);
2192 11303 : 16 : appendStringInfoString(buf, " AT TIME ZONE ");
1435 11304 : 16 : get_rule_expr_paren((Node *) linitial(expr->args), context, false,
11305 : : (Node *) expr);
2192 11306 : 16 : appendStringInfoChar(buf, ')');
11307 : 16 : return true;
11308 : :
1119 michael@paquier.xyz 11309 : 12 : case F_TIMEZONE_TIMESTAMP:
11310 : : case F_TIMEZONE_TIMESTAMPTZ:
11311 : : case F_TIMEZONE_TIMETZ:
11312 : : /* AT LOCAL */
11313 : 12 : appendStringInfoChar(buf, '(');
11314 : 12 : get_rule_expr_paren((Node *) linitial(expr->args), context, false,
11315 : : (Node *) expr);
11316 : 12 : appendStringInfoString(buf, " AT LOCAL)");
11317 : 12 : return true;
11318 : :
2192 tgl@sss.pgh.pa.us 11319 : 4 : case F_OVERLAPS_TIMESTAMPTZ_INTERVAL_TIMESTAMPTZ_INTERVAL:
11320 : : case F_OVERLAPS_TIMESTAMPTZ_INTERVAL_TIMESTAMPTZ_TIMESTAMPTZ:
11321 : : case F_OVERLAPS_TIMESTAMPTZ_TIMESTAMPTZ_TIMESTAMPTZ_INTERVAL:
11322 : : case F_OVERLAPS_TIMESTAMPTZ_TIMESTAMPTZ_TIMESTAMPTZ_TIMESTAMPTZ:
11323 : : case F_OVERLAPS_TIMESTAMP_INTERVAL_TIMESTAMP_INTERVAL:
11324 : : case F_OVERLAPS_TIMESTAMP_INTERVAL_TIMESTAMP_TIMESTAMP:
11325 : : case F_OVERLAPS_TIMESTAMP_TIMESTAMP_TIMESTAMP_INTERVAL:
11326 : : case F_OVERLAPS_TIMESTAMP_TIMESTAMP_TIMESTAMP_TIMESTAMP:
11327 : : case F_OVERLAPS_TIMETZ_TIMETZ_TIMETZ_TIMETZ:
11328 : : case F_OVERLAPS_TIME_INTERVAL_TIME_INTERVAL:
11329 : : case F_OVERLAPS_TIME_INTERVAL_TIME_TIME:
11330 : : case F_OVERLAPS_TIME_TIME_TIME_INTERVAL:
11331 : : case F_OVERLAPS_TIME_TIME_TIME_TIME:
11332 : : /* (x1, x2) OVERLAPS (y1, y2) */
11333 : 4 : appendStringInfoString(buf, "((");
11334 : 4 : get_rule_expr((Node *) linitial(expr->args), context, false);
11335 : 4 : appendStringInfoString(buf, ", ");
11336 : 4 : get_rule_expr((Node *) lsecond(expr->args), context, false);
11337 : 4 : appendStringInfoString(buf, ") OVERLAPS (");
11338 : 4 : get_rule_expr((Node *) lthird(expr->args), context, false);
11339 : 4 : appendStringInfoString(buf, ", ");
11340 : 4 : get_rule_expr((Node *) lfourth(expr->args), context, false);
11341 : 4 : appendStringInfoString(buf, "))");
11342 : 4 : return true;
11343 : :
2039 peter@eisentraut.org 11344 : 12 : case F_EXTRACT_TEXT_DATE:
11345 : : case F_EXTRACT_TEXT_TIME:
11346 : : case F_EXTRACT_TEXT_TIMETZ:
11347 : : case F_EXTRACT_TEXT_TIMESTAMP:
11348 : : case F_EXTRACT_TEXT_TIMESTAMPTZ:
11349 : : case F_EXTRACT_TEXT_INTERVAL:
11350 : : /* EXTRACT (x FROM y) */
11351 : 12 : appendStringInfoString(buf, "EXTRACT(");
11352 : : {
11353 : 12 : Const *con = (Const *) linitial(expr->args);
11354 : :
11355 [ + - + - : 12 : Assert(IsA(con, Const) &&
- + ]
11356 : : con->consttype == TEXTOID &&
11357 : : !con->constisnull);
87 nathan@postgresql.or 11358 : 12 : appendStringInfoString(buf, quote_identifier(TextDatumGetCString(con->constvalue)));
11359 : : }
2039 peter@eisentraut.org 11360 : 12 : appendStringInfoString(buf, " FROM ");
11361 : 12 : get_rule_expr((Node *) lsecond(expr->args), context, false);
11362 : 12 : appendStringInfoChar(buf, ')');
11363 : 12 : return true;
11364 : :
2192 tgl@sss.pgh.pa.us 11365 : 12 : case F_IS_NORMALIZED:
11366 : : /* IS xxx NORMALIZED */
1129 drowley@postgresql.o 11367 : 12 : appendStringInfoChar(buf, '(');
1435 tgl@sss.pgh.pa.us 11368 : 12 : get_rule_expr_paren((Node *) linitial(expr->args), context, false,
11369 : : (Node *) expr);
11370 : 12 : appendStringInfoString(buf, " IS");
2192 11371 [ + + ]: 12 : if (list_length(expr->args) == 2)
11372 : : {
11373 : 4 : Const *con = (Const *) lsecond(expr->args);
11374 : :
11375 [ + - + - : 4 : Assert(IsA(con, Const) &&
- + ]
11376 : : con->consttype == TEXTOID &&
11377 : : !con->constisnull);
11378 : : /* NB: safe because no allowed words need quoted/escaped */
11379 : 4 : appendStringInfo(buf, " %s",
11380 : 4 : TextDatumGetCString(con->constvalue));
11381 : : }
11382 : 12 : appendStringInfoString(buf, " NORMALIZED)");
11383 : 12 : return true;
11384 : :
11385 : 4 : case F_PG_COLLATION_FOR:
11386 : : /* COLLATION FOR */
11387 : 4 : appendStringInfoString(buf, "COLLATION FOR (");
11388 : 4 : get_rule_expr((Node *) linitial(expr->args), context, false);
11389 : 4 : appendStringInfoChar(buf, ')');
11390 : 4 : return true;
11391 : :
11392 : 8 : case F_NORMALIZE:
11393 : : /* NORMALIZE() */
11394 : 8 : appendStringInfoString(buf, "NORMALIZE(");
11395 : 8 : get_rule_expr((Node *) linitial(expr->args), context, false);
11396 [ + + ]: 8 : if (list_length(expr->args) == 2)
11397 : : {
11398 : 4 : Const *con = (Const *) lsecond(expr->args);
11399 : :
11400 [ + - + - : 4 : Assert(IsA(con, Const) &&
- + ]
11401 : : con->consttype == TEXTOID &&
11402 : : !con->constisnull);
11403 : 4 : appendStringInfo(buf, ", %s",
11404 : 4 : TextDatumGetCString(con->constvalue));
11405 : : }
11406 : 8 : appendStringInfoChar(buf, ')');
11407 : 8 : return true;
11408 : :
11409 : 8 : case F_OVERLAY_BIT_BIT_INT4:
11410 : : case F_OVERLAY_BIT_BIT_INT4_INT4:
11411 : : case F_OVERLAY_BYTEA_BYTEA_INT4:
11412 : : case F_OVERLAY_BYTEA_BYTEA_INT4_INT4:
11413 : : case F_OVERLAY_TEXT_TEXT_INT4:
11414 : : case F_OVERLAY_TEXT_TEXT_INT4_INT4:
11415 : : /* OVERLAY() */
11416 : 8 : appendStringInfoString(buf, "OVERLAY(");
11417 : 8 : get_rule_expr((Node *) linitial(expr->args), context, false);
11418 : 8 : appendStringInfoString(buf, " PLACING ");
11419 : 8 : get_rule_expr((Node *) lsecond(expr->args), context, false);
11420 : 8 : appendStringInfoString(buf, " FROM ");
11421 : 8 : get_rule_expr((Node *) lthird(expr->args), context, false);
11422 [ + + ]: 8 : if (list_length(expr->args) == 4)
11423 : : {
11424 : 4 : appendStringInfoString(buf, " FOR ");
11425 : 4 : get_rule_expr((Node *) lfourth(expr->args), context, false);
11426 : : }
11427 : 8 : appendStringInfoChar(buf, ')');
11428 : 8 : return true;
11429 : :
11430 : 4 : case F_POSITION_BIT_BIT:
11431 : : case F_POSITION_BYTEA_BYTEA:
11432 : : case F_POSITION_TEXT_TEXT:
11433 : : /* POSITION() ... extra parens since args are b_expr not a_expr */
11434 : 4 : appendStringInfoString(buf, "POSITION((");
11435 : 4 : get_rule_expr((Node *) lsecond(expr->args), context, false);
11436 : 4 : appendStringInfoString(buf, ") IN (");
11437 : 4 : get_rule_expr((Node *) linitial(expr->args), context, false);
11438 : 4 : appendStringInfoString(buf, "))");
11439 : 4 : return true;
11440 : :
11441 : 4 : case F_SUBSTRING_BIT_INT4:
11442 : : case F_SUBSTRING_BIT_INT4_INT4:
11443 : : case F_SUBSTRING_BYTEA_INT4:
11444 : : case F_SUBSTRING_BYTEA_INT4_INT4:
11445 : : case F_SUBSTRING_TEXT_INT4:
11446 : : case F_SUBSTRING_TEXT_INT4_INT4:
11447 : : /* SUBSTRING FROM/FOR (i.e., integer-position variants) */
11448 : 4 : appendStringInfoString(buf, "SUBSTRING(");
11449 : 4 : get_rule_expr((Node *) linitial(expr->args), context, false);
11450 : 4 : appendStringInfoString(buf, " FROM ");
11451 : 4 : get_rule_expr((Node *) lsecond(expr->args), context, false);
11452 [ + - ]: 4 : if (list_length(expr->args) == 3)
11453 : : {
11454 : 4 : appendStringInfoString(buf, " FOR ");
11455 : 4 : get_rule_expr((Node *) lthird(expr->args), context, false);
11456 : : }
11457 : 4 : appendStringInfoChar(buf, ')');
11458 : 4 : return true;
11459 : :
11460 : 4 : case F_SUBSTRING_TEXT_TEXT_TEXT:
11461 : : /* SUBSTRING SIMILAR/ESCAPE */
11462 : 4 : appendStringInfoString(buf, "SUBSTRING(");
11463 : 4 : get_rule_expr((Node *) linitial(expr->args), context, false);
11464 : 4 : appendStringInfoString(buf, " SIMILAR ");
11465 : 4 : get_rule_expr((Node *) lsecond(expr->args), context, false);
11466 : 4 : appendStringInfoString(buf, " ESCAPE ");
11467 : 4 : get_rule_expr((Node *) lthird(expr->args), context, false);
11468 : 4 : appendStringInfoChar(buf, ')');
11469 : 4 : return true;
11470 : :
11471 : 8 : case F_BTRIM_BYTEA_BYTEA:
11472 : : case F_BTRIM_TEXT:
11473 : : case F_BTRIM_TEXT_TEXT:
11474 : : /* TRIM() */
11475 : 8 : appendStringInfoString(buf, "TRIM(BOTH");
11476 [ + - ]: 8 : if (list_length(expr->args) == 2)
11477 : : {
11478 : 8 : appendStringInfoChar(buf, ' ');
11479 : 8 : get_rule_expr((Node *) lsecond(expr->args), context, false);
11480 : : }
11481 : 8 : appendStringInfoString(buf, " FROM ");
11482 : 8 : get_rule_expr((Node *) linitial(expr->args), context, false);
11483 : 8 : appendStringInfoChar(buf, ')');
11484 : 8 : return true;
11485 : :
2117 11486 : 8 : case F_LTRIM_BYTEA_BYTEA:
11487 : : case F_LTRIM_TEXT:
11488 : : case F_LTRIM_TEXT_TEXT:
11489 : : /* TRIM() */
2192 11490 : 8 : appendStringInfoString(buf, "TRIM(LEADING");
11491 [ + - ]: 8 : if (list_length(expr->args) == 2)
11492 : : {
11493 : 8 : appendStringInfoChar(buf, ' ');
11494 : 8 : get_rule_expr((Node *) lsecond(expr->args), context, false);
11495 : : }
11496 : 8 : appendStringInfoString(buf, " FROM ");
11497 : 8 : get_rule_expr((Node *) linitial(expr->args), context, false);
11498 : 8 : appendStringInfoChar(buf, ')');
11499 : 8 : return true;
11500 : :
2117 11501 : 8 : case F_RTRIM_BYTEA_BYTEA:
11502 : : case F_RTRIM_TEXT:
11503 : : case F_RTRIM_TEXT_TEXT:
11504 : : /* TRIM() */
2192 11505 : 8 : appendStringInfoString(buf, "TRIM(TRAILING");
11506 [ + + ]: 8 : if (list_length(expr->args) == 2)
11507 : : {
11508 : 4 : appendStringInfoChar(buf, ' ');
11509 : 4 : get_rule_expr((Node *) lsecond(expr->args), context, false);
11510 : : }
11511 : 8 : appendStringInfoString(buf, " FROM ");
11512 : 8 : get_rule_expr((Node *) linitial(expr->args), context, false);
11513 : 8 : appendStringInfoChar(buf, ')');
11514 : 8 : return true;
11515 : :
1498 michael@paquier.xyz 11516 : 8 : case F_SYSTEM_USER:
11517 : 8 : appendStringInfoString(buf, "SYSTEM_USER");
11518 : 8 : return true;
11519 : :
2192 tgl@sss.pgh.pa.us 11520 :UBC 0 : case F_XMLEXISTS:
11521 : : /* XMLEXISTS ... extra parens because args are c_expr */
11522 : 0 : appendStringInfoString(buf, "XMLEXISTS((");
11523 : 0 : get_rule_expr((Node *) linitial(expr->args), context, false);
11524 : 0 : appendStringInfoString(buf, ") PASSING (");
11525 : 0 : get_rule_expr((Node *) lsecond(expr->args), context, false);
11526 : 0 : appendStringInfoString(buf, "))");
11527 : 0 : return true;
11528 : : }
2192 tgl@sss.pgh.pa.us 11529 :CBC 4 : return false;
11530 : : }
11531 : :
11532 : : /* ----------
11533 : : * get_coercion_expr
11534 : : *
11535 : : * Make a string representation of a value coerced to a specific type
11536 : : * ----------
11537 : : */
11538 : : static void
7173 11539 : 3726 : get_coercion_expr(Node *arg, deparse_context *context,
11540 : : Oid resulttype, int32 resulttypmod,
11541 : : Node *parentNode)
11542 : : {
11543 : 3726 : StringInfo buf = context->buf;
11544 : :
11545 : : /*
11546 : : * Since parse_coerce.c doesn't immediately collapse application of
11547 : : * length-coercion functions to constants, what we'll typically see in
11548 : : * such cases is a Const with typmod -1 and a length-coercion function
11549 : : * right above it. Avoid generating redundant output. However, beware of
11550 : : * suppressing casts when the user actually wrote something like
11551 : : * 'foo'::text::char(3).
11552 : : *
11553 : : * Note: it might seem that we are missing the possibility of needing to
11554 : : * print a COLLATE clause for such a Const. However, a Const could only
11555 : : * have nondefault collation in a post-constant-folding tree, in which the
11556 : : * length coercion would have been folded too. See also the special
11557 : : * handling of CollateExpr in coerce_to_target_type(): any collation
11558 : : * marking will be above the coercion node, not below it.
11559 : : */
11560 [ + - + + ]: 3726 : if (arg && IsA(arg, Const) &&
11561 [ + + ]: 376 : ((Const *) arg)->consttype == resulttype &&
11562 [ + - ]: 16 : ((Const *) arg)->consttypmod == -1)
11563 : : {
11564 : : /* Show the constant without normal ::typename decoration */
6878 11565 : 16 : get_const_expr((Const *) arg, context, -1);
11566 : : }
11567 : : else
11568 : : {
7173 11569 [ + + ]: 3710 : if (!PRETTY_PAREN(context))
11570 : 3445 : appendStringInfoChar(buf, '(');
11571 : 3710 : get_rule_expr_paren(arg, context, false, parentNode);
11572 [ + + ]: 3710 : if (!PRETTY_PAREN(context))
11573 : 3445 : appendStringInfoChar(buf, ')');
11574 : : }
11575 : :
11576 : : /*
11577 : : * Never emit resulttype(arg) functional notation. A pg_proc entry could
11578 : : * take precedence, and a resulttype in pg_temp would require schema
11579 : : * qualification that format_type_with_typemod() would usually omit. We've
11580 : : * standardized on arg::resulttype, but CAST(arg AS resulttype) notation
11581 : : * would work fine.
11582 : : */
11583 : 3726 : appendStringInfo(buf, "::%s",
11584 : : format_type_with_typemod(resulttype, resulttypmod));
11585 : 3726 : }
11586 : :
11587 : : /* ----------
11588 : : * get_const_expr
11589 : : *
11590 : : * Make a string representation of a Const
11591 : : *
11592 : : * showtype can be -1 to never show "::typename" decoration, or +1 to always
11593 : : * show it, or 0 to show it only if the constant wouldn't be assumed to be
11594 : : * the right type by default.
11595 : : *
11596 : : * If the Const's collation isn't default for its type, show that too.
11597 : : * We mustn't do this when showtype is -1 (since that means the caller will
11598 : : * print "::typename", and we can't put a COLLATE clause in between). It's
11599 : : * caller's responsibility that collation isn't missed in such cases.
11600 : : * ----------
11601 : : */
11602 : : static void
6878 11603 : 48035 : get_const_expr(Const *constval, deparse_context *context, int showtype)
11604 : : {
9895 11605 : 48035 : StringInfo buf = context->buf;
11606 : : Oid typoutput;
11607 : : bool typIsVarlena;
11608 : : char *extval;
4238 11609 : 48035 : bool needlabel = false;
11610 : :
9813 11611 [ + + ]: 48035 : if (constval->constisnull)
11612 : : {
11613 : : /*
11614 : : * Always label the type of a NULL constant to prevent misdecisions
11615 : : * about type when reparsing.
11616 : : */
4753 rhaas@postgresql.org 11617 : 975 : appendStringInfoString(buf, "NULL");
6878 tgl@sss.pgh.pa.us 11618 [ + + ]: 975 : if (showtype >= 0)
11619 : : {
7173 11620 : 944 : appendStringInfo(buf, "::%s",
11621 : : format_type_with_typemod(constval->consttype,
11622 : : constval->consttypmod));
5718 11623 : 944 : get_const_collation(constval, context);
11624 : : }
9813 11625 : 5806 : return;
11626 : : }
11627 : :
8187 11628 : 47060 : getTypeOutputInfo(constval->consttype,
11629 : : &typoutput, &typIsVarlena);
11630 : :
7520 11631 : 47060 : extval = OidOutputFunctionCall(typoutput, constval->constvalue);
11632 : :
9894 11633 [ + + + + ]: 47060 : switch (constval->consttype)
11634 : : {
11635 : 27532 : case INT4OID:
11636 : :
11637 : : /*
11638 : : * INT4 can be printed without any decoration, unless it is
11639 : : * negative; in that case print it as '-nnn'::integer to ensure
11640 : : * that the output will re-parse as a constant, not as a constant
11641 : : * plus operator. In most cases we could get away with printing
11642 : : * (-nnn) instead, because of the way that gram.y handles negative
11643 : : * literals; but that doesn't work for INT_MIN, and it doesn't
11644 : : * seem that much prettier anyway.
11645 : : */
4238 11646 [ + + ]: 27532 : if (extval[0] != '-')
11647 : 27182 : appendStringInfoString(buf, extval);
11648 : : else
11649 : : {
11650 : 350 : appendStringInfo(buf, "'%s'", extval);
3424 11651 : 350 : needlabel = true; /* we must attach a cast */
11652 : : }
4238 11653 : 27532 : break;
11654 : :
8845 peter_e@gmx.net 11655 : 728 : case NUMERICOID:
11656 : :
11657 : : /*
11658 : : * NUMERIC can be printed without quotes if it looks like a float
11659 : : * constant (not an integer, and not Infinity or NaN) and doesn't
11660 : : * have a leading sign (for the same reason as for INT4).
11661 : : */
4238 tgl@sss.pgh.pa.us 11662 [ + - ]: 728 : if (isdigit((unsigned char) extval[0]) &&
11663 [ + + ]: 728 : strcspn(extval, "eE.") != strlen(extval))
11664 : : {
11665 : 251 : appendStringInfoString(buf, extval);
11666 : : }
11667 : : else
11668 : : {
11669 : 477 : appendStringInfo(buf, "'%s'", extval);
3424 11670 : 477 : needlabel = true; /* we must attach a cast */
11671 : : }
8828 bruce@momjian.us 11672 : 728 : break;
11673 : :
8845 peter_e@gmx.net 11674 : 1191 : case BOOLOID:
8828 bruce@momjian.us 11675 [ + + ]: 1191 : if (strcmp(extval, "t") == 0)
4753 rhaas@postgresql.org 11676 : 452 : appendStringInfoString(buf, "true");
11677 : : else
11678 : 739 : appendStringInfoString(buf, "false");
8845 peter_e@gmx.net 11679 : 1191 : break;
11680 : :
11681 : 17609 : default:
6634 tgl@sss.pgh.pa.us 11682 : 17609 : simple_quote_literal(buf, extval);
9894 11683 : 17609 : break;
11684 : : }
11685 : :
9896 11686 : 47060 : pfree(extval);
11687 : :
6878 11688 [ + + ]: 47060 : if (showtype < 0)
7173 11689 : 4831 : return;
11690 : :
11691 : : /*
11692 : : * For showtype == 0, append ::typename unless the constant will be
11693 : : * implicitly typed as the right type when it is read in.
11694 : : *
11695 : : * XXX this code has to be kept in sync with the behavior of the parser,
11696 : : * especially make_const.
11697 : : */
9894 11698 [ + + + + ]: 42229 : switch (constval->consttype)
11699 : : {
8845 peter_e@gmx.net 11700 : 1241 : case BOOLOID:
11701 : : case UNKNOWNOID:
11702 : : /* These types can be left unlabeled */
8814 tgl@sss.pgh.pa.us 11703 : 1241 : needlabel = false;
11704 : 1241 : break;
4238 11705 : 24911 : case INT4OID:
11706 : : /* We determined above whether a label is needed */
11707 : 24911 : break;
8814 11708 : 728 : case NUMERICOID:
11709 : :
11710 : : /*
11711 : : * Float-looking constants will be typed as numeric, which we
11712 : : * checked above; but if there's a nondefault typmod we need to
11713 : : * show it.
11714 : : */
4238 11715 : 728 : needlabel |= (constval->consttypmod >= 0);
9894 11716 : 728 : break;
11717 : 15349 : default:
8814 11718 : 15349 : needlabel = true;
9894 11719 : 15349 : break;
11720 : : }
6878 11721 [ + + - + ]: 42229 : if (needlabel || showtype > 0)
8814 11722 : 16170 : appendStringInfo(buf, "::%s",
11723 : : format_type_with_typemod(constval->consttype,
11724 : : constval->consttypmod));
11725 : :
5718 11726 : 42229 : get_const_collation(constval, context);
11727 : : }
11728 : :
11729 : : /*
11730 : : * helper for get_const_expr: append COLLATE if needed
11731 : : */
11732 : : static void
11733 : 43173 : get_const_collation(Const *constval, deparse_context *context)
11734 : : {
11735 : 43173 : StringInfo buf = context->buf;
11736 : :
11737 [ + + ]: 43173 : if (OidIsValid(constval->constcollid))
11738 : : {
5688 bruce@momjian.us 11739 : 5978 : Oid typcollation = get_typcollation(constval->consttype);
11740 : :
5718 tgl@sss.pgh.pa.us 11741 [ + + ]: 5978 : if (constval->constcollid != typcollation)
11742 : : {
11743 : 160 : appendStringInfo(buf, " COLLATE %s",
11744 : : generate_collation_name(constval->constcollid));
11745 : : }
11746 : : }
10261 bruce@momjian.us 11747 : 43173 : }
11748 : :
11749 : : /*
11750 : : * get_json_path_spec - Parse back a JSON path specification
11751 : : */
11752 : : static void
959 amitlan@postgresql.o 11753 : 436 : get_json_path_spec(Node *path_spec, deparse_context *context, bool showimplicit)
11754 : : {
11755 [ + - ]: 436 : if (IsA(path_spec, Const))
11756 : 436 : get_const_expr((Const *) path_spec, context, -1);
11757 : : else
959 amitlan@postgresql.o 11758 :UBC 0 : get_rule_expr(path_spec, context, showimplicit);
959 amitlan@postgresql.o 11759 :CBC 436 : }
11760 : :
11761 : : /*
11762 : : * get_json_format - Parse back a JsonFormat node
11763 : : */
11764 : : static void
1317 alvherre@alvh.no-ip. 11765 : 148 : get_json_format(JsonFormat *format, StringInfo buf)
11766 : : {
11767 [ + + ]: 148 : if (format->format_type == JS_FORMAT_DEFAULT)
11768 : 92 : return;
11769 : :
11770 : 56 : appendStringInfoString(buf,
11771 [ - + ]: 56 : format->format_type == JS_FORMAT_JSONB ?
11772 : : " FORMAT JSONB" : " FORMAT JSON");
11773 : :
11774 [ + + ]: 56 : if (format->encoding != JS_ENC_DEFAULT)
11775 : : {
11776 : : const char *encoding;
11777 : :
11778 : 4 : encoding =
11779 [ + - ]: 8 : format->encoding == JS_ENC_UTF16 ? "UTF16" :
11780 [ - + ]: 4 : format->encoding == JS_ENC_UTF32 ? "UTF32" : "UTF8";
11781 : :
11782 : 4 : appendStringInfo(buf, " ENCODING %s", encoding);
11783 : : }
11784 : : }
11785 : :
11786 : : /*
11787 : : * get_json_returning - Parse back a JsonReturning structure
11788 : : */
11789 : : static void
11790 : 212 : get_json_returning(JsonReturning *returning, StringInfo buf,
11791 : : bool json_format_by_default)
11792 : : {
11793 [ - + ]: 212 : if (!OidIsValid(returning->typid))
1317 alvherre@alvh.no-ip. 11794 :UBC 0 : return;
11795 : :
1317 alvherre@alvh.no-ip. 11796 :CBC 212 : appendStringInfo(buf, " RETURNING %s",
11797 : : format_type_with_typemod(returning->typid,
11798 : : returning->typmod));
11799 : :
11800 [ + + + + ]: 416 : if (!json_format_by_default ||
11801 : 204 : returning->format->format_type !=
11802 [ + + ]: 204 : (returning->typid == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON))
11803 : 24 : get_json_format(returning->format, buf);
11804 : : }
11805 : :
11806 : : /*
11807 : : * get_json_constructor - Parse back a JsonConstructorExpr node
11808 : : */
11809 : : static void
11810 : 216 : get_json_constructor(JsonConstructorExpr *ctor, deparse_context *context,
11811 : : bool showimplicit)
11812 : : {
11813 : 216 : StringInfo buf = context->buf;
11814 : : const char *funcname;
11815 : : bool is_json_object;
11816 : : int curridx;
11817 : : ListCell *lc;
11818 : :
11819 [ + + ]: 216 : if (ctor->type == JSCTOR_JSON_OBJECTAGG)
11820 : : {
11821 : 40 : get_json_agg_constructor(ctor, context, "JSON_OBJECTAGG", true);
11822 : 40 : return;
11823 : : }
11824 [ + + ]: 176 : else if (ctor->type == JSCTOR_JSON_ARRAYAGG)
11825 : : {
11826 : 68 : get_json_agg_constructor(ctor, context, "JSON_ARRAYAGG", false);
11827 : 68 : return;
11828 : : }
188 rguo@postgresql.org 11829 [ + + ]: 108 : else if (ctor->type == JSCTOR_JSON_ARRAY_QUERY)
11830 : : {
11831 : 20 : Query *query = castNode(Query, ctor->orig_query);
11832 : :
11833 : 20 : appendStringInfo(buf, "JSON_ARRAY(");
11834 : :
11835 : 20 : get_query_def(query, buf, context->namespaces, NULL, false,
11836 : : context->prettyFlags, context->wrapColumn,
11837 : : context->indentLevel);
11838 : :
101 11839 : 20 : get_json_format(ctor->format, buf);
188 11840 : 20 : get_json_constructor_options(ctor, buf);
11841 : 20 : appendStringInfoChar(buf, ')');
11842 : :
11843 : 20 : return;
11844 : : }
11845 : :
1317 alvherre@alvh.no-ip. 11846 [ + + + + : 88 : switch (ctor->type)
+ - ]
11847 : : {
11848 : 28 : case JSCTOR_JSON_OBJECT:
11849 : 28 : funcname = "JSON_OBJECT";
11850 : 28 : break;
11851 : 16 : case JSCTOR_JSON_ARRAY:
11852 : 16 : funcname = "JSON_ARRAY";
11853 : 16 : break;
1204 amitlan@postgresql.o 11854 : 28 : case JSCTOR_JSON_PARSE:
11855 : 28 : funcname = "JSON";
11856 : 28 : break;
11857 : 8 : case JSCTOR_JSON_SCALAR:
11858 : 8 : funcname = "JSON_SCALAR";
11859 : 8 : break;
11860 : 8 : case JSCTOR_JSON_SERIALIZE:
11861 : 8 : funcname = "JSON_SERIALIZE";
11862 : 8 : break;
1317 alvherre@alvh.no-ip. 11863 :UBC 0 : default:
1316 11864 [ # # ]: 0 : elog(ERROR, "invalid JsonConstructorType %d", ctor->type);
11865 : : }
11866 : :
1317 alvherre@alvh.no-ip. 11867 :CBC 88 : appendStringInfo(buf, "%s(", funcname);
11868 : :
11869 : 88 : is_json_object = ctor->type == JSCTOR_JSON_OBJECT;
11870 [ + - + + : 236 : foreach(lc, ctor->args)
+ + ]
11871 : : {
11872 : 148 : curridx = foreach_current_index(lc);
11873 [ + + ]: 148 : if (curridx > 0)
11874 : : {
11875 : : const char *sep;
11876 : :
11877 [ + + + + ]: 60 : sep = (is_json_object && (curridx % 2) != 0) ? " : " : ", ";
11878 : 60 : appendStringInfoString(buf, sep);
11879 : : }
11880 : :
11881 : 148 : get_rule_expr((Node *) lfirst(lc), context, true);
11882 : : }
11883 : :
11884 : 88 : get_json_constructor_options(ctor, buf);
1129 drowley@postgresql.o 11885 : 88 : appendStringInfoChar(buf, ')');
11886 : : }
11887 : :
11888 : : /*
11889 : : * Append options, if any, to the JSON constructor being deparsed
11890 : : */
11891 : : static void
1317 alvherre@alvh.no-ip. 11892 : 216 : get_json_constructor_options(JsonConstructorExpr *ctor, StringInfo buf)
11893 : : {
11894 [ + + ]: 216 : if (ctor->absent_on_null)
11895 : : {
11896 [ + - ]: 84 : if (ctor->type == JSCTOR_JSON_OBJECT ||
11897 [ + + ]: 84 : ctor->type == JSCTOR_JSON_OBJECTAGG)
11898 : 8 : appendStringInfoString(buf, " ABSENT ON NULL");
11899 : : }
11900 : : else
11901 : : {
11902 [ + - ]: 132 : if (ctor->type == JSCTOR_JSON_ARRAY ||
11903 [ + + ]: 132 : ctor->type == JSCTOR_JSON_ARRAYAGG)
11904 : 28 : appendStringInfoString(buf, " NULL ON NULL");
11905 : : }
11906 : :
11907 [ + + ]: 216 : if (ctor->unique)
11908 : 24 : appendStringInfoString(buf, " WITH UNIQUE KEYS");
11909 : :
11910 : : /*
11911 : : * Append RETURNING clause if needed; JSON() and JSON_SCALAR() don't
11912 : : * support one.
11913 : : */
1204 amitlan@postgresql.o 11914 [ + + + + ]: 216 : if (ctor->type != JSCTOR_JSON_PARSE && ctor->type != JSCTOR_JSON_SCALAR)
11915 : 180 : get_json_returning(ctor->returning, buf, true);
1317 alvherre@alvh.no-ip. 11916 : 216 : }
11917 : :
11918 : : /*
11919 : : * get_json_agg_constructor - Parse back an aggregate JsonConstructorExpr node
11920 : : */
11921 : : static void
11922 : 108 : get_json_agg_constructor(JsonConstructorExpr *ctor, deparse_context *context,
11923 : : const char *funcname, bool is_json_objectagg)
11924 : : {
11925 : : StringInfoData options;
11926 : :
11927 : 108 : initStringInfo(&options);
11928 : 108 : get_json_constructor_options(ctor, &options);
11929 : :
11930 [ + + ]: 108 : if (IsA(ctor->func, Aggref))
11931 : 68 : get_agg_expr_helper((Aggref *) ctor->func, context,
11932 : 68 : (Aggref *) ctor->func,
11933 : 68 : funcname, options.data, is_json_objectagg);
11934 [ + + ]: 40 : else if (IsA(ctor->func, WindowFunc))
11935 : 12 : get_windowfunc_expr_helper((WindowFunc *) ctor->func, context,
11936 : 12 : funcname, options.data,
11937 : : is_json_objectagg);
120 rguo@postgresql.org 11938 [ + - ]: 28 : else if (IsA(ctor->func, Var))
11939 : : {
11940 : : /*
11941 : : * If the aggregate is computed by a lower plan node, setrefs.c will
11942 : : * have replaced the Aggref or WindowFunc with a Var referencing that
11943 : : * node's output. Chase the Var back to it so we can still print the
11944 : : * original JSON aggregate syntax. This only happens in EXPLAIN.
11945 : : */
11946 : 28 : resolve_special_varno((Node *) ctor->func, context,
11947 : : get_json_agg_constructor_expr, ctor);
11948 : : }
11949 : : else
1317 alvherre@alvh.no-ip. 11950 [ # # ]:UBC 0 : elog(ERROR, "invalid JsonConstructorExpr underlying node type: %d",
11951 : : nodeTag(ctor->func));
1317 alvherre@alvh.no-ip. 11952 :CBC 108 : }
11953 : :
11954 : : /*
11955 : : * Deparse a JsonConstructorExpr whose aggregate is computed by a lower plan
11956 : : * node; resolve_special_varno has located the underlying Aggref/WindowFunc.
11957 : : */
11958 : : static void
120 rguo@postgresql.org 11959 : 28 : get_json_agg_constructor_expr(Node *node, deparse_context *context,
11960 : : void *callback_arg)
11961 : : {
11962 : : JsonConstructorExpr ctor;
11963 : :
11964 [ + + - + ]: 28 : if (!IsA(node, Aggref) && !IsA(node, WindowFunc))
120 rguo@postgresql.org 11965 [ # # ]:UBC 0 : elog(ERROR, "JSON aggregate constructor does not point to an Aggref or WindowFunc");
11966 : :
11967 : : /* Flat copy suffices; we only replace func. */
120 rguo@postgresql.org 11968 :CBC 28 : ctor = *(JsonConstructorExpr *) callback_arg;
11969 : 28 : ctor.func = (Expr *) node;
11970 : 28 : get_json_constructor(&ctor, context, false);
11971 : 28 : }
11972 : :
11973 : : /*
11974 : : * simple_quote_literal - Format a string as a SQL literal, append to buf
11975 : : */
11976 : : static void
6634 tgl@sss.pgh.pa.us 11977 : 18030 : simple_quote_literal(StringInfo buf, const char *val)
11978 : : {
11979 : : const char *valptr;
11980 : :
11981 : : /*
11982 : : * We always form the string literal according to standard SQL rules.
11983 : : */
11984 : 18030 : appendStringInfoChar(buf, '\'');
11985 [ + + ]: 181446 : for (valptr = val; *valptr; valptr++)
11986 : : {
11987 : 163416 : char ch = *valptr;
11988 : :
288 11989 [ + + ]: 163416 : if (SQL_STR_DOUBLE(ch, false))
6634 11990 : 204 : appendStringInfoChar(buf, ch);
11991 : 163416 : appendStringInfoChar(buf, ch);
11992 : : }
11993 : 18030 : appendStringInfoChar(buf, '\'');
11994 : 18030 : }
11995 : :
11996 : :
11997 : : /* ----------
11998 : : * get_sublink_expr - Parse back a sublink
11999 : : * ----------
12000 : : */
12001 : : static void
8729 12002 : 306 : get_sublink_expr(SubLink *sublink, deparse_context *context)
12003 : : {
9895 12004 : 306 : StringInfo buf = context->buf;
10261 bruce@momjian.us 12005 : 306 : Query *query = (Query *) (sublink->subselect);
7617 tgl@sss.pgh.pa.us 12006 : 306 : char *opname = NULL;
12007 : : bool need_paren;
12008 : :
8612 12009 [ + + ]: 306 : if (sublink->subLinkType == ARRAY_SUBLINK)
4753 rhaas@postgresql.org 12010 : 14 : appendStringInfoString(buf, "ARRAY(");
12011 : : else
8612 tgl@sss.pgh.pa.us 12012 : 292 : appendStringInfoChar(buf, '(');
12013 : :
12014 : : /*
12015 : : * Note that we print the name of only the first operator, when there are
12016 : : * multiple combining operators. This is an approximation that could go
12017 : : * wrong in various scenarios (operators in different schemas, renamed
12018 : : * operators, etc) but there is not a whole lot we can do about it, since
12019 : : * the syntax allows only one operator to be shown.
12020 : : */
7617 12021 [ + + ]: 306 : if (sublink->testexpr)
12022 : : {
12023 [ + + ]: 24 : if (IsA(sublink->testexpr, OpExpr))
12024 : : {
12025 : : /* single combining operator */
7337 bruce@momjian.us 12026 : 16 : OpExpr *opexpr = (OpExpr *) sublink->testexpr;
12027 : :
12028 : : /* Use get_rule_expr_paren to add parens if needed */
17 tgl@sss.pgh.pa.us 12029 : 16 : get_rule_expr_paren(linitial(opexpr->args), context, true,
12030 : : (Node *) sublink);
7617 12031 : 16 : opname = generate_operator_name(opexpr->opno,
12032 : 16 : exprType(linitial(opexpr->args)),
12033 : 16 : exprType(lsecond(opexpr->args)));
12034 : : }
12035 [ + + ]: 8 : else if (IsA(sublink->testexpr, BoolExpr))
12036 : : {
12037 : : /* multiple combining operators, = or <> cases */
12038 : : char *sep;
12039 : : ListCell *l;
12040 : :
9755 12041 : 4 : appendStringInfoChar(buf, '(');
7617 12042 : 4 : sep = "";
12043 [ + - + + : 12 : foreach(l, ((BoolExpr *) sublink->testexpr)->args)
+ + ]
12044 : : {
3496 12045 : 8 : OpExpr *opexpr = lfirst_node(OpExpr, l);
12046 : :
7617 12047 : 8 : appendStringInfoString(buf, sep);
12048 : 8 : get_rule_expr(linitial(opexpr->args), context, true);
12049 [ + + ]: 8 : if (!opname)
12050 : 4 : opname = generate_operator_name(opexpr->opno,
3424 12051 : 4 : exprType(linitial(opexpr->args)),
12052 : 4 : exprType(lsecond(opexpr->args)));
7617 12053 : 8 : sep = ", ";
12054 : : }
8064 12055 : 4 : appendStringInfoChar(buf, ')');
12056 : : }
7617 12057 [ + - ]: 4 : else if (IsA(sublink->testexpr, RowCompareExpr))
12058 : : {
12059 : : /* multiple combining operators, < <= > >= cases */
12060 : 4 : RowCompareExpr *rcexpr = (RowCompareExpr *) sublink->testexpr;
12061 : :
12062 : 4 : appendStringInfoChar(buf, '(');
12063 : 4 : get_rule_expr((Node *) rcexpr->largs, context, true);
12064 : 4 : opname = generate_operator_name(linitial_oid(rcexpr->opnos),
12065 : 4 : exprType(linitial(rcexpr->largs)),
3424 12066 : 4 : exprType(linitial(rcexpr->rargs)));
7617 12067 : 4 : appendStringInfoChar(buf, ')');
12068 : : }
12069 : : else
7617 tgl@sss.pgh.pa.us 12070 [ # # ]:UBC 0 : elog(ERROR, "unrecognized testexpr type: %d",
12071 : : (int) nodeTag(sublink->testexpr));
12072 : : }
12073 : :
9852 tgl@sss.pgh.pa.us 12074 :CBC 306 : need_paren = true;
12075 : :
10026 bruce@momjian.us 12076 [ + + + - : 306 : switch (sublink->subLinkType)
+ - ]
12077 : : {
10261 12078 : 113 : case EXISTS_SUBLINK:
4753 rhaas@postgresql.org 12079 : 113 : appendStringInfoString(buf, "EXISTS ");
10261 bruce@momjian.us 12080 : 113 : break;
12081 : :
12082 : 16 : case ANY_SUBLINK:
3424 tgl@sss.pgh.pa.us 12083 [ + + ]: 16 : if (strcmp(opname, "=") == 0) /* Represent = ANY as IN */
4753 rhaas@postgresql.org 12084 : 12 : appendStringInfoString(buf, " IN ");
12085 : : else
7617 tgl@sss.pgh.pa.us 12086 : 4 : appendStringInfo(buf, " %s ANY ", opname);
10261 bruce@momjian.us 12087 : 16 : break;
12088 : :
12089 : 8 : case ALL_SUBLINK:
7617 tgl@sss.pgh.pa.us 12090 : 8 : appendStringInfo(buf, " %s ALL ", opname);
10261 bruce@momjian.us 12091 : 8 : break;
12092 : :
7617 tgl@sss.pgh.pa.us 12093 :UBC 0 : case ROWCOMPARE_SUBLINK:
12094 : 0 : appendStringInfo(buf, " %s ", opname);
10261 bruce@momjian.us 12095 : 0 : break;
12096 : :
9852 tgl@sss.pgh.pa.us 12097 :CBC 169 : case EXPR_SUBLINK:
12098 : : case MULTIEXPR_SUBLINK:
12099 : : case ARRAY_SUBLINK:
12100 : 169 : need_paren = false;
12101 : 169 : break;
12102 : :
6606 tgl@sss.pgh.pa.us 12103 :UBC 0 : case CTE_SUBLINK: /* shouldn't occur in a SubLink */
12104 : : default:
8502 12105 [ # # ]: 0 : elog(ERROR, "unrecognized sublink type: %d",
12106 : : (int) sublink->subLinkType);
12107 : : break;
12108 : : }
12109 : :
9852 tgl@sss.pgh.pa.us 12110 [ + + ]:CBC 306 : if (need_paren)
9755 12111 : 137 : appendStringInfoChar(buf, '(');
12112 : :
1629 12113 : 306 : get_query_def(query, buf, context->namespaces, NULL, false,
12114 : : context->prettyFlags, context->wrapColumn,
12115 : : context->indentLevel);
12116 : :
9852 12117 [ + + ]: 306 : if (need_paren)
4753 rhaas@postgresql.org 12118 : 137 : appendStringInfoString(buf, "))");
12119 : : else
9755 tgl@sss.pgh.pa.us 12120 : 169 : appendStringInfoChar(buf, ')');
10300 bruce@momjian.us 12121 : 306 : }
12122 : :
12123 : :
12124 : : /* ----------
12125 : : * get_xmltable - Parse back a XMLTABLE function
12126 : : * ----------
12127 : : */
12128 : : static void
945 amitlan@postgresql.o 12129 : 38 : get_xmltable(TableFunc *tf, deparse_context *context, bool showimplicit)
12130 : : {
3529 alvherre@alvh.no-ip. 12131 : 38 : StringInfo buf = context->buf;
12132 : :
12133 : 38 : appendStringInfoString(buf, "XMLTABLE(");
12134 : :
12135 [ + + ]: 38 : if (tf->ns_uris != NIL)
12136 : : {
12137 : : ListCell *lc1,
12138 : : *lc2;
12139 : 9 : bool first = true;
12140 : :
12141 : 9 : appendStringInfoString(buf, "XMLNAMESPACES (");
12142 [ + - + + : 18 : forboth(lc1, tf->ns_uris, lc2, tf->ns_names)
+ - + + +
+ + - +
+ ]
12143 : : {
12144 : 9 : Node *expr = (Node *) lfirst(lc1);
1883 peter@eisentraut.org 12145 : 9 : String *ns_node = lfirst_node(String, lc2);
12146 : :
3529 alvherre@alvh.no-ip. 12147 [ - + ]: 9 : if (!first)
3529 alvherre@alvh.no-ip. 12148 :UBC 0 : appendStringInfoString(buf, ", ");
12149 : : else
3529 alvherre@alvh.no-ip. 12150 :CBC 9 : first = false;
12151 : :
2971 tgl@sss.pgh.pa.us 12152 [ + - ]: 9 : if (ns_node != NULL)
12153 : : {
3529 alvherre@alvh.no-ip. 12154 : 9 : get_rule_expr(expr, context, showimplicit);
662 dean.a.rasheed@gmail 12155 : 9 : appendStringInfo(buf, " AS %s",
12156 : 9 : quote_identifier(strVal(ns_node)));
12157 : : }
12158 : : else
12159 : : {
3529 alvherre@alvh.no-ip. 12160 :UBC 0 : appendStringInfoString(buf, "DEFAULT ");
12161 : 0 : get_rule_expr(expr, context, showimplicit);
12162 : : }
12163 : : }
3529 alvherre@alvh.no-ip. 12164 :CBC 9 : appendStringInfoString(buf, "), ");
12165 : : }
12166 : :
12167 : 38 : appendStringInfoChar(buf, '(');
12168 : 38 : get_rule_expr((Node *) tf->rowexpr, context, showimplicit);
12169 : 38 : appendStringInfoString(buf, ") PASSING (");
12170 : 38 : get_rule_expr((Node *) tf->docexpr, context, showimplicit);
12171 : 38 : appendStringInfoChar(buf, ')');
12172 : :
12173 [ + - ]: 38 : if (tf->colexprs != NIL)
12174 : : {
12175 : : ListCell *l1;
12176 : : ListCell *l2;
12177 : : ListCell *l3;
12178 : : ListCell *l4;
12179 : : ListCell *l5;
12180 : 38 : int colnum = 0;
12181 : :
12182 : 38 : appendStringInfoString(buf, " COLUMNS ");
2807 tgl@sss.pgh.pa.us 12183 [ + - + + : 231 : forfive(l1, tf->colnames, l2, tf->coltypes, l3, tf->coltypmods,
+ - + + +
- + + + -
+ + + - +
+ + + + -
+ - + - +
- + + ]
12184 : : l4, tf->colexprs, l5, tf->coldefexprs)
12185 : : {
3529 alvherre@alvh.no-ip. 12186 : 193 : char *colname = strVal(lfirst(l1));
2807 tgl@sss.pgh.pa.us 12187 : 193 : Oid typid = lfirst_oid(l2);
12188 : 193 : int32 typmod = lfirst_int(l3);
12189 : 193 : Node *colexpr = (Node *) lfirst(l4);
12190 : 193 : Node *coldefexpr = (Node *) lfirst(l5);
12191 : 193 : bool ordinality = (tf->ordinalitycol == colnum);
3529 alvherre@alvh.no-ip. 12192 : 193 : bool notnull = bms_is_member(colnum, tf->notnulls);
12193 : :
12194 [ + + ]: 193 : if (colnum > 0)
12195 : 155 : appendStringInfoString(buf, ", ");
12196 : 193 : colnum++;
12197 : :
12198 [ + + ]: 365 : appendStringInfo(buf, "%s %s", quote_identifier(colname),
12199 : : ordinality ? "FOR ORDINALITY" :
12200 : 172 : format_type_with_typemod(typid, typmod));
12201 [ + + ]: 193 : if (ordinality)
12202 : 21 : continue;
12203 : :
12204 [ + + ]: 172 : if (coldefexpr != NULL)
12205 : : {
12206 : 21 : appendStringInfoString(buf, " DEFAULT (");
12207 : 21 : get_rule_expr((Node *) coldefexpr, context, showimplicit);
12208 : 21 : appendStringInfoChar(buf, ')');
12209 : : }
12210 [ + + ]: 172 : if (colexpr != NULL)
12211 : : {
12212 : 156 : appendStringInfoString(buf, " PATH (");
12213 : 156 : get_rule_expr((Node *) colexpr, context, showimplicit);
12214 : 156 : appendStringInfoChar(buf, ')');
12215 : : }
12216 [ + + ]: 172 : if (notnull)
12217 : 21 : appendStringInfoString(buf, " NOT NULL");
12218 : : }
12219 : : }
12220 : :
12221 : 38 : appendStringInfoChar(buf, ')');
12222 : 38 : }
12223 : :
12224 : : /*
12225 : : * get_json_table_nested_columns - Parse back nested JSON_TABLE columns
12226 : : */
12227 : : static void
941 amitlan@postgresql.o 12228 : 104 : get_json_table_nested_columns(TableFunc *tf, JsonTablePlan *plan,
12229 : : deparse_context *context, bool showimplicit,
12230 : : bool needcomma)
12231 : : {
12232 [ + + ]: 104 : if (IsA(plan, JsonTablePathScan))
12233 : : {
12234 : 76 : JsonTablePathScan *scan = castNode(JsonTablePathScan, plan);
12235 : :
12236 [ + + ]: 76 : if (needcomma)
12237 : 48 : appendStringInfoChar(context->buf, ',');
12238 : :
12239 : 76 : appendStringInfoChar(context->buf, ' ');
12240 : 76 : appendContextKeyword(context, "NESTED PATH ", 0, 0, 0);
12241 : 76 : get_const_expr(scan->path->value, context, -1);
12242 : 76 : appendStringInfo(context->buf, " AS %s", quote_identifier(scan->path->name));
12243 : 76 : get_json_table_columns(tf, scan, context, showimplicit);
12244 : : }
12245 [ + - ]: 28 : else if (IsA(plan, JsonTableSiblingJoin))
12246 : : {
12247 : 28 : JsonTableSiblingJoin *join = (JsonTableSiblingJoin *) plan;
12248 : :
12249 : 28 : get_json_table_nested_columns(tf, join->lplan, context, showimplicit,
12250 : : needcomma);
12251 : 28 : get_json_table_nested_columns(tf, join->rplan, context, showimplicit,
12252 : : true);
12253 : : }
12254 : 104 : }
12255 : :
12256 : : /*
12257 : : * json_table_plan_is_default - does this plan match the implicit default?
12258 : : *
12259 : : * When no PLAN clause is given, JSON_TABLE builds a default plan that joins
12260 : : * every nested path to its parent with OUTER and every set of sibling paths
12261 : : * with UNION (see transformJsonTableColumns()). Such a plan is fully implied
12262 : : * by the NESTED COLUMNS structure, so we need not (and, to match the input,
12263 : : * should not) print a PLAN clause for it; we only deparse a PLAN clause when
12264 : : * the plan deviates from the default, i.e. uses an INNER or CROSS join
12265 : : * somewhere. This follows the usual ruleutils convention of omitting a clause
12266 : : * that merely restates the default (cf. get_json_expr_options() for ON
12267 : : * EMPTY/ON ERROR, or the NULLS FIRST/LAST handling in get_rule_orderby()).
12268 : : */
12269 : : static bool
119 akorotkov@postgresql 12270 :GNC 116 : json_table_plan_is_default(JsonTablePlan *plan)
12271 : : {
12272 [ + + ]: 116 : if (IsA(plan, JsonTablePathScan))
12273 : : {
12274 : 88 : JsonTablePathScan *scan = castNode(JsonTablePathScan, plan);
12275 : :
12276 [ + + ]: 88 : if (scan->child)
12277 : : {
12278 [ + + ]: 48 : if (!scan->outerJoin)
12279 : 4 : return false; /* INNER is not the default */
12280 : 44 : return json_table_plan_is_default(scan->child);
12281 : : }
12282 : :
12283 : 40 : return true;
12284 : : }
12285 : : else
12286 : : {
12287 : 28 : JsonTableSiblingJoin *join = castNode(JsonTableSiblingJoin, plan);
12288 : :
12289 [ - + ]: 28 : if (join->cross)
119 akorotkov@postgresql 12290 :UNC 0 : return false; /* CROSS is not the default */
119 akorotkov@postgresql 12291 [ + - + - ]:GNC 56 : return json_table_plan_is_default(join->lplan) &&
12292 : 28 : json_table_plan_is_default(join->rplan);
12293 : : }
12294 : : }
12295 : :
12296 : : /*
12297 : : * get_json_table_plan - Parse back a JSON_TABLE plan
12298 : : */
12299 : : static void
12300 : 12 : get_json_table_plan(TableFunc *tf, JsonTablePlan *plan, deparse_context *context,
12301 : : bool parenthesize)
12302 : : {
12303 [ + + ]: 12 : if (parenthesize)
12304 : 8 : appendStringInfoChar(context->buf, '(');
12305 : :
12306 [ + - ]: 12 : if (IsA(plan, JsonTablePathScan))
12307 : : {
12308 : 12 : JsonTablePathScan *s = castNode(JsonTablePathScan, plan);
12309 : :
12310 : 12 : appendStringInfoString(context->buf, quote_identifier(s->path->name));
12311 : :
12312 [ + + ]: 12 : if (s->child)
12313 : : {
12314 : 8 : appendStringInfoString(context->buf,
12315 [ + + ]: 8 : s->outerJoin ? " OUTER " : " INNER ");
12316 : 8 : get_json_table_plan(tf, s->child, context,
113 12317 [ + - ]: 16 : IsA(s->child, JsonTableSiblingJoin) ||
12318 [ + + ]: 16 : castNode(JsonTablePathScan, s->child)->child);
12319 : : }
12320 : : }
119 akorotkov@postgresql 12321 [ # # ]:UNC 0 : else if (IsA(plan, JsonTableSiblingJoin))
12322 : : {
12323 : 0 : JsonTableSiblingJoin *j = (JsonTableSiblingJoin *) plan;
12324 : :
12325 : 0 : get_json_table_plan(tf, j->lplan, context,
12326 [ # # ]: 0 : IsA(j->lplan, JsonTableSiblingJoin) ||
12327 [ # # ]: 0 : castNode(JsonTablePathScan, j->lplan)->child);
12328 : :
12329 [ # # ]: 0 : appendStringInfoString(context->buf, j->cross ? " CROSS " : " UNION ");
12330 : :
12331 : 0 : get_json_table_plan(tf, j->rplan, context,
12332 [ # # ]: 0 : IsA(j->rplan, JsonTableSiblingJoin) ||
12333 [ # # ]: 0 : castNode(JsonTablePathScan, j->rplan)->child);
12334 : : }
12335 : :
119 akorotkov@postgresql 12336 [ + + ]:GNC 12 : if (parenthesize)
12337 : 8 : appendStringInfoChar(context->buf, ')');
12338 : 12 : }
12339 : :
12340 : : /*
12341 : : * get_json_table_columns - Parse back JSON_TABLE columns
12342 : : */
12343 : : static void
941 amitlan@postgresql.o 12344 :CBC 164 : get_json_table_columns(TableFunc *tf, JsonTablePathScan *scan,
12345 : : deparse_context *context,
12346 : : bool showimplicit)
12347 : : {
945 12348 : 164 : StringInfo buf = context->buf;
12349 : : ListCell *lc_colname;
12350 : : ListCell *lc_coltype;
12351 : : ListCell *lc_coltypmod;
12352 : : ListCell *lc_colvalexpr;
12353 : 164 : int colnum = 0;
12354 : :
12355 : 164 : appendStringInfoChar(buf, ' ');
12356 : 164 : appendContextKeyword(context, "COLUMNS (", 0, 0, 0);
12357 : :
12358 [ + + ]: 164 : if (PRETTY_INDENT(context))
12359 : 112 : context->indentLevel += PRETTYINDENT_VAR;
12360 : :
12361 [ + - + + : 1316 : forfour(lc_colname, tf->colnames,
+ - + + +
- + + + -
+ + + + +
- + - + -
+ + ]
12362 : : lc_coltype, tf->coltypes,
12363 : : lc_coltypmod, tf->coltypmods,
12364 : : lc_colvalexpr, tf->colvalexprs)
12365 : : {
12366 : 1200 : char *colname = strVal(lfirst(lc_colname));
12367 : : JsonExpr *colexpr;
12368 : : Oid typid;
12369 : : int32 typmod;
12370 : : bool ordinality;
12371 : : JsonBehaviorType default_behavior;
12372 : :
12373 : 1200 : typid = lfirst_oid(lc_coltype);
12374 : 1200 : typmod = lfirst_int(lc_coltypmod);
12375 : 1200 : colexpr = castNode(JsonExpr, lfirst(lc_colvalexpr));
12376 : :
12377 : : /* Skip columns that don't belong to this scan. */
941 12378 [ + + + + ]: 1200 : if (scan->colMin < 0 || colnum < scan->colMin)
12379 : : {
12380 : 740 : colnum++;
12381 : 740 : continue;
12382 : : }
12383 [ + + ]: 460 : if (colnum > scan->colMax)
12384 : 48 : break;
12385 : :
12386 [ + + ]: 412 : if (colnum > scan->colMin)
945 12387 : 276 : appendStringInfoString(buf, ", ");
12388 : :
12389 : 412 : colnum++;
12390 : :
12391 : 412 : ordinality = !colexpr;
12392 : :
12393 : 412 : appendContextKeyword(context, "", 0, 0, 0);
12394 : :
12395 [ + + ]: 808 : appendStringInfo(buf, "%s %s", quote_identifier(colname),
12396 : : ordinality ? "FOR ORDINALITY" :
12397 : 396 : format_type_with_typemod(typid, typmod));
12398 [ + + ]: 412 : if (ordinality)
12399 : 16 : continue;
12400 : :
12401 : : /*
12402 : : * Set default_behavior to guide get_json_expr_options() on whether to
12403 : : * emit the ON ERROR / EMPTY clauses.
12404 : : */
12405 [ + + ]: 396 : if (colexpr->op == JSON_EXISTS_OP)
12406 : : {
12407 : 40 : appendStringInfoString(buf, " EXISTS");
12408 : 40 : default_behavior = JSON_BEHAVIOR_FALSE;
12409 : : }
12410 : : else
12411 : : {
12412 [ + + ]: 356 : if (colexpr->op == JSON_QUERY_OP)
12413 : : {
12414 : : char typcategory;
12415 : : bool typispreferred;
12416 : :
12417 : 168 : get_type_category_preferred(typid, &typcategory, &typispreferred);
12418 : :
12419 [ + + ]: 168 : if (typcategory == TYPCATEGORY_STRING)
12420 : 36 : appendStringInfoString(buf,
12421 [ - + ]: 36 : colexpr->format->format_type == JS_FORMAT_JSONB ?
12422 : : " FORMAT JSONB" : " FORMAT JSON");
12423 : : }
12424 : :
12425 : 356 : default_behavior = JSON_BEHAVIOR_NULL;
12426 : : }
12427 : :
12428 : 396 : appendStringInfoString(buf, " PATH ");
12429 : :
12430 : 396 : get_json_path_spec(colexpr->path_spec, context, showimplicit);
12431 : :
12432 : 396 : get_json_expr_options(colexpr, context, default_behavior);
12433 : : }
12434 : :
941 12435 [ + + ]: 164 : if (scan->child)
12436 : 48 : get_json_table_nested_columns(tf, scan->child, context, showimplicit,
12437 : 48 : scan->colMin >= 0);
12438 : :
945 12439 [ + + ]: 164 : if (PRETTY_INDENT(context))
12440 : 112 : context->indentLevel -= PRETTYINDENT_VAR;
12441 : :
12442 : 164 : appendContextKeyword(context, ")", 0, 0, 0);
12443 : 164 : }
12444 : :
12445 : : /* ----------
12446 : : * get_json_table - Parse back a JSON_TABLE function
12447 : : * ----------
12448 : : */
12449 : : static void
12450 : 88 : get_json_table(TableFunc *tf, deparse_context *context, bool showimplicit)
12451 : : {
12452 : 88 : StringInfo buf = context->buf;
12453 : 88 : JsonExpr *jexpr = castNode(JsonExpr, tf->docexpr);
12454 : 88 : JsonTablePathScan *root = castNode(JsonTablePathScan, tf->plan);
12455 : :
12456 : 88 : appendStringInfoString(buf, "JSON_TABLE(");
12457 : :
12458 [ + + ]: 88 : if (PRETTY_INDENT(context))
12459 : 56 : context->indentLevel += PRETTYINDENT_VAR;
12460 : :
12461 : 88 : appendContextKeyword(context, "", 0, 0, 0);
12462 : :
12463 : 88 : get_rule_expr(jexpr->formatted_expr, context, showimplicit);
12464 : :
12465 : 88 : appendStringInfoString(buf, ", ");
12466 : :
12467 : 88 : get_const_expr(root->path->value, context, -1);
12468 : :
12469 : 88 : appendStringInfo(buf, " AS %s", quote_identifier(root->path->name));
12470 : :
12471 [ + + ]: 88 : if (jexpr->passing_values)
12472 : : {
12473 : : ListCell *lc1,
12474 : : *lc2;
12475 : 64 : bool needcomma = false;
12476 : :
12477 : 64 : appendStringInfoChar(buf, ' ');
12478 : 64 : appendContextKeyword(context, "PASSING ", 0, 0, 0);
12479 : :
12480 [ + + ]: 64 : if (PRETTY_INDENT(context))
12481 : 32 : context->indentLevel += PRETTYINDENT_VAR;
12482 : :
12483 [ + - + + : 188 : forboth(lc1, jexpr->passing_names,
+ - + + +
+ + - +
+ ]
12484 : : lc2, jexpr->passing_values)
12485 : : {
12486 [ + + ]: 124 : if (needcomma)
12487 : 60 : appendStringInfoString(buf, ", ");
12488 : 124 : needcomma = true;
12489 : :
12490 : 124 : appendContextKeyword(context, "", 0, 0, 0);
12491 : :
12492 : 124 : get_rule_expr((Node *) lfirst(lc2), context, false);
12493 : 124 : appendStringInfo(buf, " AS %s",
12494 : 124 : quote_identifier((lfirst_node(String, lc1))->sval)
12495 : : );
12496 : : }
12497 : :
12498 [ + + ]: 64 : if (PRETTY_INDENT(context))
12499 : 32 : context->indentLevel -= PRETTYINDENT_VAR;
12500 : : }
12501 : :
941 12502 : 88 : get_json_table_columns(tf, castNode(JsonTablePathScan, tf->plan), context,
12503 : : showimplicit);
12504 : :
12505 : : /*
12506 : : * Deparse a PLAN clause only for a non-default plan; the default plan is
12507 : : * implied by the NESTED COLUMNS structure (see
12508 : : * json_table_plan_is_default).
12509 : : */
119 akorotkov@postgresql 12510 [ + + + + ]:GNC 88 : if (root->child && !json_table_plan_is_default((JsonTablePlan *) root))
12511 : : {
12512 : 4 : appendStringInfoChar(buf, ' ');
12513 : 4 : appendContextKeyword(context, "PLAN ", 0, 0, 0);
12514 : 4 : get_json_table_plan(tf, (JsonTablePlan *) root, context, true);
12515 : : }
12516 : :
790 amitlan@postgresql.o 12517 [ + + ]:CBC 88 : if (jexpr->on_error->btype != JSON_BEHAVIOR_EMPTY_ARRAY)
945 12518 : 8 : get_json_behavior(jexpr->on_error, context, "ERROR");
12519 : :
12520 [ + + ]: 88 : if (PRETTY_INDENT(context))
12521 : 56 : context->indentLevel -= PRETTYINDENT_VAR;
12522 : :
12523 : 88 : appendContextKeyword(context, ")", 0, 0, 0);
12524 : 88 : }
12525 : :
12526 : : /* ----------
12527 : : * get_tablefunc - Parse back a table function
12528 : : * ----------
12529 : : */
12530 : : static void
12531 : 126 : get_tablefunc(TableFunc *tf, deparse_context *context, bool showimplicit)
12532 : : {
12533 : : /* XMLTABLE and JSON_TABLE are the only existing implementations. */
12534 : :
12535 [ + + ]: 126 : if (tf->functype == TFT_XMLTABLE)
12536 : 38 : get_xmltable(tf, context, showimplicit);
12537 [ + - ]: 88 : else if (tf->functype == TFT_JSON_TABLE)
12538 : 88 : get_json_table(tf, context, showimplicit);
12539 : 126 : }
12540 : :
12541 : : /* ----------
12542 : : * get_from_clause - Parse back a FROM clause
12543 : : *
12544 : : * "prefix" is the keyword that denotes the start of the list of FROM
12545 : : * elements. It is FROM when used to parse back SELECT and UPDATE, but
12546 : : * is USING when parsing back DELETE.
12547 : : * ----------
12548 : : */
12549 : : static void
7882 neilc@samurai.com 12550 : 3037 : get_from_clause(Query *query, const char *prefix, deparse_context *context)
12551 : : {
9550 tgl@sss.pgh.pa.us 12552 : 3037 : StringInfo buf = context->buf;
8499 12553 : 3037 : bool first = true;
12554 : : ListCell *l;
12555 : :
12556 : : /*
12557 : : * We use the query's jointree as a guide to what to print. However, we
12558 : : * must ignore auto-added RTEs that are marked not inFromCl. (These can
12559 : : * only appear at the top level of the jointree, so it's sufficient to
12560 : : * check here.) This check also ensures we ignore the rule pseudo-RTEs
12561 : : * for NEW and OLD.
12562 : : */
9533 12563 [ + + + + : 6012 : foreach(l, query->jointree->fromlist)
+ + ]
12564 : : {
9359 bruce@momjian.us 12565 : 2975 : Node *jtnode = (Node *) lfirst(l);
12566 : :
9550 tgl@sss.pgh.pa.us 12567 [ + + ]: 2975 : if (IsA(jtnode, RangeTblRef))
12568 : : {
12569 : 2370 : int varno = ((RangeTblRef *) jtnode)->rtindex;
12570 : 2370 : RangeTblEntry *rte = rt_fetch(varno, query->rtable);
12571 : :
12572 [ + + ]: 2370 : if (!rte->inFromCl)
12573 : 214 : continue;
12574 : : }
12575 : :
8499 12576 [ + + ]: 2761 : if (first)
12577 : : {
7882 neilc@samurai.com 12578 : 2533 : appendContextKeyword(context, prefix,
12579 : : -PRETTYINDENT_STD, PRETTYINDENT_STD, 2);
8499 tgl@sss.pgh.pa.us 12580 : 2533 : first = false;
12581 : :
5373 andrew@dunslane.net 12582 : 2533 : get_from_clause_item(jtnode, query, context);
12583 : : }
12584 : : else
12585 : : {
12586 : : StringInfoData itembuf;
12587 : :
8494 bruce@momjian.us 12588 : 228 : appendStringInfoString(buf, ", ");
12589 : :
12590 : : /*
12591 : : * Put the new FROM item's text into itembuf so we can decide
12592 : : * after we've got it whether or not it needs to go on a new line.
12593 : : */
5064 tgl@sss.pgh.pa.us 12594 : 228 : initStringInfo(&itembuf);
12595 : 228 : context->buf = &itembuf;
12596 : :
5373 andrew@dunslane.net 12597 : 228 : get_from_clause_item(jtnode, query, context);
12598 : :
12599 : : /* Restore context's output buffer */
12600 : 228 : context->buf = buf;
12601 : :
12602 : : /* Consider line-wrapping if enabled */
5064 tgl@sss.pgh.pa.us 12603 [ + - + - ]: 228 : if (PRETTY_INDENT(context) && context->wrapColumn >= 0)
12604 : : {
12605 : : /* Does the new item start with a new line? */
4742 12606 [ + - - + ]: 228 : if (itembuf.len > 0 && itembuf.data[0] == '\n')
12607 : : {
12608 : : /* If so, we shouldn't add anything */
12609 : : /* instead, remove any trailing spaces currently in buf */
4742 tgl@sss.pgh.pa.us 12610 :UBC 0 : removeStringInfoSpaces(buf);
12611 : : }
12612 : : else
12613 : : {
12614 : : char *trailing_nl;
12615 : :
12616 : : /* Locate the start of the current line in the buffer */
4742 tgl@sss.pgh.pa.us 12617 :CBC 228 : trailing_nl = strrchr(buf->data, '\n');
12618 [ - + ]: 228 : if (trailing_nl == NULL)
4742 tgl@sss.pgh.pa.us 12619 :UBC 0 : trailing_nl = buf->data;
12620 : : else
4742 tgl@sss.pgh.pa.us 12621 :CBC 228 : trailing_nl++;
12622 : :
12623 : : /*
12624 : : * Add a newline, plus some indentation, if the new item
12625 : : * would cause an overflow.
12626 : : */
12627 [ + - ]: 228 : if (strlen(trailing_nl) + itembuf.len > context->wrapColumn)
12628 : 228 : appendContextKeyword(context, "", -PRETTYINDENT_STD,
12629 : : PRETTYINDENT_STD,
12630 : : PRETTYINDENT_VAR);
12631 : : }
12632 : : }
12633 : :
12634 : : /* Add the new item */
2662 drowley@postgresql.o 12635 : 228 : appendBinaryStringInfo(buf, itembuf.data, itembuf.len);
12636 : :
12637 : : /* clean up */
5064 tgl@sss.pgh.pa.us 12638 : 228 : pfree(itembuf.data);
12639 : : }
12640 : : }
9550 12641 : 3037 : }
12642 : :
12643 : : static void
12644 : 4641 : get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
12645 : : {
12646 : 4641 : StringInfo buf = context->buf;
5057 12647 : 4641 : deparse_namespace *dpns = (deparse_namespace *) linitial(context->namespaces);
12648 : :
9550 12649 [ + + ]: 4641 : if (IsA(jtnode, RangeTblRef))
12650 : : {
12651 : 3701 : int varno = ((RangeTblRef *) jtnode)->rtindex;
12652 : 3701 : RangeTblEntry *rte = rt_fetch(varno, query->rtable);
5057 12653 : 3701 : deparse_columns *colinfo = deparse_columns_fetch(varno, dpns);
4732 12654 : 3701 : RangeTblFunction *rtfunc1 = NULL;
12655 : :
5203 12656 [ + + ]: 3701 : if (rte->lateral)
12657 : 73 : appendStringInfoString(buf, "LATERAL ");
12658 : :
12659 : : /* Print the FROM item proper */
8994 12660 [ + + + + : 3701 : switch (rte->rtekind)
+ + - ]
12661 : : {
12662 : 2736 : case RTE_RELATION:
12663 : : /* Normal relation RTE */
12664 : 5472 : appendStringInfo(buf, "%s%s",
12665 [ + + ]: 2736 : only_marker(rte),
12666 : : generate_relation_name(rte->relid,
12667 : : context->namespaces));
12668 : 2736 : break;
12669 : 204 : case RTE_SUBQUERY:
12670 : : /* Subquery RTE */
12671 : 204 : appendStringInfoChar(buf, '(');
8494 bruce@momjian.us 12672 : 204 : get_query_def(rte->subquery, buf, context->namespaces, NULL,
12673 : : true,
12674 : : context->prettyFlags, context->wrapColumn,
12675 : : context->indentLevel);
8994 tgl@sss.pgh.pa.us 12676 : 204 : appendStringInfoChar(buf, ')');
12677 : 204 : break;
8943 12678 : 564 : case RTE_FUNCTION:
12679 : : /* Function RTE */
4732 12680 : 564 : rtfunc1 = (RangeTblFunction *) linitial(rte->functions);
12681 : :
12682 : : /*
12683 : : * Omit ROWS FROM() syntax for just one function, unless it
12684 : : * has both a coldeflist and WITH ORDINALITY. If it has both,
12685 : : * we must use ROWS FROM() syntax to avoid ambiguity about
12686 : : * whether the coldeflist includes the ordinality column.
12687 : : */
12688 [ + + ]: 564 : if (list_length(rte->functions) == 1 &&
12689 [ - + - - ]: 544 : (rtfunc1->funccolnames == NIL || !rte->funcordinality))
12690 : : {
3402 12691 : 544 : get_rule_expr_funccall(rtfunc1->funcexpr, context, true);
12692 : : /* we'll print the coldeflist below, if it has one */
12693 : : }
12694 : : else
12695 : : {
12696 : : bool all_unnest;
12697 : : ListCell *lc;
12698 : :
12699 : : /*
12700 : : * If all the function calls in the list are to unnest,
12701 : : * and none need a coldeflist, then collapse the list back
12702 : : * down to UNNEST(args). (If we had more than one
12703 : : * built-in unnest function, this would get more
12704 : : * difficult.)
12705 : : *
12706 : : * XXX This is pretty ugly, since it makes not-terribly-
12707 : : * future-proof assumptions about what the parser would do
12708 : : * with the output; but the alternative is to emit our
12709 : : * nonstandard ROWS FROM() notation for what might have
12710 : : * been a perfectly spec-compliant multi-argument
12711 : : * UNNEST().
12712 : : */
4732 12713 : 20 : all_unnest = true;
12714 [ + - + + : 52 : foreach(lc, rte->functions)
+ + ]
12715 : : {
12716 : 44 : RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
12717 : :
12718 [ + - ]: 44 : if (!IsA(rtfunc->funcexpr, FuncExpr) ||
2194 12719 [ + + ]: 44 : ((FuncExpr *) rtfunc->funcexpr)->funcid != F_UNNEST_ANYARRAY ||
4732 12720 [ - + ]: 32 : rtfunc->funccolnames != NIL)
12721 : : {
12722 : 12 : all_unnest = false;
12723 : 12 : break;
12724 : : }
12725 : : }
12726 : :
12727 [ + + ]: 20 : if (all_unnest)
12728 : : {
12729 : 8 : List *allargs = NIL;
12730 : :
12731 [ + - + + : 32 : foreach(lc, rte->functions)
+ + ]
12732 : : {
12733 : 24 : RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
12734 : 24 : List *args = ((FuncExpr *) rtfunc->funcexpr)->args;
12735 : :
2642 12736 : 24 : allargs = list_concat(allargs, args);
12737 : : }
12738 : :
4732 12739 : 8 : appendStringInfoString(buf, "UNNEST(");
12740 : 8 : get_rule_expr((Node *) allargs, context, true);
12741 : 8 : appendStringInfoChar(buf, ')');
12742 : : }
12743 : : else
12744 : : {
12745 : 12 : int funcno = 0;
12746 : :
4713 noah@leadboat.com 12747 : 12 : appendStringInfoString(buf, "ROWS FROM(");
4732 tgl@sss.pgh.pa.us 12748 [ + - + + : 44 : foreach(lc, rte->functions)
+ + ]
12749 : : {
12750 : 32 : RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
12751 : :
12752 [ + + ]: 32 : if (funcno > 0)
12753 : 20 : appendStringInfoString(buf, ", ");
3402 12754 : 32 : get_rule_expr_funccall(rtfunc->funcexpr, context, true);
4732 12755 [ + + ]: 32 : if (rtfunc->funccolnames != NIL)
12756 : : {
12757 : : /* Reconstruct the column definition list */
12758 : 4 : appendStringInfoString(buf, " AS ");
12759 : 4 : get_from_clause_coldeflist(rtfunc,
12760 : : NULL,
12761 : : context);
12762 : : }
12763 : 32 : funcno++;
12764 : : }
12765 : 12 : appendStringInfoChar(buf, ')');
12766 : : }
12767 : : /* prevent printing duplicate coldeflist below */
12768 : 20 : rtfunc1 = NULL;
12769 : : }
4847 stark@mit.edu 12770 [ + + ]: 564 : if (rte->funcordinality)
12771 : 12 : appendStringInfoString(buf, " WITH ORDINALITY");
8943 tgl@sss.pgh.pa.us 12772 : 564 : break;
3529 alvherre@alvh.no-ip. 12773 : 74 : case RTE_TABLEFUNC:
12774 : 74 : get_tablefunc(rte->tablefunc, context, true);
12775 : 74 : break;
7400 mail@joeconway.com 12776 : 8 : case RTE_VALUES:
12777 : : /* Values list RTE */
4271 tgl@sss.pgh.pa.us 12778 : 8 : appendStringInfoChar(buf, '(');
7400 mail@joeconway.com 12779 : 8 : get_values_def(rte->values_lists, context);
4271 tgl@sss.pgh.pa.us 12780 : 8 : appendStringInfoChar(buf, ')');
7400 mail@joeconway.com 12781 : 8 : break;
6606 tgl@sss.pgh.pa.us 12782 : 115 : case RTE_CTE:
12783 : 115 : appendStringInfoString(buf, quote_identifier(rte->ctename));
12784 : 115 : break;
8994 tgl@sss.pgh.pa.us 12785 :UBC 0 : default:
8502 12786 [ # # ]: 0 : elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
12787 : : break;
12788 : : }
12789 : :
12790 : : /* Print the relation alias, if needed */
1357 tgl@sss.pgh.pa.us 12791 :CBC 3701 : get_rte_alias(rte, varno, false, context);
12792 : :
12793 : : /* Print the column definitions or aliases, if needed */
4732 12794 [ + + - + ]: 3701 : if (rtfunc1 && rtfunc1->funccolnames != NIL)
12795 : : {
12796 : : /* Reconstruct the columndef list, which is also the aliases */
4732 tgl@sss.pgh.pa.us 12797 :UBC 0 : get_from_clause_coldeflist(rtfunc1, colinfo, context);
12798 : : }
12799 : : else
12800 : : {
12801 : : /* Else print column aliases as needed */
5057 tgl@sss.pgh.pa.us 12802 :CBC 3701 : get_column_alias_list(colinfo, context);
12803 : : }
12804 : :
12805 : : /* Tablesample clause must go after any alias */
4121 12806 [ + + + + ]: 3701 : if (rte->rtekind == RTE_RELATION && rte->tablesample)
12807 : 18 : get_tablesample_def(rte->tablesample, context);
12808 : : }
9550 12809 [ + - ]: 940 : else if (IsA(jtnode, JoinExpr))
12810 : : {
12811 : 940 : JoinExpr *j = (JoinExpr *) jtnode;
5057 12812 : 940 : deparse_columns *colinfo = deparse_columns_fetch(j->rtindex, dpns);
12813 : : bool need_paren_on_right;
12814 : :
8499 12815 : 2172 : need_paren_on_right = PRETTY_PAREN(context) &&
7802 12816 [ + + - + ]: 940 : !IsA(j->rarg, RangeTblRef) &&
2364 tgl@sss.pgh.pa.us 12817 [ # # # # ]:UBC 0 : !(IsA(j->rarg, JoinExpr) && ((JoinExpr *) j->rarg)->alias != NULL);
12818 : :
8499 tgl@sss.pgh.pa.us 12819 [ + + + + ]:CBC 940 : if (!PRETTY_PAREN(context) || j->alias != NULL)
8494 bruce@momjian.us 12820 : 720 : appendStringInfoChar(buf, '(');
12821 : :
9550 tgl@sss.pgh.pa.us 12822 : 940 : get_from_clause_item(j->larg, query, context);
12823 : :
5057 12824 [ + + + - : 940 : switch (j->jointype)
- ]
12825 : : {
12826 : 517 : case JOIN_INNER:
12827 [ + + ]: 517 : if (j->quals)
12828 : 489 : appendContextKeyword(context, " JOIN ",
12829 : : -PRETTYINDENT_STD,
12830 : : PRETTYINDENT_STD,
12831 : : PRETTYINDENT_JOIN);
12832 : : else
12833 : 28 : appendContextKeyword(context, " CROSS JOIN ",
12834 : : -PRETTYINDENT_STD,
12835 : : PRETTYINDENT_STD,
12836 : : PRETTYINDENT_JOIN);
12837 : 517 : break;
12838 : 355 : case JOIN_LEFT:
12839 : 355 : appendContextKeyword(context, " LEFT JOIN ",
12840 : : -PRETTYINDENT_STD,
12841 : : PRETTYINDENT_STD,
12842 : : PRETTYINDENT_JOIN);
12843 : 355 : break;
12844 : 68 : case JOIN_FULL:
12845 : 68 : appendContextKeyword(context, " FULL JOIN ",
12846 : : -PRETTYINDENT_STD,
12847 : : PRETTYINDENT_STD,
12848 : : PRETTYINDENT_JOIN);
12849 : 68 : break;
5057 tgl@sss.pgh.pa.us 12850 :UBC 0 : case JOIN_RIGHT:
12851 : 0 : appendContextKeyword(context, " RIGHT JOIN ",
12852 : : -PRETTYINDENT_STD,
12853 : : PRETTYINDENT_STD,
12854 : : PRETTYINDENT_JOIN);
12855 : 0 : break;
12856 : 0 : default:
12857 [ # # ]: 0 : elog(ERROR, "unrecognized join type: %d",
12858 : : (int) j->jointype);
12859 : : }
12860 : :
8499 tgl@sss.pgh.pa.us 12861 [ - + ]:CBC 940 : if (need_paren_on_right)
8494 bruce@momjian.us 12862 :UBC 0 : appendStringInfoChar(buf, '(');
9550 tgl@sss.pgh.pa.us 12863 :CBC 940 : get_from_clause_item(j->rarg, query, context);
8499 12864 [ - + ]: 940 : if (need_paren_on_right)
8494 bruce@momjian.us 12865 :UBC 0 : appendStringInfoChar(buf, ')');
12866 : :
5057 tgl@sss.pgh.pa.us 12867 [ + + ]:CBC 940 : if (j->usingClause)
12868 : : {
12869 : : ListCell *lc;
12870 : 280 : bool first = true;
12871 : :
4753 rhaas@postgresql.org 12872 : 280 : appendStringInfoString(buf, " USING (");
12873 : : /* Use the assigned names, not what's in usingClause */
5057 tgl@sss.pgh.pa.us 12874 [ + - + + : 664 : foreach(lc, colinfo->usingNames)
+ + ]
12875 : : {
12876 : 384 : char *colname = (char *) lfirst(lc);
12877 : :
12878 [ + + ]: 384 : if (first)
12879 : 280 : first = false;
12880 : : else
4753 rhaas@postgresql.org 12881 : 104 : appendStringInfoString(buf, ", ");
5057 tgl@sss.pgh.pa.us 12882 : 384 : appendStringInfoString(buf, quote_identifier(colname));
12883 : : }
12884 : 280 : appendStringInfoChar(buf, ')');
12885 : :
2045 peter@eisentraut.org 12886 [ + + ]: 280 : if (j->join_using_alias)
12887 : 8 : appendStringInfo(buf, " AS %s",
12888 : 8 : quote_identifier(j->join_using_alias->aliasname));
12889 : : }
5057 tgl@sss.pgh.pa.us 12890 [ + + ]: 660 : else if (j->quals)
12891 : : {
4753 rhaas@postgresql.org 12892 : 628 : appendStringInfoString(buf, " ON ");
5057 tgl@sss.pgh.pa.us 12893 [ + + ]: 628 : if (!PRETTY_PAREN(context))
12894 : 624 : appendStringInfoChar(buf, '(');
12895 : 628 : get_rule_expr(j->quals, context, false);
12896 [ + + ]: 628 : if (!PRETTY_PAREN(context))
12897 : 624 : appendStringInfoChar(buf, ')');
12898 : : }
3395 12899 [ + + ]: 32 : else if (j->jointype != JOIN_INNER)
12900 : : {
12901 : : /* If we didn't say CROSS JOIN above, we must provide an ON */
12902 : 4 : appendStringInfoString(buf, " ON TRUE");
12903 : : }
12904 : :
8499 12905 [ + + + + ]: 940 : if (!PRETTY_PAREN(context) || j->alias != NULL)
8494 bruce@momjian.us 12906 : 720 : appendStringInfoChar(buf, ')');
12907 : :
12908 : : /* Yes, it's correct to put alias after the right paren ... */
9550 tgl@sss.pgh.pa.us 12909 [ + + ]: 940 : if (j->alias != NULL)
12910 : : {
12911 : : /*
12912 : : * Note that it's correct to emit an alias clause if and only if
12913 : : * there was one originally. Otherwise we'd be converting a named
12914 : : * join to unnamed or vice versa, which creates semantic
12915 : : * subtleties we don't want. However, we might print a different
12916 : : * alias name than was there originally.
12917 : : */
12918 : 72 : appendStringInfo(buf, " %s",
2703 12919 : 72 : quote_identifier(get_rtable_name(j->rtindex,
12920 : : context)));
5057 12921 : 72 : get_column_alias_list(colinfo, context);
12922 : : }
12923 : : }
12924 : : else
8502 tgl@sss.pgh.pa.us 12925 [ # # ]:UBC 0 : elog(ERROR, "unrecognized node type: %d",
12926 : : (int) nodeTag(jtnode));
9550 tgl@sss.pgh.pa.us 12927 :CBC 4641 : }
12928 : :
12929 : : /*
12930 : : * get_rte_alias - print the relation's alias, if needed
12931 : : *
12932 : : * If printed, the alias is preceded by a space, or by " AS " if use_as is true.
12933 : : */
12934 : : static void
1357 12935 : 4028 : get_rte_alias(RangeTblEntry *rte, int varno, bool use_as,
12936 : : deparse_context *context)
12937 : : {
12938 : 4028 : deparse_namespace *dpns = (deparse_namespace *) linitial(context->namespaces);
12939 : 4028 : char *refname = get_rtable_name(varno, context);
12940 : 4028 : deparse_columns *colinfo = deparse_columns_fetch(varno, dpns);
12941 : 4028 : bool printalias = false;
12942 : :
12943 [ + + ]: 4028 : if (rte->alias != NULL)
12944 : : {
12945 : : /* Always print alias if user provided one */
12946 : 1915 : printalias = true;
12947 : : }
12948 [ + + ]: 2113 : else if (colinfo->printaliases)
12949 : : {
12950 : : /* Always print alias if we need to print column aliases */
12951 : 210 : printalias = true;
12952 : : }
12953 [ + + ]: 1903 : else if (rte->rtekind == RTE_RELATION)
12954 : : {
12955 : : /*
12956 : : * No need to print alias if it's same as relation name (this would
12957 : : * normally be the case, but not if set_rtable_names had to resolve a
12958 : : * conflict).
12959 : : */
12960 [ + + ]: 1724 : if (strcmp(refname, get_relation_name(rte->relid)) != 0)
12961 : 52 : printalias = true;
12962 : : }
12963 [ - + ]: 179 : else if (rte->rtekind == RTE_FUNCTION)
12964 : : {
12965 : : /*
12966 : : * For a function RTE, always print alias. This covers possible
12967 : : * renaming of the function and/or instability of the FigureColname
12968 : : * rules for things that aren't simple functions. Note we'd need to
12969 : : * force it anyway for the columndef list case.
12970 : : */
1357 tgl@sss.pgh.pa.us 12971 :UBC 0 : printalias = true;
12972 : : }
1357 tgl@sss.pgh.pa.us 12973 [ + + ]:CBC 179 : else if (rte->rtekind == RTE_SUBQUERY ||
12974 [ + + ]: 163 : rte->rtekind == RTE_VALUES)
12975 : : {
12976 : : /*
12977 : : * For a subquery, always print alias. This makes the output
12978 : : * SQL-spec-compliant, even though we allow such aliases to be omitted
12979 : : * on input.
12980 : : */
12981 : 24 : printalias = true;
12982 : : }
12983 [ + + ]: 155 : else if (rte->rtekind == RTE_CTE)
12984 : : {
12985 : : /*
12986 : : * No need to print alias if it's same as CTE name (this would
12987 : : * normally be the case, but not if set_rtable_names had to resolve a
12988 : : * conflict).
12989 : : */
12990 [ + + ]: 89 : if (strcmp(refname, rte->ctename) != 0)
12991 : 12 : printalias = true;
12992 : : }
12993 : :
12994 [ + + ]: 4028 : if (printalias)
12995 [ + + ]: 2213 : appendStringInfo(context->buf, "%s%s",
12996 : : use_as ? " AS " : " ",
12997 : : quote_identifier(refname));
12998 : 4028 : }
12999 : :
13000 : : /*
13001 : : * get_column_alias_list - print column alias list for an RTE
13002 : : *
13003 : : * Caller must already have printed the relation's alias name.
13004 : : */
13005 : : static void
5057 13006 : 3773 : get_column_alias_list(deparse_columns *colinfo, deparse_context *context)
13007 : : {
8113 13008 : 3773 : StringInfo buf = context->buf;
13009 : : int i;
13010 : 3773 : bool first = true;
13011 : :
13012 : : /* Don't print aliases if not needed */
5057 13013 [ + + ]: 3773 : if (!colinfo->printaliases)
13014 : 2953 : return;
13015 : :
13016 [ + + ]: 6405 : for (i = 0; i < colinfo->num_new_cols; i++)
13017 : : {
13018 : 5585 : char *colname = colinfo->new_colnames[i];
13019 : :
8113 13020 [ + + ]: 5585 : if (first)
13021 : : {
13022 : 820 : appendStringInfoChar(buf, '(');
13023 : 820 : first = false;
13024 : : }
13025 : : else
4753 rhaas@postgresql.org 13026 : 4765 : appendStringInfoString(buf, ", ");
5057 tgl@sss.pgh.pa.us 13027 : 5585 : appendStringInfoString(buf, quote_identifier(colname));
13028 : : }
8113 13029 [ + - ]: 820 : if (!first)
13030 : 820 : appendStringInfoChar(buf, ')');
13031 : : }
13032 : :
13033 : : /*
13034 : : * get_from_clause_coldeflist - reproduce FROM clause coldeflist
13035 : : *
13036 : : * When printing a top-level coldeflist (which is syntactically also the
13037 : : * relation's column alias list), use column names from colinfo. But when
13038 : : * printing a coldeflist embedded inside ROWS FROM(), we prefer to use the
13039 : : * original coldeflist's names, which are available in rtfunc->funccolnames.
13040 : : * Pass NULL for colinfo to select the latter behavior.
13041 : : *
13042 : : * The coldeflist is appended immediately (no space) to buf. Caller is
13043 : : * responsible for ensuring that an alias or AS is present before it.
13044 : : */
13045 : : static void
4732 13046 : 4 : get_from_clause_coldeflist(RangeTblFunction *rtfunc,
13047 : : deparse_columns *colinfo,
13048 : : deparse_context *context)
13049 : : {
8834 13050 : 4 : StringInfo buf = context->buf;
13051 : : ListCell *l1;
13052 : : ListCell *l2;
13053 : : ListCell *l3;
13054 : : ListCell *l4;
13055 : : int i;
13056 : :
13057 : 4 : appendStringInfoChar(buf, '(');
13058 : :
5057 13059 : 4 : i = 0;
2807 13060 [ + - + + : 16 : forfour(l1, rtfunc->funccoltypes,
+ - + + +
- + + + -
+ + + + +
- + - + -
+ + ]
13061 : : l2, rtfunc->funccoltypmods,
13062 : : l3, rtfunc->funccolcollations,
13063 : : l4, rtfunc->funccolnames)
13064 : : {
5057 13065 : 12 : Oid atttypid = lfirst_oid(l1);
13066 : 12 : int32 atttypmod = lfirst_int(l2);
13067 : 12 : Oid attcollation = lfirst_oid(l3);
13068 : : char *attname;
13069 : :
4732 13070 [ - + ]: 12 : if (colinfo)
4732 tgl@sss.pgh.pa.us 13071 :UBC 0 : attname = colinfo->colnames[i];
13072 : : else
4732 tgl@sss.pgh.pa.us 13073 :CBC 12 : attname = strVal(lfirst(l4));
13074 : :
5057 13075 [ - + ]: 12 : Assert(attname); /* shouldn't be any dropped columns here */
13076 : :
8834 13077 [ + + ]: 12 : if (i > 0)
4753 rhaas@postgresql.org 13078 : 8 : appendStringInfoString(buf, ", ");
8834 tgl@sss.pgh.pa.us 13079 : 12 : appendStringInfo(buf, "%s %s",
13080 : : quote_identifier(attname),
13081 : : format_type_with_typemod(atttypid, atttypmod));
5676 13082 [ + + - + ]: 16 : if (OidIsValid(attcollation) &&
13083 : 4 : attcollation != get_typcollation(atttypid))
5705 tgl@sss.pgh.pa.us 13084 :UBC 0 : appendStringInfo(buf, " COLLATE %s",
13085 : : generate_collation_name(attcollation));
13086 : :
8834 tgl@sss.pgh.pa.us 13087 :CBC 12 : i++;
13088 : : }
13089 : :
13090 : 4 : appendStringInfoChar(buf, ')');
13091 : 4 : }
13092 : :
13093 : : /*
13094 : : * get_tablesample_def - print a TableSampleClause
13095 : : */
13096 : : static void
4121 13097 : 18 : get_tablesample_def(TableSampleClause *tablesample, deparse_context *context)
13098 : : {
13099 : 18 : StringInfo buf = context->buf;
13100 : : Oid argtypes[1];
13101 : : int nargs;
13102 : : ListCell *l;
13103 : :
13104 : : /*
13105 : : * We should qualify the handler's function name if it wouldn't be
13106 : : * resolved by lookup in the current search path.
13107 : : */
13108 : 18 : argtypes[0] = INTERNALOID;
13109 : 18 : appendStringInfo(buf, " TABLESAMPLE %s (",
13110 : : generate_function_name(tablesample->tsmhandler, 1,
13111 : : NIL, argtypes,
13112 : : false, NULL, false));
13113 : :
13114 : 18 : nargs = 0;
13115 [ + - + + : 36 : foreach(l, tablesample->args)
+ + ]
13116 : : {
13117 [ - + ]: 18 : if (nargs++ > 0)
4121 tgl@sss.pgh.pa.us 13118 :UBC 0 : appendStringInfoString(buf, ", ");
4121 tgl@sss.pgh.pa.us 13119 :CBC 18 : get_rule_expr((Node *) lfirst(l), context, false);
13120 : : }
13121 : 18 : appendStringInfoChar(buf, ')');
13122 : :
13123 [ + + ]: 18 : if (tablesample->repeatable != NULL)
13124 : : {
13125 : 9 : appendStringInfoString(buf, " REPEATABLE (");
13126 : 9 : get_rule_expr((Node *) tablesample->repeatable, context, false);
13127 : 9 : appendStringInfoChar(buf, ')');
13128 : : }
13129 : 18 : }
13130 : :
13131 : : /*
13132 : : * get_opclass_name - fetch name of an index operator class
13133 : : *
13134 : : * The opclass name is appended (after a space) to buf.
13135 : : *
13136 : : * Output is suppressed if the opclass is the default for the given
13137 : : * actual_datatype. (If you don't want this behavior, just pass
13138 : : * InvalidOid for actual_datatype.)
13139 : : */
13140 : : static void
9163 13141 : 7503 : get_opclass_name(Oid opclass, Oid actual_datatype,
13142 : : StringInfo buf)
13143 : : {
13144 : : HeapTuple ht_opc;
13145 : : Form_pg_opclass opcrec;
13146 : : char *opcname;
13147 : : char *nspname;
13148 : :
6108 rhaas@postgresql.org 13149 : 7503 : ht_opc = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass));
9166 tgl@sss.pgh.pa.us 13150 [ - + ]: 7503 : if (!HeapTupleIsValid(ht_opc))
9166 tgl@sss.pgh.pa.us 13151 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for opclass %u", opclass);
9166 tgl@sss.pgh.pa.us 13152 :CBC 7503 : opcrec = (Form_pg_opclass) GETSTRUCT(ht_opc);
13153 : :
7257 13154 [ + + + + ]: 14980 : if (!OidIsValid(actual_datatype) ||
13155 : 7477 : GetDefaultOpClass(actual_datatype, opcrec->opcmethod) != opclass)
13156 : : {
13157 : : /* Okay, we need the opclass name. Do we need to qualify it? */
8952 13158 : 293 : opcname = NameStr(opcrec->opcname);
7257 13159 [ + - ]: 293 : if (OpclassIsVisible(opclass))
8952 13160 : 293 : appendStringInfo(buf, " %s", quote_identifier(opcname));
13161 : : else
13162 : : {
1927 tgl@sss.pgh.pa.us 13163 :UBC 0 : nspname = get_namespace_name_or_temp(opcrec->opcnamespace);
8952 13164 : 0 : appendStringInfo(buf, " %s.%s",
13165 : : quote_identifier(nspname),
13166 : : quote_identifier(opcname));
13167 : : }
13168 : : }
9166 tgl@sss.pgh.pa.us 13169 :CBC 7503 : ReleaseSysCache(ht_opc);
13170 : 7503 : }
13171 : :
13172 : : /*
13173 : : * generate_opclass_name
13174 : : * Compute the name to display for an opclass specified by OID
13175 : : *
13176 : : * The result includes all necessary quoting and schema-prefixing.
13177 : : */
13178 : : char *
2411 akorotkov@postgresql 13179 : 4 : generate_opclass_name(Oid opclass)
13180 : : {
13181 : : StringInfoData buf;
13182 : :
13183 : 4 : initStringInfo(&buf);
13184 : 4 : get_opclass_name(opclass, InvalidOid, &buf);
13185 : :
2366 tgl@sss.pgh.pa.us 13186 : 4 : return &buf.data[1]; /* get_opclass_name() prepends space */
13187 : : }
13188 : :
13189 : : /*
13190 : : * processIndirection - take care of array and subfield assignment
13191 : : *
13192 : : * We strip any top-level FieldStore or assignment SubscriptingRef nodes that
13193 : : * appear in the input, printing them as decoration for the base column
13194 : : * name (which we assume the caller just printed). We might also need to
13195 : : * strip CoerceToDomain nodes, but only ones that appear above assignment
13196 : : * nodes.
13197 : : *
13198 : : * Returns the subexpression that's to be assigned.
13199 : : */
13200 : : static Node *
3746 13201 : 721 : processIndirection(Node *node, deparse_context *context)
13202 : : {
8184 13203 : 721 : StringInfo buf = context->buf;
3403 13204 : 721 : CoerceToDomain *cdomain = NULL;
13205 : :
13206 : : for (;;)
13207 : : {
8184 13208 [ - + ]: 925 : if (node == NULL)
8184 tgl@sss.pgh.pa.us 13209 :UBC 0 : break;
8184 tgl@sss.pgh.pa.us 13210 [ + + ]:CBC 925 : if (IsA(node, FieldStore))
13211 : : {
13212 : 72 : FieldStore *fstore = (FieldStore *) node;
13213 : : Oid typrelid;
13214 : : char *fieldname;
13215 : :
13216 : : /* lookup tuple type */
13217 : 72 : typrelid = get_typ_typrelid(fstore->resulttype);
13218 [ - + ]: 72 : if (!OidIsValid(typrelid))
8184 tgl@sss.pgh.pa.us 13219 [ # # ]:UBC 0 : elog(ERROR, "argument type %s of FieldStore is not a tuple type",
13220 : : format_type_be(fstore->resulttype));
13221 : :
13222 : : /*
13223 : : * Print the field name. There should only be one target field in
13224 : : * stored rules. There could be more than that in executable
13225 : : * target lists, but this function cannot be used for that case.
13226 : : */
6104 tgl@sss.pgh.pa.us 13227 [ - + ]:CBC 72 : Assert(list_length(fstore->fieldnums) == 1);
3188 alvherre@alvh.no-ip. 13228 : 72 : fieldname = get_attname(typrelid,
13229 : 72 : linitial_int(fstore->fieldnums), false);
3746 tgl@sss.pgh.pa.us 13230 : 72 : appendStringInfo(buf, ".%s", quote_identifier(fieldname));
13231 : :
13232 : : /*
13233 : : * We ignore arg since it should be an uninteresting reference to
13234 : : * the target column or subcolumn.
13235 : : */
8184 13236 : 72 : node = (Node *) linitial(fstore->newvals);
13237 : : }
2834 alvherre@alvh.no-ip. 13238 [ + + ]: 853 : else if (IsA(node, SubscriptingRef))
13239 : : {
13240 : 92 : SubscriptingRef *sbsref = (SubscriptingRef *) node;
13241 : :
13242 [ - + ]: 92 : if (sbsref->refassgnexpr == NULL)
8184 tgl@sss.pgh.pa.us 13243 :UBC 0 : break;
13244 : :
2834 alvherre@alvh.no-ip. 13245 :CBC 92 : printSubscripts(sbsref, context);
13246 : :
13247 : : /*
13248 : : * We ignore refexpr since it should be an uninteresting reference
13249 : : * to the target column or subcolumn.
13250 : : */
13251 : 92 : node = (Node *) sbsref->refassgnexpr;
13252 : : }
3403 tgl@sss.pgh.pa.us 13253 [ + + ]: 761 : else if (IsA(node, CoerceToDomain))
13254 : : {
13255 : 40 : cdomain = (CoerceToDomain *) node;
13256 : : /* If it's an explicit domain coercion, we're done */
13257 [ - + ]: 40 : if (cdomain->coercionformat != COERCE_IMPLICIT_CAST)
3403 tgl@sss.pgh.pa.us 13258 :UBC 0 : break;
13259 : : /* Tentatively descend past the CoerceToDomain */
3403 tgl@sss.pgh.pa.us 13260 :CBC 40 : node = (Node *) cdomain->arg;
13261 : : }
13262 : : else
8184 13263 : 721 : break;
13264 : : }
13265 : :
13266 : : /*
13267 : : * If we descended past a CoerceToDomain whose argument turned out not to
13268 : : * be a FieldStore or array assignment, back up to the CoerceToDomain.
13269 : : * (This is not enough to be fully correct if there are nested implicit
13270 : : * CoerceToDomains, but such cases shouldn't ever occur.)
13271 : : */
3403 13272 [ + + - + ]: 721 : if (cdomain && node == (Node *) cdomain->arg)
3403 tgl@sss.pgh.pa.us 13273 :UBC 0 : node = (Node *) cdomain;
13274 : :
8184 tgl@sss.pgh.pa.us 13275 :CBC 721 : return node;
13276 : : }
13277 : :
13278 : : static void
2834 alvherre@alvh.no-ip. 13279 : 320 : printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
13280 : : {
8184 tgl@sss.pgh.pa.us 13281 : 320 : StringInfo buf = context->buf;
13282 : : ListCell *lowlist_item;
13283 : : ListCell *uplist_item;
13284 : :
2834 alvherre@alvh.no-ip. 13285 : 320 : lowlist_item = list_head(sbsref->reflowerindexpr); /* could be NULL */
13286 [ + - + + : 640 : foreach(uplist_item, sbsref->refupperindexpr)
+ + ]
13287 : : {
8184 tgl@sss.pgh.pa.us 13288 : 320 : appendStringInfoChar(buf, '[');
13289 [ - + ]: 320 : if (lowlist_item)
13290 : : {
13291 : : /* If subexpression is NULL, get_rule_expr prints nothing */
8184 tgl@sss.pgh.pa.us 13292 :UBC 0 : get_rule_expr((Node *) lfirst(lowlist_item), context, false);
13293 : 0 : appendStringInfoChar(buf, ':');
2670 13294 : 0 : lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item);
13295 : : }
13296 : : /* If subexpression is NULL, get_rule_expr prints nothing */
8184 tgl@sss.pgh.pa.us 13297 :CBC 320 : get_rule_expr((Node *) lfirst(uplist_item), context, false);
13298 : 320 : appendStringInfoChar(buf, ']');
13299 : : }
9581 13300 : 320 : }
13301 : :
13302 : : /*
13303 : : * quote_identifier - Quote an identifier only if needed
13304 : : *
13305 : : * When quotes are needed, we palloc the required space; slightly
13306 : : * space-wasteful but well worth it for notational simplicity.
13307 : : */
13308 : : const char *
8960 13309 : 1873664 : quote_identifier(const char *ident)
13310 : : {
13311 : : /*
13312 : : * Can avoid quoting if ident starts with a lowercase letter or underscore
13313 : : * and contains only lowercase letters, digits, and underscores, *and* is
13314 : : * not any SQL keyword. Otherwise, supply quotes.
13315 : : */
8938 13316 : 1873664 : int nquotes = 0;
13317 : : bool safe;
13318 : : const char *ptr;
13319 : : char *result;
13320 : : char *optr;
13321 : :
13322 : : /*
13323 : : * would like to use <ctype.h> macros here, but they might yield unwanted
13324 : : * locale-specific results...
13325 : : */
8957 13326 [ + + - + : 1873664 : safe = ((ident[0] >= 'a' && ident[0] <= 'z') || ident[0] == '_');
+ + ]
13327 : :
8938 13328 [ + + ]: 16373192 : for (ptr = ident; *ptr; ptr++)
13329 : : {
13330 : 14499528 : char ch = *ptr;
13331 : :
13332 [ + + + - : 14499528 : if ((ch >= 'a' && ch <= 'z') ||
+ + ]
13333 [ + + + + ]: 2215712 : (ch >= '0' && ch <= '9') ||
13334 : : (ch == '_'))
13335 : : {
13336 : : /* okay */
13337 : : }
13338 : : else
13339 : : {
13340 : 367952 : safe = false;
13341 [ + + ]: 367952 : if (ch == '"')
13342 : 89 : nquotes++;
13343 : : }
13344 : : }
13345 : :
5950 rhaas@postgresql.org 13346 [ + + ]: 1873664 : if (quote_all_identifiers)
13347 : 6838 : safe = false;
13348 : :
9831 tgl@sss.pgh.pa.us 13349 [ + + ]: 1873664 : if (safe)
13350 : : {
13351 : : /*
13352 : : * Check for keyword. We quote keywords except for unreserved ones.
13353 : : * (In some cases we could avoid quoting a col_name or type_func_name
13354 : : * keyword, but it seems much harder than it's worth to tell that.)
13355 : : *
13356 : : * Note: ScanKeywordLookup() does case-insensitive comparison, but
13357 : : * that's fine, since we already know we have all-lower-case.
13358 : : */
2860 13359 : 1811247 : int kwnum = ScanKeywordLookup(ident, &ScanKeywords);
13360 : :
13361 [ + + + + ]: 1811247 : if (kwnum >= 0 && ScanKeywordCategories[kwnum] != UNRESERVED_KEYWORD)
9831 13362 : 2441 : safe = false;
13363 : : }
13364 : :
9894 13365 [ + + ]: 1873664 : if (safe)
13366 : 1808806 : return ident; /* no change needed */
13367 : :
8938 13368 : 64858 : result = (char *) palloc(strlen(ident) + nquotes + 2 + 1);
13369 : :
13370 : 64858 : optr = result;
13371 : 64858 : *optr++ = '"';
13372 [ + + ]: 518732 : for (ptr = ident; *ptr; ptr++)
13373 : : {
13374 : 453874 : char ch = *ptr;
13375 : :
13376 [ + + ]: 453874 : if (ch == '"')
13377 : 89 : *optr++ = '"';
13378 : 453874 : *optr++ = ch;
13379 : : }
13380 : 64858 : *optr++ = '"';
13381 : 64858 : *optr = '\0';
13382 : :
9894 13383 : 64858 : return result;
13384 : : }
13385 : :
13386 : : /*
13387 : : * quote_qualified_identifier - Quote a possibly-qualified identifier
13388 : : *
13389 : : * Return a name of the form qualifier.ident, or just ident if qualifier
13390 : : * is NULL, quoting each component if necessary. The result is palloc'd.
13391 : : */
13392 : : char *
6321 peter_e@gmx.net 13393 : 720306 : quote_qualified_identifier(const char *qualifier,
13394 : : const char *ident)
13395 : : {
13396 : : StringInfoData buf;
13397 : :
8960 tgl@sss.pgh.pa.us 13398 : 720306 : initStringInfo(&buf);
6321 peter_e@gmx.net 13399 [ + + ]: 720306 : if (qualifier)
13400 : 248185 : appendStringInfo(&buf, "%s.", quote_identifier(qualifier));
8314 neilc@samurai.com 13401 : 720306 : appendStringInfoString(&buf, quote_identifier(ident));
8960 tgl@sss.pgh.pa.us 13402 : 720306 : return buf.data;
13403 : : }
13404 : :
13405 : : /*
13406 : : * get_relation_name
13407 : : * Get the unqualified name of a relation specified by OID
13408 : : *
13409 : : * This differs from the underlying get_rel_name() function in that it will
13410 : : * throw error instead of silently returning NULL if the OID is bad.
13411 : : */
13412 : : static char *
5847 13413 : 10067 : get_relation_name(Oid relid)
13414 : : {
13415 : 10067 : char *relname = get_rel_name(relid);
13416 : :
13417 [ - + ]: 10067 : if (!relname)
5847 tgl@sss.pgh.pa.us 13418 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
5847 tgl@sss.pgh.pa.us 13419 :CBC 10067 : return relname;
13420 : : }
13421 : :
13422 : : /*
13423 : : * generate_relation_name
13424 : : * Compute the name to display for a relation specified by OID
13425 : : *
13426 : : * The result includes all necessary quoting and schema-prefixing.
13427 : : *
13428 : : * If namespaces isn't NIL, it must be a list of deparse_namespace nodes.
13429 : : * We will forcibly qualify the relation name if it equals any CTE name
13430 : : * visible in the namespace list.
13431 : : */
13432 : : static char *
6604 13433 : 4975 : generate_relation_name(Oid relid, List *namespaces)
13434 : : {
13435 : : HeapTuple tp;
13436 : : Form_pg_class reltup;
13437 : : bool need_qual;
13438 : : ListCell *nslist;
13439 : : char *relname;
13440 : : char *nspname;
13441 : : char *result;
13442 : :
6108 rhaas@postgresql.org 13443 : 4975 : tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
8952 tgl@sss.pgh.pa.us 13444 [ - + ]: 4975 : if (!HeapTupleIsValid(tp))
8502 tgl@sss.pgh.pa.us 13445 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
8952 tgl@sss.pgh.pa.us 13446 :CBC 4975 : reltup = (Form_pg_class) GETSTRUCT(tp);
6604 13447 : 4975 : relname = NameStr(reltup->relname);
13448 : :
13449 : : /* Check for conflicting CTE name */
13450 : 4975 : need_qual = false;
13451 [ + + + + : 8471 : foreach(nslist, namespaces)
+ + ]
13452 : : {
13453 : 3496 : deparse_namespace *dpns = (deparse_namespace *) lfirst(nslist);
13454 : : ListCell *ctlist;
13455 : :
13456 [ + + + + : 3584 : foreach(ctlist, dpns->ctes)
+ + ]
13457 : : {
13458 : 88 : CommonTableExpr *cte = (CommonTableExpr *) lfirst(ctlist);
13459 : :
13460 [ - + ]: 88 : if (strcmp(cte->ctename, relname) == 0)
13461 : : {
6604 tgl@sss.pgh.pa.us 13462 :UBC 0 : need_qual = true;
13463 : 0 : break;
13464 : : }
13465 : : }
6604 tgl@sss.pgh.pa.us 13466 [ - + ]:CBC 3496 : if (need_qual)
6604 tgl@sss.pgh.pa.us 13467 :UBC 0 : break;
13468 : : }
13469 : :
13470 : : /* Otherwise, qualify the name if not visible in search path */
6604 tgl@sss.pgh.pa.us 13471 [ + - ]:CBC 4975 : if (!need_qual)
13472 : 4975 : need_qual = !RelationIsVisible(relid);
13473 : :
13474 [ + + ]: 4975 : if (need_qual)
1927 13475 : 1272 : nspname = get_namespace_name_or_temp(reltup->relnamespace);
13476 : : else
6604 13477 : 3703 : nspname = NULL;
13478 : :
13479 : 4975 : result = quote_qualified_identifier(nspname, relname);
13480 : :
8952 13481 : 4975 : ReleaseSysCache(tp);
13482 : :
13483 : 4975 : return result;
13484 : : }
13485 : :
13486 : : /*
13487 : : * generate_qualified_relation_name
13488 : : * Compute the name to display for a relation specified by OID
13489 : : *
13490 : : * As above, but unconditionally schema-qualify the name.
13491 : : */
13492 : : static char *
4003 13493 : 4590 : generate_qualified_relation_name(Oid relid)
13494 : : {
13495 : : HeapTuple tp;
13496 : : Form_pg_class reltup;
13497 : : char *result;
13498 : :
13499 : 4590 : tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
13500 [ - + ]: 4590 : if (!HeapTupleIsValid(tp))
4003 tgl@sss.pgh.pa.us 13501 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
4003 tgl@sss.pgh.pa.us 13502 :CBC 4590 : reltup = (Form_pg_class) GETSTRUCT(tp);
13503 : :
181 akapila@postgresql.o 13504 : 4590 : result = get_qualified_objname(reltup->relnamespace,
13505 : 4590 : NameStr(reltup->relname));
4003 tgl@sss.pgh.pa.us 13506 : 4590 : ReleaseSysCache(tp);
13507 : :
13508 : 4590 : return result;
13509 : : }
13510 : :
13511 : : /*
13512 : : * generate_function_name
13513 : : * Compute the name to display for a function specified by OID,
13514 : : * given that it is being called with the specified actual arg names and
13515 : : * types. (Those matter because of ambiguous-function resolution rules.)
13516 : : *
13517 : : * If we're dealing with a potentially variadic function (in practice, this
13518 : : * means a FuncExpr or Aggref, not some other way of calling a function), then
13519 : : * has_variadic must specify whether variadic arguments have been merged,
13520 : : * and *use_variadic_p will be set to indicate whether to print VARIADIC in
13521 : : * the output. For non-FuncExpr cases, has_variadic should be false and
13522 : : * use_variadic_p can be NULL.
13523 : : *
13524 : : * inGroupBy must be true if we're deparsing a GROUP BY clause.
13525 : : *
13526 : : * The result includes all necessary quoting and schema-prefixing.
13527 : : */
13528 : : static char *
5036 13529 : 9861 : generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
13530 : : bool has_variadic, bool *use_variadic_p,
13531 : : bool inGroupBy)
13532 : : {
13533 : : char *result;
13534 : : HeapTuple proctup;
13535 : : Form_pg_proc procform;
13536 : : char *proname;
13537 : : bool use_variadic;
13538 : : char *nspname;
13539 : : FuncDetailCode p_result;
13540 : : int fgc_flags;
13541 : : Oid p_funcid;
13542 : : Oid p_rettype;
13543 : : bool p_retset;
13544 : : int p_nvargs;
13545 : : Oid p_vatype;
13546 : : Oid *p_true_typeids;
4191 andres@anarazel.de 13547 : 9861 : bool force_qualify = false;
13548 : :
6108 rhaas@postgresql.org 13549 : 9861 : proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
8952 tgl@sss.pgh.pa.us 13550 [ - + ]: 9861 : if (!HeapTupleIsValid(proctup))
8502 tgl@sss.pgh.pa.us 13551 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for function %u", funcid);
8952 tgl@sss.pgh.pa.us 13552 :CBC 9861 : procform = (Form_pg_proc) GETSTRUCT(proctup);
13553 : 9861 : proname = NameStr(procform->proname);
13554 : :
13555 : : /*
13556 : : * Due to parser hacks to avoid needing to reserve CUBE, we need to force
13557 : : * qualification of some function names within GROUP BY.
13558 : : */
798 13559 [ - + ]: 9861 : if (inGroupBy)
13560 : : {
4191 andres@anarazel.de 13561 [ # # # # ]:UBC 0 : if (strcmp(proname, "cube") == 0 || strcmp(proname, "rollup") == 0)
13562 : 0 : force_qualify = true;
13563 : : }
13564 : :
13565 : : /*
13566 : : * Determine whether VARIADIC should be printed. We must do this first
13567 : : * since it affects the lookup rules in func_get_detail().
13568 : : *
13569 : : * We always print VARIADIC if the function has a merged variadic-array
13570 : : * argument. Note that this is always the case for functions taking a
13571 : : * VARIADIC argument type other than VARIADIC ANY. If we omitted VARIADIC
13572 : : * and printed the array elements as separate arguments, the call could
13573 : : * match a newer non-VARIADIC function.
13574 : : */
5036 tgl@sss.pgh.pa.us 13575 [ + + ]:CBC 9861 : if (use_variadic_p)
13576 : : {
13577 : : /* Parser should not have set funcvariadic unless fn is variadic */
4599 13578 [ + + - + ]: 8866 : Assert(!has_variadic || OidIsValid(procform->provariadic));
13579 : 8866 : use_variadic = has_variadic;
5036 13580 : 8866 : *use_variadic_p = use_variadic;
13581 : : }
13582 : : else
13583 : : {
4599 13584 [ - + ]: 995 : Assert(!has_variadic);
5036 13585 : 995 : use_variadic = false;
13586 : : }
13587 : :
13588 : : /*
13589 : : * The idea here is to schema-qualify only if the parser would fail to
13590 : : * resolve the correct function given the unqualified func name with the
13591 : : * specified argtypes and VARIADIC flag. But if we already decided to
13592 : : * force qualification, then we can skip the lookup and pretend we didn't
13593 : : * find it.
13594 : : */
4191 andres@anarazel.de 13595 [ + - ]: 9861 : if (!force_qualify)
13596 : 9861 : p_result = func_get_detail(list_make1(makeString(proname)),
13597 : : NIL, argnames, nargs, argtypes,
13598 : : !use_variadic, true, false,
13599 : : &fgc_flags,
13600 : : &p_funcid, &p_rettype,
13601 : : &p_retset, &p_nvargs, &p_vatype,
13602 : 9861 : &p_true_typeids, NULL);
13603 : : else
13604 : : {
4191 andres@anarazel.de 13605 :UBC 0 : p_result = FUNCDETAIL_NOTFOUND;
13606 : 0 : p_funcid = InvalidOid;
13607 : : }
13608 : :
6521 tgl@sss.pgh.pa.us 13609 [ + + + + ]:CBC 9861 : if ((p_result == FUNCDETAIL_NORMAL ||
13610 [ + + ]: 683 : p_result == FUNCDETAIL_AGGREGATE ||
13611 : 9274 : p_result == FUNCDETAIL_WINDOWFUNC) &&
8525 13612 [ + - ]: 9274 : p_funcid == funcid)
8952 13613 : 9274 : nspname = NULL;
13614 : : else
1927 13615 : 587 : nspname = get_namespace_name_or_temp(procform->pronamespace);
13616 : :
8952 13617 : 9861 : result = quote_qualified_identifier(nspname, proname);
13618 : :
13619 : 9861 : ReleaseSysCache(proctup);
13620 : :
13621 : 9861 : return result;
13622 : : }
13623 : :
13624 : : /*
13625 : : * generate_operator_name
13626 : : * Compute the name to display for an operator specified by OID,
13627 : : * given that it is being called with the specified actual arg types.
13628 : : * (Arg types matter because of ambiguous-operator resolution rules.
13629 : : * Pass InvalidOid for unused arg of a unary operator.)
13630 : : *
13631 : : * The result includes all necessary quoting and schema-prefixing,
13632 : : * plus the OPERATOR() decoration needed to use a qualified operator name
13633 : : * in an expression.
13634 : : */
13635 : : static char *
13636 : 45151 : generate_operator_name(Oid operid, Oid arg1, Oid arg2)
13637 : : {
13638 : : StringInfoData buf;
13639 : : HeapTuple opertup;
13640 : : Form_pg_operator operform;
13641 : : char *oprname;
13642 : : char *nspname;
13643 : : Operator p_result;
13644 : :
13645 : 45151 : initStringInfo(&buf);
13646 : :
6108 rhaas@postgresql.org 13647 : 45151 : opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operid));
8952 tgl@sss.pgh.pa.us 13648 [ - + ]: 45151 : if (!HeapTupleIsValid(opertup))
8502 tgl@sss.pgh.pa.us 13649 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for operator %u", operid);
8952 tgl@sss.pgh.pa.us 13650 :CBC 45151 : operform = (Form_pg_operator) GETSTRUCT(opertup);
13651 : 45151 : oprname = NameStr(operform->oprname);
13652 : :
13653 : : /*
13654 : : * The idea here is to schema-qualify only if the parser would fail to
13655 : : * resolve the correct operator given the unqualified op name with the
13656 : : * specified argtypes.
13657 : : */
13658 [ + + - ]: 45151 : switch (operform->oprkind)
13659 : : {
13660 : 45115 : case 'b':
7541 13661 : 45115 : p_result = oper(NULL, list_make1(makeString(oprname)), arg1, arg2,
13662 : : true, -1);
8952 13663 : 45115 : break;
13664 : 36 : case 'l':
7541 13665 : 36 : p_result = left_oper(NULL, list_make1(makeString(oprname)), arg2,
13666 : : true, -1);
8952 13667 : 36 : break;
8952 tgl@sss.pgh.pa.us 13668 :UBC 0 : default:
8502 13669 [ # # ]: 0 : elog(ERROR, "unrecognized oprkind: %d", operform->oprkind);
13670 : : p_result = NULL; /* keep compiler quiet */
13671 : : break;
13672 : : }
13673 : :
8952 tgl@sss.pgh.pa.us 13674 [ + + + - ]:CBC 45151 : if (p_result != NULL && oprid(p_result) == operid)
13675 : 45146 : nspname = NULL;
13676 : : else
13677 : : {
1927 13678 : 5 : nspname = get_namespace_name_or_temp(operform->oprnamespace);
8952 13679 : 5 : appendStringInfo(&buf, "OPERATOR(%s.", quote_identifier(nspname));
13680 : : }
13681 : :
8314 neilc@samurai.com 13682 : 45151 : appendStringInfoString(&buf, oprname);
13683 : :
8952 tgl@sss.pgh.pa.us 13684 [ + + ]: 45151 : if (nspname)
13685 : 5 : appendStringInfoChar(&buf, ')');
13686 : :
13687 [ + + ]: 45151 : if (p_result != NULL)
13688 : 45146 : ReleaseSysCache(p_result);
13689 : :
13690 : 45151 : ReleaseSysCache(opertup);
13691 : :
13692 : 45151 : return buf.data;
13693 : : }
13694 : :
13695 : : /*
13696 : : * generate_operator_clause --- generate a binary-operator WHERE clause
13697 : : *
13698 : : * This is used for internally-generated-and-executed SQL queries, where
13699 : : * precision is essential and readability is secondary. The basic
13700 : : * requirement is to append "leftop op rightop" to buf, where leftop and
13701 : : * rightop are given as strings and are assumed to yield types leftoptype
13702 : : * and rightoptype; the operator is identified by OID. The complexity
13703 : : * comes from needing to be sure that the parser will select the desired
13704 : : * operator when the query is parsed. We always name the operator using
13705 : : * OPERATOR(schema.op) syntax, so as to avoid search-path uncertainties.
13706 : : * We have to emit casts too, if either input isn't already the input type
13707 : : * of the operator; else we are at the mercy of the parser's heuristics for
13708 : : * ambiguous-operator resolution. The caller must ensure that leftop and
13709 : : * rightop are suitable arguments for a cast operation; it's best to insert
13710 : : * parentheses if they aren't just variables or parameters.
13711 : : */
13712 : : void
3153 13713 : 3307 : generate_operator_clause(StringInfo buf,
13714 : : const char *leftop, Oid leftoptype,
13715 : : Oid opoid,
13716 : : const char *rightop, Oid rightoptype)
13717 : : {
13718 : : HeapTuple opertup;
13719 : : Form_pg_operator operform;
13720 : : char *oprname;
13721 : : char *nspname;
13722 : :
13723 : 3307 : opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(opoid));
13724 [ - + ]: 3307 : if (!HeapTupleIsValid(opertup))
3153 tgl@sss.pgh.pa.us 13725 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for operator %u", opoid);
3153 tgl@sss.pgh.pa.us 13726 :CBC 3307 : operform = (Form_pg_operator) GETSTRUCT(opertup);
13727 [ - + ]: 3307 : Assert(operform->oprkind == 'b');
13728 : 3307 : oprname = NameStr(operform->oprname);
13729 : :
13730 : 3307 : nspname = get_namespace_name(operform->oprnamespace);
13731 : :
13732 : 3307 : appendStringInfoString(buf, leftop);
13733 [ + + ]: 3307 : if (leftoptype != operform->oprleft)
13734 : 740 : add_cast_to(buf, operform->oprleft);
13735 : 3307 : appendStringInfo(buf, " OPERATOR(%s.", quote_identifier(nspname));
13736 : 3307 : appendStringInfoString(buf, oprname);
13737 : 3307 : appendStringInfo(buf, ") %s", rightop);
13738 [ + + ]: 3307 : if (rightoptype != operform->oprright)
13739 : 684 : add_cast_to(buf, operform->oprright);
13740 : :
13741 : 3307 : ReleaseSysCache(opertup);
13742 : 3307 : }
13743 : :
13744 : : /*
13745 : : * Add a cast specification to buf. We spell out the type name the hard way,
13746 : : * intentionally not using format_type_be(). This is to avoid corner cases
13747 : : * for CHARACTER, BIT, and perhaps other types, where specifying the type
13748 : : * using SQL-standard syntax results in undesirable data truncation. By
13749 : : * doing it this way we can be certain that the cast will have default (-1)
13750 : : * target typmod.
13751 : : */
13752 : : static void
13753 : 1424 : add_cast_to(StringInfo buf, Oid typid)
13754 : : {
13755 : : HeapTuple typetup;
13756 : : Form_pg_type typform;
13757 : : char *typname;
13758 : : char *nspname;
13759 : :
13760 : 1424 : typetup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
13761 [ - + ]: 1424 : if (!HeapTupleIsValid(typetup))
3153 tgl@sss.pgh.pa.us 13762 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for type %u", typid);
3153 tgl@sss.pgh.pa.us 13763 :CBC 1424 : typform = (Form_pg_type) GETSTRUCT(typetup);
13764 : :
13765 : 1424 : typname = NameStr(typform->typname);
1927 13766 : 1424 : nspname = get_namespace_name_or_temp(typform->typnamespace);
13767 : :
3153 13768 : 1424 : appendStringInfo(buf, "::%s.%s",
13769 : : quote_identifier(nspname), quote_identifier(typname));
13770 : :
13771 : 1424 : ReleaseSysCache(typetup);
13772 : 1424 : }
13773 : :
13774 : : /*
13775 : : * generate_qualified_type_name
13776 : : * Compute the name to display for a type specified by OID
13777 : : *
13778 : : * This is different from format_type_be() in that we unconditionally
13779 : : * schema-qualify the name. That also means no special syntax for
13780 : : * SQL-standard type names ... although in current usage, this should
13781 : : * only get used for domains, so such cases wouldn't occur anyway.
13782 : : */
13783 : : static char *
3291 13784 : 13 : generate_qualified_type_name(Oid typid)
13785 : : {
13786 : : HeapTuple tp;
13787 : : Form_pg_type typtup;
13788 : : char *typname;
13789 : : char *nspname;
13790 : : char *result;
13791 : :
13792 : 13 : tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
13793 [ - + ]: 13 : if (!HeapTupleIsValid(tp))
3291 tgl@sss.pgh.pa.us 13794 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for type %u", typid);
3291 tgl@sss.pgh.pa.us 13795 :CBC 13 : typtup = (Form_pg_type) GETSTRUCT(tp);
13796 : 13 : typname = NameStr(typtup->typname);
13797 : :
1927 13798 : 13 : nspname = get_namespace_name_or_temp(typtup->typnamespace);
3291 13799 [ - + ]: 13 : if (!nspname)
3291 tgl@sss.pgh.pa.us 13800 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for namespace %u",
13801 : : typtup->typnamespace);
13802 : :
3291 tgl@sss.pgh.pa.us 13803 :CBC 13 : result = quote_qualified_identifier(nspname, typname);
13804 : :
13805 : 13 : ReleaseSysCache(tp);
13806 : :
13807 : 13 : return result;
13808 : : }
13809 : :
13810 : : /*
13811 : : * generate_collation_name
13812 : : * Compute the name to display for a collation specified by OID
13813 : : *
13814 : : * The result includes all necessary quoting and schema-prefixing.
13815 : : */
13816 : : char *
5749 peter_e@gmx.net 13817 : 306 : generate_collation_name(Oid collid)
13818 : : {
13819 : : HeapTuple tp;
13820 : : Form_pg_collation colltup;
13821 : : char *collname;
13822 : : char *nspname;
13823 : : char *result;
13824 : :
13825 : 306 : tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collid));
13826 [ - + ]: 306 : if (!HeapTupleIsValid(tp))
5749 peter_e@gmx.net 13827 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for collation %u", collid);
5749 peter_e@gmx.net 13828 :CBC 306 : colltup = (Form_pg_collation) GETSTRUCT(tp);
13829 : 306 : collname = NameStr(colltup->collname);
13830 : :
13831 [ - + ]: 306 : if (!CollationIsVisible(collid))
1927 tgl@sss.pgh.pa.us 13832 :UBC 0 : nspname = get_namespace_name_or_temp(colltup->collnamespace);
13833 : : else
5749 peter_e@gmx.net 13834 :CBC 306 : nspname = NULL;
13835 : :
13836 : 306 : result = quote_qualified_identifier(nspname, collname);
13837 : :
13838 : 306 : ReleaseSysCache(tp);
13839 : :
13840 : 306 : return result;
13841 : : }
13842 : :
13843 : : /*
13844 : : * Given a C string, produce a TEXT datum.
13845 : : *
13846 : : * We assume that the input was palloc'd and may be freed.
13847 : : */
13848 : : static text *
8219 tgl@sss.pgh.pa.us 13849 : 25319 : string_to_text(char *str)
13850 : : {
13851 : : text *result;
13852 : :
6799 13853 : 25319 : result = cstring_to_text(str);
8219 13854 : 25319 : pfree(str);
13855 : 25319 : return result;
13856 : : }
13857 : :
13858 : : /*
13859 : : * Generate a C string representing a relation options from text[] datum.
13860 : : */
13861 : : void
2411 akorotkov@postgresql 13862 : 131 : get_reloptions(StringInfo buf, Datum reloptions)
13863 : : {
13864 : : Datum *options;
13865 : : int noptions;
13866 : : int i;
13867 : :
1588 peter@eisentraut.org 13868 : 131 : deconstruct_array_builtin(DatumGetArrayTypeP(reloptions), TEXTOID,
13869 : : &options, NULL, &noptions);
13870 : :
2411 akorotkov@postgresql 13871 [ + + ]: 284 : for (i = 0; i < noptions; i++)
13872 : : {
13873 : 153 : char *option = TextDatumGetCString(options[i]);
13874 : : char *name;
13875 : : char *separator;
13876 : : char *value;
13877 : :
13878 : : /*
13879 : : * Each array element should have the form name=value. If the "=" is
13880 : : * missing for some reason, treat it like an empty value.
13881 : : */
13882 : 153 : name = option;
13883 : 153 : separator = strchr(option, '=');
13884 [ + - ]: 153 : if (separator)
13885 : : {
13886 : 153 : *separator = '\0';
13887 : 153 : value = separator + 1;
13888 : : }
13889 : : else
2411 akorotkov@postgresql 13890 :UBC 0 : value = "";
13891 : :
2411 akorotkov@postgresql 13892 [ + + ]:CBC 153 : if (i > 0)
13893 : 22 : appendStringInfoString(buf, ", ");
13894 : 153 : appendStringInfo(buf, "%s=", quote_identifier(name));
13895 : :
13896 : : /*
13897 : : * In general we need to quote the value; but to avoid unnecessary
13898 : : * clutter, do not quote if it is an identifier that would not need
13899 : : * quoting. (We could also allow numbers, but that is a bit trickier
13900 : : * than it looks --- for example, are leading zeroes significant? We
13901 : : * don't want to assume very much here about what custom reloptions
13902 : : * might mean.)
13903 : : */
13904 [ + + ]: 153 : if (quote_identifier(value) == value)
13905 : 4 : appendStringInfoString(buf, value);
13906 : : else
13907 : 149 : simple_quote_literal(buf, value);
13908 : :
13909 : 153 : pfree(option);
13910 : : }
13911 : 131 : }
13912 : :
13913 : : /*
13914 : : * Generate a C string representing a relation's reloptions, or NULL if none.
13915 : : */
13916 : : static char *
7431 bruce@momjian.us 13917 : 4581 : flatten_reloptions(Oid relid)
13918 : : {
13919 : 4581 : char *result = NULL;
13920 : : HeapTuple tuple;
13921 : : Datum reloptions;
13922 : : bool isnull;
13923 : :
6108 rhaas@postgresql.org 13924 : 4581 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
7430 tgl@sss.pgh.pa.us 13925 [ - + ]: 4581 : if (!HeapTupleIsValid(tuple))
7430 tgl@sss.pgh.pa.us 13926 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
13927 : :
7430 tgl@sss.pgh.pa.us 13928 :CBC 4581 : reloptions = SysCacheGetAttr(RELOID, tuple,
13929 : : Anum_pg_class_reloptions, &isnull);
13930 [ + + ]: 4581 : if (!isnull)
13931 : : {
13932 : : StringInfoData buf;
13933 : :
3961 13934 : 105 : initStringInfo(&buf);
2411 akorotkov@postgresql 13935 : 105 : get_reloptions(&buf, reloptions);
13936 : :
3961 tgl@sss.pgh.pa.us 13937 : 105 : result = buf.data;
13938 : : }
13939 : :
7430 13940 : 4581 : ReleaseSysCache(tuple);
13941 : :
7431 bruce@momjian.us 13942 : 4581 : return result;
13943 : : }
13944 : :
13945 : : /*
13946 : : * get_range_partbound_string
13947 : : * A C string representation of one range partition bound
13948 : : */
13949 : : char *
3374 rhaas@postgresql.org 13950 : 2702 : get_range_partbound_string(List *bound_datums)
13951 : : {
13952 : : deparse_context context;
13953 : : StringInfoData buf;
13954 : : ListCell *cell;
13955 : : char *sep;
13956 : :
364 drowley@postgresql.o 13957 : 2702 : initStringInfo(&buf);
3374 rhaas@postgresql.org 13958 : 2702 : memset(&context, 0, sizeof(deparse_context));
364 drowley@postgresql.o 13959 : 2702 : context.buf = &buf;
13960 : :
13961 : 2702 : appendStringInfoChar(&buf, '(');
3374 rhaas@postgresql.org 13962 : 2702 : sep = "";
13963 [ + - + + : 5882 : foreach(cell, bound_datums)
+ + ]
13964 : : {
13965 : : PartitionRangeDatum *datum =
1266 tgl@sss.pgh.pa.us 13966 : 3180 : lfirst_node(PartitionRangeDatum, cell);
13967 : :
364 drowley@postgresql.o 13968 : 3180 : appendStringInfoString(&buf, sep);
3374 rhaas@postgresql.org 13969 [ + + ]: 3180 : if (datum->kind == PARTITION_RANGE_DATUM_MINVALUE)
364 drowley@postgresql.o 13970 : 148 : appendStringInfoString(&buf, "MINVALUE");
3374 rhaas@postgresql.org 13971 [ + + ]: 3032 : else if (datum->kind == PARTITION_RANGE_DATUM_MAXVALUE)
364 drowley@postgresql.o 13972 : 80 : appendStringInfoString(&buf, "MAXVALUE");
13973 : : else
13974 : : {
3374 rhaas@postgresql.org 13975 : 2952 : Const *val = castNode(Const, datum->value);
13976 : :
13977 : 2952 : get_const_expr(val, &context, -1);
13978 : : }
13979 : 3180 : sep = ", ";
13980 : : }
364 drowley@postgresql.o 13981 : 2702 : appendStringInfoChar(&buf, ')');
13982 : :
13983 : 2702 : return buf.data;
13984 : : }
|